[TOC]
1 .
2. 死锁
2.1 死锁是怎么被发现的?
2.1.1 死锁成因&&检测方法
与java中死锁的成因一样,都是互相等待资源造成,
innodb是怎么探知死锁的?
直观方法是在两个事务相互等待时,当一个等待时间超过设置的某一阀值时,对其中一个事务进行回滚,另一个事务就能继续执行。这种方法简单有效,在innodb中,参数innodb_lock_wait_timeout用来设置超时时间。
仅用上述方法来检测死锁太过被动,innodb还提供了wait-for graph算法来主动进行死锁检测,每当加锁请求无法立即满足需要并进入等待时,wait-for graph算法都会被触发
2.1.2 wait-for graph原理
他们相互等待对方的资源,而且形成环路!我们将每辆车看为一个节点,当节点1需要等待节点2的资源时,就生成一条有向边指向节点2,最后形成一个有向图。我们只要检测这个有向图是否出现环路即可,出现环路就是死锁!这就是wait-for graph算法
2.2 innodb隔离级别、索引与锁
2.3 死锁成因
2.3.1不同表相同记录行锁冲突
这种情况很好理解,事务A和事务B操作两张表,但出现循环等待锁情况
2.3.2相同表记录行锁冲突
这种情况比较常见,之前遇到两个job在执行数据批量更新时,jobA处理的的id列表为[1,2,3,4],而job处理的id列表为[8,9,10,4,2],这样就造成了死锁。
2.3.3不同索引锁冲突
这种情况比较隐晦,事务A在执行时,除了在二级索引加锁外,还会在聚簇索引上加锁,在聚簇索引上加锁的顺序是[1,4,2,3,5],而事务B执行时,只在聚簇索引上加锁,加锁顺序是[1,2,3,4,5],这样就造成了死锁的可能性
2.3.4 gap锁(间隙锁)冲突
innodb在RR级别下,如下的情况也会产生死锁,比较隐晦。不清楚的同学可以自行根据上节的gap锁原理分析下。
2.4 如何尽可能避免死锁
1)以固定的顺序访问表和行。比如对第2节两个job批量更新的情形,简单方法是对id列表先排序,后执行,这样就避免了交叉等待锁的情形;又比如对于3.1节的情形,将两个事务的sql顺序调整为一致,也能避免死锁。
2)大事务拆小。大事务更倾向于死锁,如果业务允许,将大事务拆小。
3)在同一个事务中,尽可能做到一次锁定所需要的所有资源,减少死锁概率。
4)降低隔离级别。如果业务允许,将隔离级别调低也是较好的选择,比如将隔离级别从RR调整为RC,可以避免掉很多因为gap锁造成的死锁。
5)为表添加合理的索引。可以看到如果不走索引将会为表的每一行记录添加上锁,死锁的概率大大增大。
2.5.怎么解决死锁
- 通过应用业务日志定位到问题代码,找到相应的事务对应的sql;
- 查看数据库死锁相关日志,确定死锁情况;
- 找到死锁的进程,手动kill一个或多个进程(破坏环路)
一般来说,数据库会帮我们检测死锁并破坏死锁情况
show engine innodb status;
查看引擎信息, 包含了死锁信息
3. 意向锁
在Innodb引擎支持表锁和行锁,意向锁将提高锁判断的效率,并可以让行锁和表锁同时存在 .
行锁分为共享锁,一个事务对一行的共享只读锁。排它锁,一个事务对一行的排他读写锁。
MyISAM 引擎中只有表锁
共享锁:加了读锁,只允许别的事务继续加读锁而不能加写锁,也就是只读
排它锁: 加了写锁,别的事务不允许加任何锁。
考虑一个例子:
事物A获得了一个行锁,事物B想来获得一个表锁,此时表锁应该不能成功,不然就冲突了.但是事物B要去判断是否会冲突,就会判断有没有表锁和行锁,而行锁只能一行行记录去判断,效率低下,所以此时需要意向锁.
当事务A想获得行锁时,会自动先获得一个意向锁,成功后才能获得行锁; 此时事务B判断完表锁后,再判断共享锁,这样就减少了判断次数
意向锁分为 意向共享锁和意向排它锁, 共享锁可以有多把, 从其特性看,意向锁是表锁,所以它可以让行锁和表锁同时存在
4. for update
for update
仅适用于InnoDB
,并且必须开启事务,在begin
与commit
之间才生效。
这叫获得写锁或者排它锁 , 获得排他锁后,别人将不能获得排它锁 , 也不能修改该数据,只能读取
SELECT * FROM user WHERE id = 1 FOR UPDATE;
- InnoDB 默认是行级锁,当有明确指定的
主键/索引
时候(索引要生效),是行级锁
,否则是表级锁
。 - 如果没有命中数据则不会锁
5.间隙锁Gap Locks
间隙锁基于非唯一索引,它锁定一段范围内的索引记录。间隙锁基于下面将会提到的Next-Key Locking 算法,请务必牢记:使用间隙锁锁住的是一个区间,而不仅仅是这个区间中的每一条数据, (这个区间内有几条就锁几条, 并且想修改或者插入到这个区间都不允许)。
加锁规则有以下特性:
加锁的基本单位是 临键锁(next-key lock),临间锁是前开后闭原则
插叙过程中访问的对象会增加锁
索引上的等值查询–给唯一索引加锁的时候,next-key lock升级为行锁
索引上的等值查询–向右遍历时最后一个值不满足查询需求时,next-key lock 退化为间隙锁
唯一索引上的范围查询会访问到不满足条件的第一个值为止
例如:
SELECT * FROM 表名称 WHERE id BETWEN 1 AND 10 FOR UPDATE;
即所有在(1,10)
区间内的记录行都会被锁住,所有id 为 2、3、4、5、6、7、8、9
的数据行的插入会被阻塞,但是 1 和 10
两条记录行并不会被锁住。
简单记忆: 就是锁 区间的,但是不包括边界 , 只包括 区间内的值以及它们的"间隙"
- 间隙锁存在于非唯一索引中,锁定开区间范围内的一段间隔,它是基于临键锁实现的。在索引记录之间的间隙中加锁,或者是在某一条索引记录之前或者之后加锁,并不包括该索引记录本身。
- 临键锁存在于非唯一索引中,该类型的每条记录的索引上都存在这种锁,它是一种特殊的间隙锁,锁定一段左开右闭的索引区间。即,除了锁住记录本身,也锁住索引之间的间隙。
MYSQL(04)-间隙锁详解 - 简书 (jianshu.com)
6. 临键锁Next-Key Locks
Next-Key 可以理解为一种特殊的间隙锁,本质是间隙锁和行锁的结合。 通过临建锁和MVCC可以解决幻读的问题。 每个数据行上的非唯一索引列上都会存在一把临键锁,当某个事务持有该数据行的临键锁时,会锁住一段左开右闭区间的数据。需要强调的一点是,InnoDB 中行级锁是基于索引实现的,临键锁只与非唯一索引列有关,在唯一索引列(包括主键列)上不存在临键锁。
假设有如下表:
引擎:InnoDB,隔离级别:Repeatable-Read:table(id PK, age KEY, name )
id | age | name |
---|---|---|
1 | 10 | Lee |
3 | 24 | Soraka |
5 | 32 | Zed |
7 | 45 | Talon |
该表中 age 列潜在的临键锁有:
(-∞, 10],
(10, 24],
(24, 32],
(32, 45],
(45, +∞],
执行如下命令:
-- 根据非唯一索引列 UPDATE 某条记录
UPDATE table SET name = Vladimir WHERE age = 24;
因为24在边界上, 会锁住 上一个区间和下一个区间, 也就是说会锁住 (10,32)
执行如下命令:
-- 根据非唯一索引列 UPDATE 某条记录
UPDATE table SET name = Vladimir WHERE age = 22;
22在区间内, 会锁住 其所在区间, 也就是说会锁住 (10,24]
执行如下命令:
-- 根据非唯一索引列 delete 不存在的值
DELETE FROM table WHERE age = '100';// 100是不存在的值
会锁住100到无穷大都会锁住 (
100,∞],
一次MySQL死锁问题的排查与分析(一) | Throwable (throwx.cn)
MySQL:insert 加锁 - 简书 (jianshu.com)
面试官:MySQL的UPDATE语句会加哪些锁? | 毛英东的个人博客 (maoyingdong.com)
- MDL锁
- 意向锁
- 行锁
- 间隙锁
- Next-key Locking