[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.怎么解决死锁

  1. 通过应用业务日志定位到问题代码,找到相应的事务对应的sql;
  2. 查看数据库死锁相关日志,确定死锁情况;
  3. 找到死锁的进程,手动kill一个或多个进程(破坏环路)

一般来说,数据库会帮我们检测死锁并破坏死锁情况

show engine innodb status; 查看引擎信息, 包含了死锁信息

MySQL死锁日志的查看和分析

3. 意向锁

在Innodb引擎支持表锁和行锁,意向锁将提高锁判断的效率,并可以让行锁和表锁同时存在 .

行锁分为共享锁,一个事务对一行的共享只读锁。排它锁,一个事务对一行的排他读写锁。

MyISAM 引擎中只有表锁

共享锁:加了读锁,只允许别的事务继续加读锁而不能加写锁,也就是只读

排它锁: 加了写锁,别的事务不允许加任何锁。

来自: https://www.zhihu.com/question/51513268

考虑一个例子:

事物A获得了一个行锁,事物B想来获得一个表锁,此时表锁应该不能成功,不然就冲突了.但是事物B要去判断是否会冲突,就会判断有没有表锁和行锁,而行锁只能一行行记录去判断,效率低下,所以此时需要意向锁.

当事务A想获得行锁时,会自动先获得一个意向锁,成功后才能获得行锁; 此时事务B判断完表锁后,再判断共享锁,这样就减少了判断次数

意向锁分为 意向共享锁和意向排它锁, 共享锁可以有多把, 从其特性看,意向锁是表锁,所以它可以让行锁和表锁同时存在

意向锁

表锁和行锁共存

4. for update

for update 仅适用于InnoDB,并且必须开启事务,在begincommit之间才生效。

这叫获得写锁或者排它锁 , 获得排他锁后,别人将不能获得排它锁 , 也不能修改该数据,只能读取

SELECT * FROM user WHERE id = 1 FOR UPDATE;

  1. InnoDB 默认是行级锁,当有明确指定的主键/索引时候(索引要生效),是行级锁,否则是表级锁
  2. 如果没有命中数据则不会锁

5.间隙锁Gap Locks

间隙锁基于非唯一索引,它锁定一段范围内的索引记录。间隙锁基于下面将会提到的Next-Key Locking 算法,请务必牢记:使用间隙锁锁住的是一个区间,而不仅仅是这个区间中的每一条数据, (这个区间内有几条就锁几条, 并且想修改或者插入到这个区间都不允许)。

加锁规则有以下特性:

  1. 加锁的基本单位是 临键锁(next-key lock),临间锁是前开后闭原则

  2. 插叙过程中访问的对象会增加锁

  3. 索引上的等值查询–给唯一索引加锁的时候,next-key lock升级为行锁

  4. 索引上的等值查询–向右遍历时最后一个值不满足查询需求时,next-key lock 退化为间隙锁

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

例如:

SELECT * FROM 表名称 WHERE id BETWEN 1 AND 10 FOR UPDATE;

即所有在(1,10)区间内的记录行都会被锁住,所有id 为 2、3、4、5、6、7、8、9 的数据行的插入会被阻塞,但是 1 和 10 两条记录行并不会被锁住。

简单记忆: 就是锁 区间的,但是不包括边界 , 只包括 区间内的值以及它们的"间隙"

  • 间隙锁存在于非唯一索引中,锁定开区间范围内的一段间隔,它是基于临键锁实现的。在索引记录之间的间隙中加锁,或者是在某一条索引记录之前或者之后加锁,并不包括该索引记录本身
  • 临键锁存在于非唯一索引中,该类型的每条记录的索引上都存在这种锁,它是一种特殊的间隙锁,锁定一段左开右闭的索引区间。即,除了锁住记录本身,也锁住索引之间的间隙。

MySQL如何解决幻读和不可重复读?

MYSQL(04)-间隙锁详解 - 简书 (jianshu.com)

6. 临键锁Next-Key Locks

Next-Key 可以理解为一种特殊的间隙锁,本质是间隙锁和行锁的结合。 通过临建锁和MVCC可以解决幻读的问题。 每个数据行上的非唯一索引列上都会存在一把临键锁,当某个事务持有该数据行的临键锁时,会锁住一段左开右闭区间的数据。需要强调的一点是,InnoDB 中行级锁是基于索引实现的,临键锁只与非唯一索引列有关,在唯一索引列(包括主键列)上不存在临键锁。

假设有如下表:

引擎:InnoDB,隔离级别:Repeatable-Read:table(id PK, age KEY, name )

idagename
110Lee
324Soraka
532Zed
745Talon

该表中 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