MySQL复习之锁

出于对数据并发访问的控制,MySQL引入众多锁的概念。

锁的粒度

按作用的数据范围分类,有表级锁,行级锁。

  • 表级锁:作用在整张表上,锁的粒度比较大。
  • 行级锁:作用在数据行上,锁的粒度比较小。

再大点的粒度还有全局锁,如 FTWRL(flush table with read lock)禁止全局更新,或者把整个实例设置为read only。

锁的分类

按照锁的兼容性,是否能共存,MySQL又分为共享锁和排它锁。

  • 共享锁:简称S锁。S锁可以同时被多个事务持有,最简单的,我们可以用 select ... lock in share mode (8.0支持 select ... for share) 加上S锁。

  • 排它锁:简称X锁。X锁不能同时被多个事务持有,X锁加锁方式有2种:一是自动加锁,在对数据进行增删改的时候由系统自动加上;二是手动加锁,如 select ... for update

另外还有个意向锁,意向锁是由数据库自己维护的,一般来说,当我们给一行数据加上共享锁之前,数据库会自动在这张表上加上意向共享锁(IS锁);当我们给一行数据加上排它锁之前,数据库会自动在这张表上加上意向排它锁(IX锁)。

意向锁可以认为是S锁和X锁在数据表上的标志,通过意向锁可以快速判断表中是否有记录被上锁,从而避免通过遍历的方式来查看表中是否有记录被上锁,提升加锁效率。例如,我们要加表级别的X锁,这时候数据表里如果存在行级别的X锁或者S锁,加锁就会失败,此时直接根据 意向锁 就能知道这张表是否有行级别的锁。

简而言之呢,意向锁纯内部使用,在分析锁冲突的时候可不用看它。

表级锁

  • S锁。

    lock table t4 read;

38-01.png

给表加上只读锁之后,其它会话不能对这个表update:

38-02.png

可以从 performance_schema.metadata_locks 观察到 MDL 锁的情况:

38-03.png

图中显示,48那个线程即执行update语句的线程,在申请表t4上的意向排它锁时被阻塞了,处于PENDING状态,所以update语句将一直被阻塞直到前面那个会话执行 unlock tables 释放表锁。

  • X锁。

    lock table t4 write;

    表级别的排它锁,一但有会话执行这个操作,那么其它会话执行简单的select都会被阻塞。

    在一会话执行上述语句后,MDL 锁状态为:

38-04.png

其他会话执行简单select:

38-05.png

观察此时的MDL锁状态:

38-06.png

发现,普通select也会尝试对表加共享读锁(图中48线程),显然处于PENDING被阻塞了。

所以,这个语句锁定范围大,连普通select都阻塞,一般只在备份恢复的时候用,常见于 mysqldump 生成的文件。

  • 自增锁。

    系统针对 AUTO_INCREMENT 列,就是用自增锁保证递增的。关于自增锁的行为,有个参数:innodb_autoinc_lock_mode。主要是锁要不要等到insert语句执行完成后再释放锁的区别,具体可以参考文档

    只要不去更新主键值(如:update t set id=x where id=y),就不会出什么意外情况。这也是共识,应该不会有人去写这种奇怪的逻辑吧?

InnoDB的行级锁

先复习下MySQL读的类型,有快照读,当前读,半一致性读。

  • 快照读:普通select,以及事务里面的select。事务有一个ReadView的概念,RR隔离级别借此实现MVCC,创建ReadView快照的时机是事务里第一条select语句执行的时候,除非显示使用 start transaction with consistent read
  • 当前读:始终读取当前最新的数据,select ... for share/update 。另外事务里面的update语句也会采用当前读。
  • 半一致性读:这个只存在于RC隔离级别,并且是update语句。

行锁的一个大前提是:Innodb引擎,并且涉及的列有索引。

InnoDB 的行锁,是通过锁住索引来实现的,如果加锁查询的时候没有使用索引,会将整个聚簇索引都锁住(主键上全部加临键锁Next-Key Lock),相当于锁表。

根据锁定范围不同,行锁又分为:记录锁(Record Lock),间隙锁(Gap Lock),临键锁(Next-Key Lock)。

  • 记录锁。

    基于唯一索引上的等值或者范围查询,会加记录锁。如果是辅助索引,除了辅助索引会加锁,相应主键索引也会加锁。

    C1是表t4上的唯一索引:

38-07.png

此时,索引c1上应该有7把记录锁(会向右扫描到第一个不符合条件的记录,并且加上记录锁),相应主键上有6把锁。

38-08.png

注意c1=11这行记录,只有c1索引加锁了,没有去主键上面加锁,所以:

38-09.png
  • 间隙锁。

    间隙锁指的是两个记录之间逻辑上尚未填入数据的部分,主要是为了解决幻读而引入。普通索引等值查询,唯一索引范围查询都会加GAP锁。就拿上述例子来看,c1索引上的(2,5),(7,11)其实是被加上了间隙锁的,验证:

38-10.png

c1=8 在 (7,11) 的间隙,所以插入会被阻塞,查看此时的行锁情况:

38-11.png

显示,c1索引上的X,GAP,INSERT_INTENTION处于PENDING状态,对应记录值是11,明显GAP锁冲突。

  • 临键锁。

    即Next-Key Lock,Lock ORDINARY,指的是间隙加上它右边的记录组成的左开右闭区间。

    MySQL默认的行锁类型就是临键锁,唯一索引上的等值查询并且命中记录则退化成记录锁,唯一索引上的等值查询未命中记录则退化成间隙锁。

38-12.png

c1=21 未匹配到任何记录,临键锁退化成间隙锁:

38-13.png

(20,+) 20到正无穷的区间被锁住,此时插入c1=22的记录将被阻塞:

38-14.png

行锁试验

无索引

t4表上c2列没有索引,

mysql> show create table t4;
+-------+--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+
| Table | Create Table                                                                                                                                                                                                                                     |
+-------+--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+
| t4    | CREATE TABLE `t4` (
  `id` int NOT NULL AUTO_INCREMENT,
  `c1` int DEFAULT NULL,
  `c2` int DEFAULT '0',
  PRIMARY KEY (`id`),
  UNIQUE KEY `idx_c1` (`c1`)
) ENGINE=InnoDB AUTO_INCREMENT=23 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci |
+-------+--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+
1 row in set (0.01 sec)

mysql>
38-15.png

此时观察行锁情况:

38-16.png

发现主键索引上全部被加上了临键锁,还有个 supremum pseudo-record 虚拟最大值,意味着整个表现在不可以插入任何数据,效率极低。

38-17.png
38-18.png

GAP锁冲突,验证了主键索引上加的是临键锁,即Next-Key lock,每个都是左开右闭区间。

包括 select * from t4 where id=21 for update;update t4 set c1=c1+1 where id=21; 这种都会被阻塞,不过这时候是记录锁冲突,不是GAP锁冲突了:

38-19.png

顺带说一下,DDL语句会加表级别的IX锁,所以此时 alter table 等DDL语句也会被阻塞。

最开始那个查询语句,除了加行锁,还有MDL锁,先看一眼:

38-20.png
38-21.png
38-22.png

可见MDL锁冲突了,t4表级别排它锁需要等待。

普通索引
38-23.png
38-24.png

可见,c2=20 被加上了临键锁,主键20被加上记录锁,另外(20,21)是有一个GAP锁,因为最后一个记录的c2值是21,类型是int,所以这个GAP没什么意义。此时插入c2=15的值被阻塞,因为(11,20)有个GAP锁:

38-25.png
38-26.png

此时若插入c2=21的值,就没有问题,因为c2=21上没有记录锁。

这是命中记录的情况,未命中呢?

38-27.png
38-28.png

仅在c2=20上加了GAP锁,此时插入[11,19]的c2值将被阻塞,锁冲突情况如图:

38-29.png

按照左开右闭的原则,c2=11应该不会被阻塞,但是这里实际上是会被阻塞的,因为GAP锁会阻塞意向插入锁,即图中INSERT_INTENSTION,但是意向插入锁不会阻塞GAP锁,这个可能难以理解,举例看看:

update t4 set c2=15 where id=20;
//id=20上有记录锁,同时c2上会有隐式的插入意向锁
select * from t4 where c2=16 for update;
//只有c2上面GAP锁

因为 c2=15和c2=16的记录都不存在,c2有(11,20)的区间,所以,上面如果先执行update语句,后执行select...for update,则不会被阻塞,因为插入意向锁不会阻塞GAP锁;相反,先执行select...for update,后执行update,就会被阻塞,因为GAP锁会阻塞插入意向锁。

范围查询呢?

38-30.png
38-31.png

这里c2索引上c2=7的那行显示加上了临键锁,这据说是个bug,实际这里应该只是个GAP锁,因为此时insert c2=7的值是可以成功的,insert c2=6的值会被阻塞。

唯一索引

等值查询,匹配到记录的时候加的是索引上的记录锁和主键上的记录锁,X,REC_NOT_GAP;未匹配到记录的时候,加的是索引上的下一个记录的GAP锁,X,GAP,如果没有下一条记录,则加在supremum pseudo-record虚拟列上的临键锁,其实作用是一样的。

范围查询,将符合条件的索引列加上临键锁,X,相应的回溯到主键索引上加记录锁,X,REC_NOT_GAP,同时向右扫描到第一个不满足的记录,并且加上临键锁(但是不回溯到主键加锁)。

总之,间隙锁和临键锁都是用来解决幻读问题的,在RC隔离级别下,两者都会失效。

其他锁

你可能还听说过许多其它锁,自旋锁 用于innodb内部cpu的资源分配;备份锁,8.0引入的,lock instance for backup ,会阻塞ddl操作,不会影响普通update。

总结

最重要的表锁和行锁,MDL锁及行锁,要会用各种手段和工具去分析现象。MDL锁可以从表 perfomance_schema.medadata_locks 查看,行锁可以从 performance_schema.data_locks 查看,其他情况可以从 show engine innodb status 查看或者其他方式。

©著作权归作者所有,转载或内容合作请联系作者
平台声明:文章内容(如有图片或视频亦包括在内)由作者上传并发布,文章内容仅代表作者本人观点,简书系信息发布平台,仅提供信息存储服务。

推荐阅读更多精彩内容

  • 1. mysql锁知多少 我们进行insert,update,delete,select会加锁吗,如果加锁,加锁步...
    liwsh阅读 5,064评论 0 4
  • 系列MySQL实战45讲阅读笔记-MySQL入门MySQL实战45讲阅读笔记-日志MySQL实战45讲阅读笔记-锁...
    Mhhhhhhy阅读 974评论 0 3
  • MySQL(事务&锁) 引擎 InnoDB 索引默认聚簇索引主键索引-叶子节点包含整行数据、回滚指针、更新版本号、...
    Yves_Chen阅读 285评论 0 0
  • 执行流程 查询缓存 解析器生成解析树 预处理再次生成解析树 查询优化器 查询执行计划 查询执行引擎 查询数据返回结...
    BigJeffWang阅读 487评论 0 3
  • 1. 锁类型 锁是数据库区别与文件系统的一个关键特性,锁机制用于管理对共享资源的并发访问。InnoDB使用的锁类型...
    butterfly100阅读 1,193评论 0 2