1.  基础知识回顾

1、索引的有序性,索引本身就是有序的

2、InnoDB中间隙锁的唯一目的是防止其他事务插入间隙。间隙锁可以共存。一个事务取得的间隙锁并不会阻止另一个事务取得同一间隙上的间隙锁。共享和独占间隔锁之间没有区别。它们彼此之间不冲突,并且执行相同的功能。

3、MySQL默认隔离级别是 REPEATABLE-READ

4、加锁的对象是索引,加锁的基本单位是next-key锁,而行锁和间隙锁,是由next-key锁退化而来的

5、记录锁,锁的是索引,而非数据本身

6、间隙锁是开区间,next-key锁是前开后闭区间

7、意向锁是表级锁,它相当于一个标志,可以用来提高加锁的效率

8、间隙锁的目的是为了防止幻读,在“读已提交”隔离级别下允许幻读,所以如果隔离级别是“读已提交”,就不会用到间隙锁,更不会用到next-key锁。因此,只有“可重复读”及以上隔离级别下,才会有next-key锁

9、InnoDB中锁住的是索引。对辅助索引加锁时,辅助索引所对应的主键索引也会被锁住。

10、所谓“间隙”本质是又间隙右边的那条记录决定的

接下来,具体看一下走不同的索引时的加锁情况。本例中使用的MySQL版本为8.0.30

SELECT VERSION();
SHOW VARIABLES LIKE 'transaction_isolation';
SHOW VARIABLES LIKE 'innodb_lock_wait_timeout';

测试表结构及数据如下:

2.  案例分析

LOCK_MODE不同值的含义:

  • X :代表next-key锁
  • X,GAP :代表间隙锁
  • X,REC_NOT_GAP :代表记录锁

2.1.  主键索引

情况一:等值查询,存在

Session A Session B
BEGIN;
SELECT * FROM t_user WHERE id = 10 FOR UPDATE;
 
 

INSERT INTO t_user (id, `name`, id_card_no, birthday, score) VALUES (9, '于禁', '1012', '2023-11-01', 1);

Affected rows: 1

首先对表加意向排它锁,然后对主键加记录锁,可以看到只锁住了id=10这个主键索引

情况二:等值查询,不存在

Session A Session B Session C
BEGIN;
SELECT * FROM t_user WHERE id = 5 FOR UPDATE;
   
 

INSERT INTO t_user (id, `name`, id_card_no, birthday, score) VALUES (6, '于禁', '1012', '2023-11-01', 1);

1205 - Lock wait timeout exceeded; try restarting transaction

 
   

UPDATE t_user SET score = score + 1 WHERE id = 10;

Affected rows: 1

加锁范围: (-∞, 10)

注意,是开区间,10并没有被锁

情况三:范围查找

Session A Session B Session C
BEGIN;
SELECT * FROM t_user WHERE id >= 10 AND id < 11 FOR UPDATE;
   
 

INSERT INTO t_user (id,`name`,id_card_no,birthday,score) VALUES (9,'典韦','1011','2022-12-19',1)

Affected rows: 1

 
   

INSERT INTO t_user (id,`name`,id_card_no,birthday,score) VALUES (18,'徐晃','1018','2022-12-09',1);

1205 - Lock wait timeout exceeded; try restarting transaction

一个记录锁10,加一个间隙锁(10, 20),合起来就是[10, 20)

锁定区间:[10, 20)

Session A Session B Session C
BEGIN;
SELECT * FROM t_user WHERE id >= 10 AND id <= 20 FOR UPDATE;
   
 

UPDATE t_user SET score = score + 1 WHERE id = 20;

1205 - Lock wait timeout exceeded; try restarting transaction

 
   

INSERT INTO t_user (id,`name`,id_card_no,birthday,score) VALUES (21,'张辽','1021','2022-12-09',1);

Affected rows: 1

id=10上加了记录锁,id=20上加了next-key锁

next-key锁是前开后闭区间,所以,最终锁定区间为:[10,20]

如果这里不是id>=10,而是id>10的话,最终只会在id=20上加next-key锁,这种情况下锁定区间为:(10,20]

2.2.  唯一索引(非主键)

情况一:等值查询,存在

Session A Session B
BEGIN;
SELECT * FROM t_user WHERE id_card_no = '1003' FOR UPDATE;
 
 

UPDATE t_user SET score = score + 1 WHERE id = 30;

1205 - Lock wait timeout exceeded; try restarting transaction

辅助索引 ('1003',30)加记录锁,同时,主键索引上id=30加记录锁

情况二:等值查询,不存在

先看一眼现在的数据

Session A Session B Session C
BEGIN;
SELECT * FROM t_user WHERE id_card_no = '1042' FOR UPDATE;
   
 

INSERT INTO t_user (id,`name`,id_card_no,birthday,score) VALUES (52,'许褚','1041','2023-01-01',1);

1205 - Lock wait timeout exceeded; try restarting transaction

 
   

UPDATE t_user SET score = score + 1 WHERE id_card_no = '1041';

Affected rows: 0

只在辅助索引idx_card上加了间隙锁,锁定范围是:('1040', '1050')

索引是有序的,尽管索引字段类型是字符串类型,仍然是有序的

因为是间隙锁,所以没有锁定1050,也就自然不会给id=50加记录锁

值得注意的是,在('1040', '1050')这个区间内插入是不行的,但是更新是可以的

情况三:范围查找

Session A Session B Session C
BEGIN;
SELECT * FROM t_user WHERE id_card_no <= '1024' FOR UPDATE;
   
 

INSERT INTO t_user (id,`name`,id_card_no,birthday,score) VALUES (11,'潘凤','1011','2023-01-01',1);

1205 - Lock wait timeout exceeded; try restarting transaction

 
   

INSERT INTO t_user (id,`name`,id_card_no,birthday,score) VALUES (11,'潘凤','1031','2023-01-01',1);

Affected rows: 1

主键索引上id=10和id=20都加了记录锁

辅助索引idx_card上加了Next-key锁,锁定范围为:(-∞, '1010']、('1010', '1020']、('1020', '1030']

2.3.  非唯一索引(普通索引)

情况一:等值查询,存在

Session A Session B Session C
BEGIN;
SELECT * FROM t_user WHERE birthday = '2023-12-01' FOR UPDATE;
   
 

UPDATE t_user SET score = score + 1 WHERE birthday = '2023-12-11';

Affected rows: 0

 
   

UPDATE t_user SET score = score + 1 WHERE birthday = '2023-12-09';

Affected rows: 0

主键索引id=10加记录锁

辅助索引idx_birthday上,'2023-12-01'上加Next-key锁,'2023-12-12'上加间隙锁

加锁区间:(-∞, 2023-12-01]、(2023-12-01, 2023-12-12)、id=10

因为是非唯一索引,所以当找到第一条birthday = '2023-12-01'的记录时,不确定后面还有没有这样的记录,所以必须继续往后找,直到遇到一条不是2023-12-01的记录未止。

间隙锁阻止其它事务插入,但是不阻止更新

情况二:范围查找

Session A Session B Session C
BEGIN;
SELECT * FROM t_user WHERE birthday >= '2023-11-11' AND birthday <='2023-11-28' FOR UPDATE;
   
 

UPDATE t_user SET score = score + 1 WHERE birthday = '2023-11-29';

Affected rows: 0

 
   

INSERT INTO t_user (id,`name`,id_card_no,birthday,score) VALUES (13,'华雄','1033','2023-11-29',1);

1205 - Lock wait timeout exceeded; try restarting transaction

主键索引上加锁范围:id=30和id=40

辅助索引idx_birthday上加锁范围:(2023-01-01, 2023-11-12]、(2023-11-12, 2023-11-28]、(2023-11-28, 2023-11-30]

2.4.  不走索引

Session A Session B Session C
BEGIN;
SELECT * FROM t_user WHERE score = 2 FOR UPDATE;
   
 

UPDATE t_user SET score = score + 1 WHERE id = 33;

Affected rows: 0

 
   

INSERT INTO t_user (id,`name`,id_card_no,birthday,score) VALUES (33,'颜良','1038','2023-12-20',1);

Lock wait timeout exceeded; try restarting transaction

在所有记录的主键上加next-key锁

加锁范围:(-∞, 10]、(10, 20]、(20, 30]、(30, 40]、(40, 50]、(50, +∞)

3.  总结

1、主键索引

  • 等值查询,命中,则被命中的主键索引加记录锁
  • 等值查询,未命中,则继续向后(向右)查找,直到找到第一个不满足的记录,对该记录加间隙锁,即锁住该记录之前的间隙,以防止其它事务向其中插入数据
  • 范围查找,找到的(满足条件的)记录的主键加记录锁,扫描过的区间加间隙锁

2、非主键唯一索引

  • 与主键索引类似,唯一的区别是锁住辅助索引记录的同时会锁住对应的主键索引

3、非唯一索引

  • 向右查找直到遇到一条不满足条件的记录,然后对扫描到的区间加间隙锁,对扫描到的辅助索引记录加记录锁,同时对与其对应的主键加记录锁

4、不走索引

  • 表中所有记录的主键加next-key锁

总结几个规律:

  1. 命中的索引记录会加记录锁,如果它是一个辅助索引,则对应的主键索引也会被加上记录锁
  2. 没有命中的记录不会被加记录锁
  3. 非唯一索引上查找时,当找到第一条满足条件的索引记录时,还会继续向右查找,直到遇到一条不满足条件的记录(PS:幸亏索引是有序的,不然找到累死)
  4. 当一条SQL没有走索引时,那么将会在每一条聚集索引上加X锁,这个类似于表锁,但原理上和表锁是完全不同的

建议:

  1. 尽量控制事务大小,减少锁定资源量和时间长度
  2. 即便在条件中使用了索引字段,但是否使用索引来检索数据是由 MySQL 通过判断不同执行计划的代价来决定的。如果 MySQL 认为全表扫描效率更高,它就不会使用索引。因此,在分析锁冲突时,可以查看执行计划(explain)以确认是否真正使用了索引

最后,重要的事情说三遍:

  • 加锁的单位是next-key锁
  • 加锁的单位是next-key锁
  • 加锁的单位是next-key锁

参考

https://www.cnblogs.com/harda/p/16820592.html

https://blog.csdn.net/qq_42604176/article/details/115431744

https://zhuanlan.zhihu.com/p/378306056

https://cloud.tencent.com/developer/article/1971381

https://cloud.tencent.com/developer/article/1844928

MySQL InnoDB加锁规则分析的更多相关文章

  1. 何登成大神对Innodb加锁的分析

    背景 MySQL/InnoDB的加锁分析,一直是一个比较困难的话题.我在工作过程中,经常会有同事咨询这方面的问题.同时,微博上也经常会收到MySQL锁相关的私信,让我帮助解决一些死锁的问题.本文,准备 ...

  2. 聊聊MySQL的加锁规则《死磕MySQL系列 十五》

    大家好,我是咔咔 不期速成,日拱一卒 本期来聊聊MySQL的加锁规则,知道这些规则后可以判断SQL语句的加锁范围,同时也可以写出更好的SQL语句,防止幻读问题的产生,在能力范围内最大程度的提升MySQ ...

  3. mysql InnoDB加锁分析

    文章转载自:http://www.fanyilun.me/2017/04/20/MySQL%E5%8A%A0%E9%94%81%E5%88%86%E6%9E%90/ 以下实验数据基于MySQL 5.7 ...

  4. MySQL InnoDB MVCC深度分析

    关于MySQL的InnoDB的MVCC原理,很多朋友都能说个大概: 每行记录都含有两个隐藏列,分别是记录的创建时间与删除时间 每次开启事务都会产生一个全局自增ID 在RR隔离级别下 INSERT -& ...

  5. 你了解MySQL的加锁规则吗?

    注:加锁规则指的是next-key lock,如果还不了解next-key lock,请阅读上一篇博客 加锁规则可以概括为:两个原则.两个优化和一个bug: 原则1:加锁的基本单位是next-key ...

  6. MySQL Lock--INSERT加锁规则

    Insert操作加锁规则 1.INSERT操作会对新插入的记录加行锁(ROW LOCK)+排他锁(X LOCK),不会产生任何GAP锁和Next-Key锁 2.在插入记录前,会向插入记录所在位置申请意 ...

  7. MySQL Lock--MySQL加锁规则

    ===================================================================== 淘宝林晓斌总结 在可重复读事务隔离级别下,加锁规则如下: 原 ...

  8. MySQL InnoDB加锁超时回滚机制(转)

    add by zhj: 看来我对MySQL的理解还有待深入,水还是挺深的啊,MySQL给记录加锁时,可以通过innodb_lock_wait_timeout参数设置超时时间, 如果加锁等待超过这个时间 ...

  9. MySQL 加锁处理分析

    1    背景    1 1.1    MVCC:Snapshot Read vs Current Read    2 1.2    Cluster Index:聚簇索引    3 1.3    2P ...

  10. MySQL 加锁处理分析 转

    MySQL 加锁处理分析  转 http://hedengcheng.com/?p=771 十二 13th, 2013 发表评论 | Trackback   1    背景    1 1.1    M ...

随机推荐

  1. Go开始:Go基本元素介绍

    本文深入探讨了Go编程语言中的核心概念,包括标识符.关键字.具名函数.具名值.定义类型.类型别名.包和模块管理,以及代码块和断行.这些元素是构成Go程序的基础,也是编写高质量代码的关键. 关注Tech ...

  2. 发布策略:蓝绿部署、金丝雀发布(灰度发布)、AB测试、滚动发布、红黑部署的概念与区别

    蓝绿发布(Blue-Green Deployment) 蓝绿发布提供了一种零宕机的部署方式.不停老版本,部署新版本进行测试,确认OK,将流量切到新版本,然后老版本同时也升级到新版本.始终有两个版本同时 ...

  3. Microsoft Build 2021第二天

    C++20 Ranges are complete in Visual Studio 2019 version 16.10 https://devblogs.microsoft.com/cppblog ...

  4. linux查看IP、域名、端口的网络是否相通

    linux查看IP.域名.端口的网络是否相通 1. ping # 检索当前域名对应的IP地址 ping 域名 # 查看IP是否相通 ping IP 2. tlenet # 查看指定IP的端口是否相通, ...

  5. IPv6的基本认识

    IPv6 1.IPv6的基本认识 IPv4 位数是 32位,4字节,能够提供的IP地址大约是42亿,但你知道的,如今一个人都不止一个IP地址,看看如今设备的数量及发展速度就知道,所以有了IPv6,IP ...

  6. AIGC革新,将文字或者LOGO融入AI视频基于PIKA-labs(Python3.10)

    很多平台都会禁止用户使用带有网址或者二维码的头像以及文章配图,这样可以有效的防止用户的一些"导流"行为.当然,头像.文章或者视频现在都是AI来审,毕竟现在人工的成本实在太高,但是如 ...

  7. 记一次 .NET某账本软件 非托管泄露分析

    一:背景 1. 讲故事 中秋国庆长假结束,哈哈,在老家拍了很多的短视频,有兴趣的可以上B站观看:https://space.bilibili.com/409524162 ,今天继续给大家分享各种奇奇怪 ...

  8. Linux系列教程——Linux文件编辑、Linux用户管理

    @ 目录 1 Linux基本权限 1.权限基本概述 1.什么是权限? 2.为什么要有权限? 3.权限与用户之间的关系? 4.权限中的rwx分别代表什么含义? 2.权限设置示例 1.为什么要设定权限,我 ...

  9. 软件开发人员 Kubernetes 入门指南|Part 2

    在第 1 部分中,我们讲解了 Kubernetes 的核心组件,Kubernetes 是一种开源容器编排器,用于在分布式环境中部署和扩展应用程序:我们还讲解了如何在集群中部署一个简单的应用程序,然后更 ...

  10. QT Recursive repaint detected 检测到递归重绘

    1.打印绘图时的线程号,如果与主线程号不一致,则需要使用信号传递数据,在主线程窗体中绘图 如下: qDebug() << "当前线程:" <<QThread ...