首页 > 数据库 >MySQL InnoDB加锁规则分析

MySQL InnoDB加锁规则分析

时间:2023-12-20 19:33:46浏览次数:50  
标签:加锁 score 2023 索引 Session InnoDB user MySQL id

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

标签:加锁,score,2023,索引,Session,InnoDB,user,MySQL,id
From: https://www.cnblogs.com/cjsblog/p/17914390.html

相关文章

  • 如何解决MySQL Workbench中的错误Error Code: 1175
    错误描述:在MySQLWorkbench8.0中练习SQL语句时,执行一条update语句,总是提示如下错误:ErrorCode:1175.YouareusingsafeupdatemodeandyoutriedtoupdateatablewithoutaWHEREthatusesaKEYcolumnTodisablesafemode,toggletheoptioninPreferences->SQ......
  • Mysql以及TCP socket的C++代码
    在使用socket编写tcp的C++程序时,遇到了一个问题:那就bind冲突了,分析原因:是因为std中有bind函数,而socket中也有,但是没有报重复定义的错误,这就有一点难办了。百度了一下:发现只要使用::bind就可以调用socket的bind。下面把这个套接字socket的server端代码贴出来:staticvoid*serv......
  • mysql新增字典
    INSERTINTO`xxx`.`sys_dict`(`type`,`description`,`create_time`,`update_time`,`remarks`,`system`,`del_flag`,`tenant_id`)VALUES('status_flag','xxx','2022-08-0113:35:50','2022-08-0113:35:50','xxx&......
  • mysql主从配置(1主2从)
    1、主库数据库配置[root@master~]#cat/etc/my.cnf#Foradviceonhowtochangesettingspleasesee#http://dev.mysql.com/doc/refman/5.7/en/server-configuration-defaults.html[mysqld]##Removeleading#andsettotheamountofRAMforthemostimportan......
  • MySQL 存储过程
    8.1.3mysql流程控制语句mysql流程控制语句和局部变量一样,只能放在存储过程,存储函数和触发器中1.顺序语句begin....end语句块,语句块中可以包含一组语句,语句可以嵌套begin语句块......;end;delimiter命令delimiter结束符;--将mysql的结束符修改为结束符2.......
  • MySQL EXPLAIN详解
    MySQL数据库是许多Web应用程序的底层支持,而查询性能的优化是确保系统高效运行的关键。在MySQL中,EXPLAIN是一项强大的工具,可帮助开发者深入了解查询语句的执行计划,从而更好地优化查询性能。本文将详细解析MySQL的EXPLAIN关键字,以揭开查询执行计划的面纱。什么是EXPLAIN?mysql官网文......
  • Redis和Mysql如何保证数据一致性?
    1、redis作用:用于读数据库操作的缓存层,可以减少数据库的io,还能提升数据的io性能;无法保证数据的acid2、实现一致性方案:1、先更新数据库,在更新缓存2、先删除缓存再更新数据库3、最终一致性方案: (1)基于roketMQ可靠通信 (2)通过canal组件采集mysqlbinlog日志,同步redis......
  • MySQL运维12-Mycat分库分表之按天分片
    一、按天分片指定一个时间周期,将数据写入一个数据节点中,例如:第1-10天的数据,写入到第一个数据节点中,第2-20天的数据写入到第二个节点中,第3-30天的数据节点写入到第三个数据节点中。   说明1:按天分片要配置一个起始日期,一个结束日期,一个分片间隔时间三个参数......
  • mysql-----------------------------------------------testdata
    6种SQL数据去重技巧大揭秘!原创 测试开发成长录 测试开发成长录 2023-12-1714:08 发表于广东你终于来了,戳蓝一键关注 测试开发成长录不负时光,遇见每一次成长   在上一期中,我们学习了SQL基本语法|查询语句的使用方法和技巧。接下来,我们将重点学习SQL中去重数据......
  • mysql union all、union、join
    union和join是需要联合多张表时常见的关联词,join:两张表做交连后里面条件相同的部分记录产生一个记录集,union:union是产生的两个记录集(字段要一样的)并在一起,成为一个新的记录集。二者区别:join和union的主要区别就一条,join是将拼接内容变成一行(左右拼接),根据共同字段将数据拼接成......