MySQL 30 用动态的观点看加锁

首先复习一下加锁规则:

  • 原则1:加锁的基本单位是next-key lock,是一个前开后闭区间;

  • 原则2:查找过程中访问到的对象才会加锁;

  • 优化1:索引上的等值查询,给唯一索引加锁的时候,next-key lock退化为行锁;

  • 优化2:索引上的等值查询,向右遍历时且最后一个值不满足等值条件的时候,next-key lock退化为间隙锁;

  • 一个bug:唯一索引上的范围查询会访问到不满足条件的第一个值为止。

接下来的讨论基于下表t:

sql 复制代码
CREATE TABLE `t` (
  `id` int(11) NOT NULL,
  `c` int(11) DEFAULT NULL,
  `d` int(11) DEFAULT NULL,
  PRIMARY KEY (`id`),
  KEY `c` (`c`)
) ENGINE=InnoDB;

insert into t values(0,0,0),(5,5,5),
(10,10,10),(15,15,15),(20,20,20),(25,25,25);

不等号条件里的等值查询

等值查询和遍历有什么区别?为什么当where条件是不等号,这个过程也有等值查询?

sql 复制代码
begin;
select * from t where id>9 and id<12 order by id desc for update;

利用加锁规则,这个语句的加锁范围是主键索引上的(0,5]、(5,10]、(10,15)。id=15没有加上行锁是因为用到了优化2,退化为了间隙锁。

但是查询语句里where条件不是等号,这里的等值查询又是从哪来的呢?

分析索引id的示意图:

  • 由于语义是order by id desc,要拿到满足条件的所有行,优化器必须先找到第一个id<12的值;

  • 该过程需要搜索索引树找到id=12的值,但最终没找到,只找到(10,15)的间隙;

  • 然后向左遍历,该遍历过程不是等值查询,会扫描到id=5这一行,会加一个(0,5]。

也就是说,在执行过程中,通过树搜索方式定位记录时用的是等值查询的方法。

等值查询的过程

下面这个语句的加锁范围是什么呢?

sql 复制代码
begin;
select id from t where c in(5,20,10) lock in share mode;

先看语句的explain结果:

该语句使用了索引c并且rows=3,说明这三个值都是通过B+树搜索定位的。

在查找c=5时,先锁住了(0,5],但因为c不是唯一索引,为了确认还有没有其他c=5的记录,需要向右遍历,直到c=10才确认没有,该过程满足优化2,所以加间隙锁(5,10)。

同样的,执行c=10 的时候,加锁的范围是(5,10]和(10,15);执行c=20的时候,加锁的范围是(15,20]和(20,25)。

这些锁是在执行过程中一个一个加的,而不是一次性加上去的。

假设同时有另外一个语句:

sql 复制代码
select id from t where c in(5,20,10) order by c desc for update;

间隙锁不互锁,但这两条语句都会在索引c上的c=5、10、20三行记录上加记录锁。

由于两条语句要加锁相同的资源,但加锁顺序相反,当这两条语句并发执行的时候,就可能出现死锁。

关于死锁的信息,MySQL只保留了最后一个死锁的现场,但这个现场还是不完备的。接下来就分析上面例子的死锁现场。

怎么看死锁?

出现死锁后,执行show engine innodb status命令能输出很多信息,其中有一节LATESTDETECTED DEADLOCK,就是记录的最后一次死锁信息。

该结果分为三部分:

  • (1) TRANSACTION:是第一个事务的信息;

  • (2) TRANSACTION:是第二个事务的信息;

  • WE ROLL BACK TRANSACTION (1):是最终处理结果,表示回滚了第一个事务。

第一个事务的信息中:

  • WAITING FOR THIS LOCK TO BE GRANTED:表示这个事务在等待的锁信息;

  • index c of table test.t:说明在等的是表t的索引c上面的锁;

  • lock mode S waiting:表示这个语句要自己加一个读锁,当前的状态是等待中;

  • Record lock:说明这是一个记录锁;

  • n_fields 2:表示这个记录是两列,也就是字段c和主键字段id;

  • 0: len 4; hex 0000000a; asc ;;:是第一个字段c。值是十六进制a,也就是10;

  • 1: len 4; hex 0000000a; asc ;;:是第二个字段,也就是主键id,值也是10;

  • 这两行里面的asc表示的是,接下来要打印出值里面的"可打印字符",但10不是可打印字符,因此就显示空格;

  • 第一个事务信息就只显示出了等锁的状态,在等待(c=10,id=10)这一行的锁;

第二个事务的信息中:

  • " HOLDS THE LOCK(S)"用来显示这个事务持有哪些锁;

  • index c of table test.t 表示锁是在表t的索引c上;

  • hex 0000000a和hex 00000014表示这个事务持有c=10和c=20这两个记录锁;

  • WAITING FOR THIS LOCK TO BE GRANTED,表示在等(c=5,id=5)这个记录锁。

从上面这些信息中能知道:

  • lock in share mode这条语句,持有c=5的记录锁,在等c=10的锁;

  • for update这个语句,持有c=20c=10的记录锁,在等c=5的记录锁。

因此导致死锁,由此得到结论:

  • 由于锁是一个个加的,要避免死锁,对同一组资源要按照尽量相同的顺序访问;

  • 在发生死锁的时刻,for update语句占用的资源更多,回滚成本更大,因此InnoDB选择了回滚成本更小的lock in share mode语句来回滚。

怎么看锁等待?

看完死锁,再看一个锁等待的例子。

由于session A并没有锁住c=10,所以session B删除这一行是可以的,但之后再想insert这一行回去就不行了。

此时执行show engine innodb status,锁信息是在TRANSACTIONS这一节:

  • index PRIMARY of table test.t :表示这个语句被锁住是因为表t主键上的某个锁;

  • lock_mode X locks gap before rec insert intention waiting:

    • insert intention:表示当前线程准备插入一个记录,这是一个插入意向锁。可以认为它就是这个插入动作本身;

    • gap before rec:表示这是一个间隙锁,而不是记录锁。这个gap是在哪个记录之前的呢?接下来的0~4这5行的内容就是这个记录的信息;

  • n_fields 5表示这一个记录有5列:

    • 0: len 4; hex 0000000f; asc ;; 第一列是主键id字段,这个间隙是id=15之前的,因为id=10已经不存在了,它表示的就是(5,15);

    • 1: len 6; hex 000000000513; asc ;; 第二列是长度为6字节的事务id,表示最后修改这一行的是trx id为1299的事务;

    • 2: len 7; hex b0000001250134; asc % 4;; 第三列长度为7字节的回滚段信息。acs后面有显示内容 (% 和 4),这是因为刚好这个字节是可打印字符。后面两列是c和d的值,都是15。

由此可知,delete操作删除了id=10的行,原来的间隙(5,10)、(10,15)变成了(5,15)。

有个结论:所谓间隙,其实是由"这个间隙右边的记录"定义的。

update的例子

再看一个update语句的案例:

session A的加锁范围是索引c上的(5,10]、(10,15]、(15,20]、(20,25]、(25,supremum]。

session B第一个语句要把c=5改为c=1,可以理解为两步:

  • 插入(c=1,id=5);

  • 删除(c=5,id=5)。

根据上面的结论,间隙是由间隙右边的记录定义,此时session A加锁范围变为:

session B的第二个语句拆成两步:

  • 插入(c=5,id=5);

  • 删除(c=1,id=5)。

第一步试图在间隙锁(1,10)插入数据,被堵住。

相关推荐
Zzzzmo_4 小时前
【MySQL】JDBC(含settings.xml文件配置/配置国内镜像以及pom.xml文件修改)
数据库·mysql
FirstFrost --sy5 小时前
MySQL内置函数
数据库·mysql
eggwyw6 小时前
MySQL-练习-数据汇总-CASE WHEN
数据库·mysql
mygljx8 小时前
MySQL 数据库连接池爆满问题排查与解决
android·数据库·mysql
Bdygsl9 小时前
MySQL(1)—— 基本概念和操作
数据库·mysql
身如柳絮随风扬9 小时前
什么是左匹配规则?
数据库·sql·mysql
jiankeljx10 小时前
mysql之如何获知版本
数据库·mysql
小李来了!10 小时前
数据库DDL、DML、DQL、DCL详解
数据库·mysql
我科绝伦(Huanhuan Zhou)11 小时前
【生产案例】MySQL InnoDB 数据损坏崩溃修复
数据库·mysql·adb
海棠蚀omo12 小时前
从零敲开 MySQL 的大门:库与表的基础操作实战(保姆级入门指南)
数据库·mysql