2026/1/17 4:14:29
网站建设
项目流程
中文域名怎样绑定网站,安康网站建设公司报价,wap网站代码,跨越速运在黑龙江黑河网点#x1f468;#x1f4bb;程序员三明治#xff1a;个人主页#x1f525; 个人专栏: 《设计模式精解》 《重学数据结构》#x1f91e;先做到 再看见#xff01; 目录行级锁Record LockGap LockNext-Key Lockselect ... for update有啥用#xff1f;我不加for update不行…程序员三明治个人主页 个人专栏: 《设计模式精解》 《重学数据结构》先做到 再看见目录行级锁Record LockGap LockNext-Key Lockselect ... for update有啥用我不加for update不行吗MySQL 是怎么加行级锁的唯一索引主键索引等值查询唯一索引主键索引范围查询实验一针对「大于」的范围查询的情况。实验二针对「大于等于」的范围查询的情况。实验三针对「小于」的范围查询时查询条件值的记录「不存在」表中的情况。实验四针对「小于等于」的范围查询时查询条件值的记录「存在」表中的情况。非唯一索引等值查询1、记录不存在的情况2、记录存在的情况非唯一索引范围查询没有加索引的查询死锁死锁的发生如何避免死锁行级锁InnoDB 引擎是支持行级锁的而 MyISAM 引擎并不支持行级锁。顾名思义行锁就是针对数据表中行记录的锁。这很好理解比如事务 A 更新了一行而这时候事务 B 也要更新同一行则必须等事务 A 的操作完成后才能进行更新。前面也提到普通的 select 语句是不会对记录加锁的因为它属于快照读。如果要在查询时对记录加行锁可以使用下面这两个方式这种查询会加锁的语句称为锁定读。//对读取的记录加共享锁select...lock in share mode;//对读取的记录加独占锁select...forupdate;Record LockRecord Lock 称为记录锁锁住的是一条记录。而且记录锁是有 S 锁和 X 锁之分的当一个事务对一条记录加了 S 型记录锁后其他事务也可以继续对该记录加 S 型记录锁S 型与 S 锁兼容但是不可以对该记录加 X 型记录锁S 型与 X 锁不兼容;当一个事务对一条记录加了 X 型记录锁后其他事务既不可以对该记录加 S 型记录锁S 型与 X 锁不兼容也不可以对该记录加 X 型记录锁X 型与 X 锁不兼容。举个例子当一个事务执行了下面这条语句mysqlbegin;mysqlselect*fromt_testwhereid1forupdate;就是对 t_test 表中主键 id 为 1 的这条记录加上 X 型的记录锁这样其他事务就无法对这条记录进行修改了。当事务执行 commit 后事务过程中生成的锁都会被释放。Gap LockGap Lock 称为间隙锁存在于可重复读隔离级别和串行化隔离级别目的是为了解决可重复读隔离级别下幻读的现象。假设表中有一个范围 id 为35间隙锁那么其他事务就无法插入 id 4 这条记录了这样就有效的防止幻读现象的发生。Next-Key LockNext-Key Lock 称为临键锁是 Record Lock Gap Lock 的组合锁定一个范围并且锁定记录本身。假设表中有一个范围 id 为35] 的 next-key lock那么其他事务即不能插入 id 4 记录也不能修改 id 5 这条记录。所以next-key lock 即能保护该记录又能阻止其他事务将新纪录插入到被保护记录前面的间隙中。next-key lock 是包含间隙锁记录锁的如果一个事务获取了 X 型的 next-key lock那么另外一个事务在获取相同范围的 X 型的 next-key lock 时是会被阻塞的。select … for update有啥用我不加for update不行吗可以解决“快照读”在特定场景下的不足在 MySQL 的默认隔离级别“可重复读”下普通的SELECT语句是“快照读”。它基于 MVCC 读取一个历史快照不会加锁。这虽然保证了高并发下的读取性能但在某些业务场景下会产生问题。经典场景库存扣减假设商品 A 的库存为 1。事务 A 查询库存SELECT stock FROM products WHERE id 1;返回 stock1事务 B 也查询库存SELECT stock FROM products WHERE id 1;返回 stock1事务 A 下单执行UPDATE products SET stock stock - 1 WHERE id 1并提交。此时库存已为 0。事务 B 也执行UPDATE products SET stock stock - 1 WHERE id 1并提交。最终结果是库存变成了-1这就是典型的“超卖”问题。使用SELECT … FOR UPDATE解决事务 A 查询并锁定库存SELECT stock FROM products WHERE id 1 FOR UPDATE;对 id1 的记录加排他锁事务 B 也尝试执行SELECT … FOR UPDATE查询同一件商品但会被阻塞直到事务 A 释放锁。事务 A 完成下单和更新操作提交事务释放锁。事务 B 获得锁执行查询此时它读到的 stock 已经是事务 A 更新后的 0因此可以阻止后续的扣减操作。MySQL 是怎么加行级锁的加锁的对象是索引加锁的基本单位是 next-key lock。但是next-key lock 在一些场景下会退化成记录锁或间隙锁。那到底是什么场景呢总结一句在能使用记录锁或者间隙锁就能避免幻读现象的场景下 next-key lock 就会退化成记录锁或间隙锁。这次会以下面这个表结构来进行实验说明CREATETABLEuser(idbigintNOTNULLAUTO_INCREMENT,namevarchar(30)COLLATEutf8mb4_unicode_ciNOTNULL,ageintNOTNULL,PRIMARYKEY(id),KEYindex_age(age)USINGBTREE)ENGINEInnoDBDEFAULTCHARSETutf8mb4COLLATEutf8mb4_unicode_ci;其中id 是主键索引唯一索引age 是普通索引非唯一索引name 是普通的列。表中的有这些行记录我本篇文章的「唯一索引」是用「主键索引」作为案例说明的加锁只加在主键索引项上。然后很多同学误以为如果是二级索引的「唯一索引」加锁也是只加在二级索引项上。其实这是不对的所以这里特此说明下如果是用二级索引不管是不是非唯一索引还是唯一索引进行锁定读查询的时候除了会对二级索引项加行级锁如果是唯一索引的二级索引加锁规则和主键索引的案例相同而且还会对查询到的记录的主键索引项上加「记录锁」。在文章的「非唯一索引」的案例中我就是用二级索引作为例子在后面的章节我有说明对二级索引进行锁定读查询的时候因为存在两个索引二级索引和主键索引所以两个索引都会加锁。唯一索引主键索引等值查询当我们用唯一索引进行等值查询的时候查询的记录存不存在加锁的规则也会不同当查询的记录是「存在」的在索引树上定位到这一条记录后将该记录的索引中的 next-key lock 会退化成「记录锁」。mysqlbegin;Query OK,0rowsaffected(0.00sec)mysqlselect*fromuserwhereid1forupdate;-----------------|id|name|age|-----------------|1|路飞|19|-----------------1rowinset(0.02sec)接下来如果有其他事务对 id 为 1 的记录进行更新或者删除操作的话这些操作都会被阻塞因为更新或者删除操作也会对记录加 X 型的记录锁而 X 锁和 X 锁之间是互斥关系。当查询的记录是「不存在」的在索引树找到第一条大于该查询记录的记录后将该记录的索引中的 next-key lock 会退化成「间隙锁」。mysqlbegin;Query OK,0rowsaffected(0.00sec)mysqlselect*fromuserwhereid2forupdate;Emptyset(0.03sec)此时事务 A 在 id 5 记录的主键索引上加的是间隙锁锁住的范围是 (1, 5)。唯一索引主键索引范围查询实验一针对「大于」的范围查询的情况。假设事务 A 执行了这条范围查询语句mysqlbegin;Query OK,0rowsaffected(0.00sec)mysqlselect*fromuserwhereid15forupdate;--------------------|id|name|age|--------------------|20|香克斯|39|--------------------1rowinset(0.01sec)在 id 20 这条记录的主键索引上加了范围为 (15, 20] 的 next-key 锁意味着其他事务即无法更新或者删除 id 20 的记录同时无法插入 id 值为 16、17、18、19 的这一些新记录。在特殊记录 supremum pseudo-record的主键索引上加了范围为 (20, ∞] 的 next-key 锁意味着其他事务无法插入 id 值大于 20 的这一些新记录。实验二针对「大于等于」的范围查询的情况。假设事务 A 执行了这条范围查询语句mysqlbegin;Query OK,0rowsaffected(0.00sec)mysqlselect*fromuserwhereid15forupdate;--------------------|id|name|age|--------------------|15|乌索普|20||20|香克斯|39|--------------------2rowsinset(0.00sec)在 id 15 这条记录的主键索引上加了记录锁范围是 id 15 这一行记录意味着其他事务无法更新或者删除 id 15 的这一条记录在 id 20 这条记录的主键索引上加了 next-key 锁范围是 (15, 20] 。意味着其他事务即无法更新或者删除 id 20 的记录同时无法插入 id 值为 16、17、18、19 的这一些新记录。在特殊记录 supremum pseudo-record的主键索引上加了 next-key 锁范围是 (20, ∞] 。意味着其他事务无法插入 id 值大于 20 的这一些新记录。实验三针对「小于」的范围查询时查询条件值的记录「不存在」表中的情况。假设事务 A 执行了这条范围查询语句注意查询条件值的记录id 为 6并不存在于表中。mysqlbegin;Query OK,0rowsaffected(0.00sec)mysqlselect*fromuserwhereid6forupdate;-----------------|id|name|age|-----------------|1|路飞|19||5|索隆|21|-----------------3rowsinset(0.00sec)在 id 1 这条记录的主键索引上加了范围为 (-∞, 1] 的 next-key 锁意味着其他事务即无法更新或者删除 id 1 的这一条记录同时也无法插入 id 小于 1 的这一些新记录。在 id 5 这条记录的主键索引上加了范围为 (1, 5] 的 next-key 锁意味着其他事务即无法更新或者删除 id 5 的这一条记录同时也无法插入 id 值为 2、3、4 的这一些新记录。在 id 10 这条记录的主键索引上加了范围为 (5, 10) 的间隙锁意味着其他事务无法插入 id 值为 6、7、8、9 的这一些新记录。读者提问请教一个问题 在看mysql加行级锁中 select *from user where id6 for update; 为什么id(5,10)要加间隙锁呢 不加 也不会发生幻读吧 回答第三个间隙锁是加在id10 索引上的这个例子不太好说明如果是id7id10索引不加间隙锁的话中途插入一个 6就发生幻读了实验四针对「小于等于」的范围查询时查询条件值的记录「存在」表中的情况。假设事务 A 执行了这条范围查询语句注意查询条件值的记录id 为 5存在于表中。mysqlbegin;Query OK,0rowsaffected(0.00sec)mysqlselect*fromuserwhereid5forupdate;-----------------|id|name|age|-----------------|1|路飞|19||5|索隆|21|-----------------2rowsinset(0.00sec)在 id 1 这条记录的主键索引上加了范围为 (-∞, 1] 的 next-key 锁。意味着其他事务即无法更新或者删除 id 1 的这一条记录同时也无法插入 id 小于 1 的这一些新记录。在 id 5 这条记录的主键索引上加了范围为 (1, 5] 的 next-key 锁。意味着其他事务即无法更新或者删除 id 5 的这一条记录同时也无法插入 id 值为 2、3、4 的这一些新记录。非唯一索引等值查询当我们用非唯一索引进行等值查询的时候因为存在两个索引一个是主键索引一个是非唯一索引二级索引所以在加锁时同时会对这两个索引都加锁但是对主键索引加锁的时候只有满足查询条件的记录才会对它们的主键索引加锁。先说结论当查询的记录「不存在」时扫描到第一条不符合条件的二级索引记录该二级索引的 next-key 锁会退化成间隙锁。因为不存在满足查询条件的记录所以不会对主键索引加锁。当查询的记录「存在」时由于不是唯一索引所以肯定存在索引值相同的记录于是非唯一索引等值查询的过程是一个扫描的过程直到扫描到第一个不符合条件的二级索引记录就停止扫描然后在扫描的过程中对扫描到的二级索引记录加的是 next-key 锁而对于第一个不符合条件的二级索引记录该二级索引的 next-key 锁会退化成间隙锁。同时在符合查询条件的记录的主键索引上加记录锁。1、记录不存在的情况假设事务 A 对非唯一索引age进行了等值查询且表中不存在 age 25 的记录。mysqlbegin;Query OK,0rowsaffected(0.00sec)mysqlselect*fromuserwhereage25forupdate;Emptyset(0.00sec)2、记录存在的情况假设事务 A 对非唯一索引age进行了等值查询且表中存在 age 22 的记录。mysqlbegin;Query OK,0rowsaffected(0.00sec)mysqlselect*fromuserwhereage22forupdate;-----------------|id|name|age|-----------------|10|山治|22|-----------------1rowinset(0.00sec)非唯一索引范围查询非唯一索引和主键索引的范围查询的加锁也有所不同不同之处在于非唯一索引范围查询索引的 next-key lock 不会有退化为间隙锁和记录锁的情况都是加next-key 锁。就带大家简单分析一下事务 A 的这条范围查询语句mysqlbegin;Query OK,0rowsaffected(0.00sec)mysqlselect*fromuserwhereage22forupdate;--------------------|id|name|age|--------------------|10|山治|22||20|香克斯|39|--------------------2rowsinset(0.01sec)在 age 22 的范围查询中明明查询 age 22 的记录存在并且属于等值查询为什么不会像唯一索引那样将 age 22 记录的二级索引上的 next-key 锁退化为记录锁因为 age 字段是非唯一索引不具有唯一性所以如果只加记录锁记录锁无法防止插入只能防止删除或者修改就会导致其他事务插入一条 age 22 的记录这样前后两次查询的结果集就不相同了出现了幻读现象。读者问题这里的查询条件如果是age 22验证了一下确实都是临键锁不过感觉age39那里加一个间隙锁就可以避免幻读了为什么age39也要加临键锁额外的把39也锁住了。回答这里属于能优化但是 mysql 没有做这方面的优化具体原因未知官方文档也没解释大概率是作者忘记了没有加索引的查询当前读查询语句没有使用索引列作为查询条件或者查询语句没有走索引查询导致扫描是全表扫描。那么每一条记录的索引上都会加 next-key 锁这样就相当于锁住的全表这时如果其他事务对该表进行增、删、改操作的时候都会被阻塞。不只是当前读查询语句不加索引才会导致这种情况update 和 delete 语句如果查询条件不加索引那么由于扫描的方式是全表扫描于是就会对每一条记录的索引上都会加 next-key 锁这样就相当于锁住的全表问题为什么select…for update/ update / delete这些语句的查询语句没有走索引就会对每一条记录的索引上加next-key 锁锁全表呢答案是为了防止在事务执行期间其他事务修改或删除某一条记录或者在其前后插入新记录从而出现了不可重复读和幻读的问题。死锁死锁的发生我建了一张订单表其中 id 字段为主键索引order_no 字段普通索引也就是非唯一索引CREATETABLEt_order(idintNOTNULLAUTO_INCREMENT,order_nointDEFAULTNULL,create_datedatetimeDEFAULTNULL,PRIMARYKEY(id),KEYindex_order(order_no)USINGBTREE)ENGINEInnoDB;然后先t_order表里现在已经有了 6 条记录假设这时有两事务一个事务要插入订单 1007 另外一个事务要插入订单 1008因为需要对订单做幂等性校验所以两个事务先要查询该订单是否存在不存在才插入记录过程如下对于A事务而言因为表中最大记录是1006所以会对 (1006, ∞]加next-key 锁对于B事务而言也是会对 (1006, ∞]加next-key锁两个事务在插入的时候都在等待对方事务的间隙锁释放于是就造成了循环等待导致死锁。如何避免死锁死锁的四个必要条件互斥、占有且等待、不可强占用、循环等待。只要系统发生死锁这些条件必然成立但是只要破坏任意一个条件就死锁就不会成立。在数据库层面有两种策略通过「打破循环等待条件」来解除死锁状态设置事务等待锁的超时时间。当一个事务的等待时间超过该值后就对这个事务进行回滚于是锁就释放了另一个事务就可以继续执行了。在 InnoDB 中参数innodb_lock_wait_timeout是用来设置超时时间的默认值时 50 秒。当发生超时后就出现下面这个提示开启主动死锁检测。主动死锁检测在发现死锁后主动回滚死锁链条中的某一个事务让其他事务得以继续执行。将参数innodb_deadlock_detect设置为 on表示开启这个逻辑默认就开启。当检测到死锁后就会出现下面这个提示上面这个两种策略是「当有死锁发生时」的避免方式。我们可以回归业务的角度来预防死锁对订单做幂等性校验的目的是为了保证不会出现重复的订单那我们可以直接将 order_no 字段设置为唯一索引列利用它的唯一性来保证订单表不会出现重复的订单不过有一点不好的地方就是在我们插入一个已经存在的订单记录时就会抛出异常。如果我的内容对你有帮助请辛苦动动您的手指为我点赞评论收藏。感谢大家!!我的博客即将同步至腾讯云开发者社区邀请大家一同入驻https://cloud.tencent.com/developer/support-plan?invite_codeh70g0sv71wz