当前位置: 首页 > 科技观察

我敢打赌!大多数人无法回答这个SQL问题

时间:2023-03-16 11:31:36 科技观察

大家好,我是小林。周末,一位读者问了我一个关于MySQL更新锁的有趣问题。当他用下面的数据库表做MySQL实验的时候。发现事务B的更新不会被阻塞,但是事务C的更新会被阻塞。都是更新id=10的记录,为什么一个被屏蔽,一个不被屏蔽?首先我们分析一下事务A的SQL加了哪些锁。//事务Aselect*fromt_personwhereid<10forupdate;我直接断定事务A加了这三个行级锁:在id为1的主键索引上,加了一个X型next-key锁,范围是(-∞,1)。也就是说其他事务不能删除和更新id=1的记录,不能插入id小于1的新记录。在id为5的主键索引上,添加X类型next-key锁范围为(1,5)。表示其他事务不能删除和更新id=5的记录,也不能插入新的id为2、3、4的记录。在id为10的主键索引上,加了一个X型间隙锁,范围为(5,10).表示其他事务不能插入id为6,7,8,9的新记录.PS:如果你不知道什么是MySQL的这些行级锁(recordlocks,gaplocks,next-keylocks),行级锁的加锁规则不清楚,强烈推荐看我之前写的这篇文章:MySQL是如何加行级锁的?看完之后,一看就会有感觉b回复我的文章。为什么事务B的update语句没有阻塞?事务B的update语句是更新id=10的行记录的name字段。//TransactionBupdatet_personsetname="Xiaolin"whereid=10;事务B会在id=10的主键索引上加一个X型记录锁,只锁这一行。因为当我们使用唯一索引进行等价查询时,查询记录是“存在的”,在索引树上定位到这条记录后,该记录的索引中的next-key锁就会退化为“记录锁”。事务A没有在id=10的主键索引上加X型记录锁,而是在id=10的主键索引上加X型间隙锁,间隙锁和记录之间没有互斥关系锁,所以事务B的更新语句不会阻塞。为什么事务C的update语句会阻塞?事务C的update语句是将id=10的行记录的id更新为2。//TransactionCupdatet_personsetid=2whereid=10;这次更新很特别,特别之处在于更新了主键索引。你以为它只是一个更新操作,但实际上它在幕后执行了两个操作:操作??一:deletefromt_personwhereid=10;操作2:插入t_person(2,Chen,30,广州市海珠区);即先删除id=10的记录,再插入一条id=2的新记录。为什么update语句更新索引值的时候,会拆分成delete和insert操作呢?要回答这个问题,首先要了解B+树的特点。Innodb(MySQL存储引擎)实现索引时,使用的数据结构是B+树。B+树是在二叉查找树的基础上演化而来的,所以B+树在存储索引的时候,是按照顺序存储的,因为这样使用二分查找就可以快速的检索到索引。现在有这样一棵B+树,可以看到叶子节点的索引值都是升序排列的。假设此时需要将索引值从25更新为3,如果在索引值为25的位置直接将值改为3,这时候你会发现这棵B+树不满足顺序!因此,更新一个索引的值,不能只修改一个索引值,还要保证更新后的索引值能够继续满足B+树的顺序。解决办法是先删除索引值为25的节点,再插入索引值为3的节点,这样这棵B+树就可以满足顺序了。事务C的更新语句阻塞了哪个“操作”?现在我们知道在事务C的update特殊语句后面执行了两个操作,即删除和插入操作。哪个“操作”被阻止了?“操作1”是删除id=10的记录,事务C会在id=10的主键索引上加X型记录锁,事务A不会在主键索引上加X型记录锁id=10,and就是给id=10的主键索引加一个X型的间隙锁。间隙锁和记录锁之间没有互斥关系,所以“操作1”不会阻塞。根据排除法,由于“操作1”不会阻塞,事务C的update语句之所以被阻塞,是因为“操作2”被阻塞了。为什么“操作2”会阻塞?我们首先需要知道insert操作什么时候会被阻塞:在插入一条新记录之前,insert语句需要定位这条记录在B+树中的位置。如果在插入位置的下一条记录的索引上有间隙锁,这时会产生一个插入意向锁,锁的状态被设置为等待状态。现象是insert语句会被阻塞。“操作2”插入一条id=2的新记录,定位插入位置在主键索引的B+树中如下图所示。插入位置的下一条记录是id=5的记录,事务A在id为5的主键索引上加了X型next-key锁,其中包含间隙锁。因此,“操作2”的插入操作会被阻塞,这就是事务C的更新语句被阻塞的原因。由此我们也可以知道,间隙锁的作用是防止其他事务在间隙锁范围内插入新的记录,从而避免可重复读隔离级别下的幻读现象。我们还可以使用select*fromperformance_schema.data_locks\G;语句来查看事务C添加了哪些锁导致阻塞。从上面的输出信息可以看出,事务C在添加“插入意向锁”时被阻塞了。插入意向锁是只针对插入操作的锁,事务C只执行了更新语句,但是插入意向锁出现了。由此也可以证明,事务C的特殊更新语句在运行时,被拆分成了两个操作,一个是delete,一个是insert。简而言之,如果update语句更新了一个普通字段的值,就会在更新的记录上加一个X型的记录锁。但是,如果update语句更新了索引的值,那么在执行过程中会被拆分为delete和insert操作。这时候在分析锁的时候,就要从这两个操作的角度来分析。结束了!怎么样,够详细了吧?