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

MySQL行锁、表锁、间隙锁一文详解

时间:2023-03-14 19:20:13 科技观察

前言我们在之前的文章中讲过什么是索引,如何使用explain来分析索引的使用情况,如何优化索引,并显示配置文件以分析SQL语句执行研究的资源消耗。今天我们就来说说MySQL的各种锁。这里我们使用InnoDB准备存储引擎创建表tb_innodb_lockdroptableifexiststest_innodb_lock;CREATETABLEtest_innodb_lock(aINT(11),bVARCHAR(20))ENGINEINNODBDEFAULTcharset=utf8;insertintotest_innodb_lockvalues(1,'a');insertintotest_innodb_lockvalues(2,'b');insertintotest_innodb_lockvalues(3,'c');insertintotest_innodb_lockvalues(4,'d');insertintotest_innodb_lockvalues(5,'e');createindexcreateindexidx_lock_aontest_innodb_lock(a);createindexidx_lock_bontest_innodb_lock(b);MySQL各种锁演示先改autocommit事务手动提交:setautocommit=0;我们启动两个会话窗口A和B,模拟一个抢到锁,一个被阻塞。行锁(write&read)一个窗口执行updatetest_innodb_locksetb='a1'wherea=1;SELECT*fromtest_innodb_lock;我们可以看到A窗口可以看到更新后的结果B窗口执行SELECT*fromtest_innodb_lock;我们可以看到B窗口不能看到更新后的结果,看到的还是旧数据。这是因为a=1行被窗口A执行的SQL语句锁定了,没有执行commit操作。所以B窗口看到的还是旧数据。这在MySQL隔离级别中是“已提交读”。窗口A执行提交操作COMMIT;窗口B查询SELECT*fromtest_innodb_lock;这时,我们发现B窗口读取了最新的数据并锁定了行锁(write&write)WindowA执行updatea=1recordupdatetest_innodb_locksetb='a2'wherea=1;此时没有commit,锁被窗口A持有,窗口B也执行updatea=1recordupdatetest_innodb_locksetb='a3'wherea=1;可以看出,窗口B已经被阻塞了,因为窗口A还没有执行commit,还持有锁。窗口B抢不到a=1行的锁,一直阻塞等待。窗口A执行提交操作COMMIT;B窗口的变化说明此时B窗口已经成功执行了表锁。当索引失效时,行锁会升级为表锁。使索引无效的方法之一是自动或手动更改索引类型。a字段本身是一个整数。如果我们加上引号,它就变成了一个字符串。这时候索引就会失效。窗口A更新记录a=1updatetest_innodb_locksetb='a4'wherea=1ora=2;窗口B更新记录a=2updatetest_innodb_locksetb='b1'wherea=3;这时发现虽然窗口A和B更新的行不同,但是窗口B还是阻塞的,因为窗口A的索引无效,导致行锁升级为表锁,锁住了整个表,索引窗口B被阻塞。窗口A执行提交操作COMMIT;窗口B的变化说明此时窗口B已经成功执行了间隙锁。什么是间隙锁?当我们使用范围条件查询数据时,InnoDB会锁定这个范围内的数据。比如有4条id为1、3、5、7的数据,我们查找1-7范围内的数据。然后1-7将被锁定。2、4、6也在1-7的范围内,但是这些数据记录并不存在,这2、4、6称为间隙。间隙锁的危害在查找范围时,整个范围内的所有数据都会被锁定。甚至一些不存在于这个范围内的数据也会被无辜地锁定。比如我想把2插入到1,3,5,7中,这时候1-7都被锁住了,2根本插不进去。在某些场景下,会对性能产生很大的影响Gaplockdemonstration我们先修改字段a的值为1,3,5,7,9WindowA更新a=1~7范围内的数据updatetest_innodb_locksetb='b5'where>1and<7;窗口B在a=2处插入数据insertintotest_innodb_lockvalues(2,"b6");这时发现窗口B中更新a=2的操作一直在等待,因为1~7范围内的数据被gap锁定了,locked。只有在窗口A执行commit后,窗口B的a=2才能更新成功。行锁分析执行SQL分析命令showstatuslike'innodb_row_lock%';variable_name说明Innodb_row_lock_current_waits:当前等待的锁数。Innodb_row_lock_time:从系统启动到现在锁定的时间长度。Innodb_row_lock_time_avg:平均每次等待锁的时间。Innodb_row_lock_time_max:从系统启动到现在等待锁的最长时间。Innodb_row_lock_waits:系统自启动以来等待锁的总次数。结论可以考虑是否根据Variable_name的参数进行优化。如果加锁时间和加锁次数太大,那么就要考虑优化了。优化方法可以参考之前关于索引优化的文章。