阿里面试MySQL死锁问题的处理

目录
  • 1、什么是死锁
  • 2、InnoDB锁类型
    • 2.1、间隙锁(gaplock)
    • 2.2、next-keylock
    • 2.3、意向锁(Intentionlock)
    • 2.4、插入意向锁(InsertIntentionlock)
    • 2.5、锁模式兼容矩阵
  • 3、阅读死锁日志
    • 3.1、日志分析如下:
  • 4、经典案例分析
    • 4.1、事务并发insert唯一键冲突
    • 4.2、先update再insert的并发死锁问题
  • 5、如何尽可能避免死锁
  • 结尾

咱们使用 MySQL 大概率上都会遇到死锁问题,这实在是个令人非常头痛的问题。本文将会对死锁进行相应介绍,对常见的死锁案例进行相关分析与探讨,以及如何去尽可能避免死锁给出一些建议。

1、什么是死锁

死锁是并发系统中常见的问题,同样也会出现在数据库MySQL的并发读写请求场景中。当两个及以上的事务,双方都在等待对方释放已经持有的锁或因为加锁顺序不一致造成循环等待锁资源,就会出现“死锁”。常见的报错信息为 Deadlock found when trying to get lock...

举例来说 A 事务持有 X1 锁 ,申请 X2 锁,B事务持有 X2 锁,申请 X1 锁。A 和 B 事务持有锁并且申请对方持有的锁进入循环等待,就造成了死锁。

如上图,是右侧的四辆汽车资源请求产生了回路现象,即死循环,导致了死锁。

从死锁的定义来看,MySQL 出现死锁的几个要素为:

  • 两个或者两个以上事务
  • 每个事务都已经持有锁并且申请新的锁
  • 锁资源同时只能被同一个事务持有或者不兼容
  • 事务之间因为持有锁和申请锁导致彼此循环等待

2、InnoDB 锁类型

为了分析死锁,我们有必要对 InnoDB 的锁类型有一个了解。

MySQL InnoDB 引擎实现了标准的行级别锁:共享锁( S lock ) 和排他锁 ( X lock )

  • 不同事务可以同时对同一行记录加 S 锁。
  • 如果一个事务对某一行记录加 X 锁,其他事务就不能加 S 锁或者 X 锁,从而导致锁等待。

如果事务 T1 持有行 r 的 S 锁,那么另一个事务 T2 请求 r 的锁时,会做如下处理:

  • T2 请求 S 锁立即被允许,结果 T1 T2 都持有 r 行的 S 锁
  • T2 请求 X 锁不能被立即允许

如果 T1 持有 r 的 X 锁,那么 T2 请求 r 的 X、S 锁都不能被立即允许,T2 必须等待 T1 释放 X 锁才可以,因为 X 锁与任何的锁都不兼容。共享锁和排他锁的兼容性如下所示:

2.1、间隙锁( gap lock )

间隙锁锁住一个间隙以防止插入。假设索引列有2, 4, 8 三个值,如果对 4 加锁,那么也会同时对(2,4)和(4,8)这两个间隙加锁。其他事务无法插入索引值在这两个间隙之间的记录。但是,间隙锁有个例外:

  • 如果索引列是唯一索引,那么只会锁住这条记录(只加行锁),而不会锁住间隙。
  • 对于联合索引且是唯一索引,如果 where 条件只包括联合索引的一部分,那么依然会加间隙锁。

2.2、next-key lock

next-key lock 实际上就是 行锁+这条记录前面的 gap lock 的组合。假设有索引值10,11,13和 20,那么可能的 next-key lock 包括:

(负无穷,10],(10,11],(11,13],(13,20],(20,正无穷)

在 RR 隔离级别下,InnoDB 使用 next-key lock 主要是防止幻读问题产生。

2.3、意向锁( Intention lock )

InnoDB 为了支持多粒度的加锁,允许行锁和表锁同时存在。为了支持在不同粒度上的加锁操作,InnoDB 支持了额外的一种锁方式,称之为意向锁( Intention Lock )。意向锁是将锁定的对象分为多个层次,意向锁意味着事务希望在更细粒度上进行加锁。意向锁分为两种:

  • 意向共享锁( IS ):事务有意向对表中的某些行加共享锁
  • 意向排他锁( IX ):事务有意向对表中的某些行加排他锁

由于 InnoDB 存储引擎支持的是行级别的锁,因此意向锁其实不会阻塞除全表扫描以外的任何请求。表级意向锁与行级锁的兼容性如下所示:

2.4、插入意向锁( Insert Intention lock )

插入意向锁是在插入一行记录操作之前设置的一种间隙锁,这个锁释放了一种插入方式的信号,即多个事务在相同的索引间隙插入时如果不是插入间隙中相同的位置就不需要互相等待。假设某列有索引值2,6,只要两个事务插入位置不同(如事务 A 插入3,事务 B 插入4),那么就可以同时插入。

2.5、锁模式兼容矩阵

横向是已持有锁,纵向是正在请求的锁:

3、阅读死锁日志

在进行具体案例分析之前,咱们先了解下如何去读懂死锁日志,尽可能地使用死锁日志里面的信息来帮助我们来解决死锁问题。

后面测试用例的数据库场景如下:MySQL 5.7 事务隔离级别为 RR

表结构和数据如下:

测试用例如下:

通过执行show engine innodb status 可以查看到最近一次死锁的日志。

3.1、日志分析如下:

1.***** (1) TRANSACTION: TRANSACTION 2322, ACTIVE 6 sec starting index read

事务号为2322,活跃 6秒,starting index read 表示事务状态为根据索引读取数据。常见的其他状态有:

mysql tables in use 1 说明当前的事务使用一个表。

locked 1 表示表上有一个表锁,对于 DML 语句为LOCK_IX

LOCK WAIT 2 lock struct(s), heap size 1136, 1 row lock(s)

LOCK WAIT 表示正在等待锁,2 lock struct(s) 表示 trx->trx_locks 锁链表的长度为2,每个链表节点代表该事务持有的一个锁结构,包括表锁,记录锁以及自增锁等。本用例中 2locks 表示 IX 锁和lock_mode X (Next-key lock)

1 row lock(s) 表示当前事务持有的行记录锁/ gap 锁的个数。

MySQL thread id 37, OS thread handle 140445500716800, query id 1234 127.0.0.1 root updating

MySQL thread id 37 表示执行该事务的线程 ID 为 37 (即 show processlist; 展示的 ID )

delete from student where stuno=5 表示事务1正在执行的 sql,比较难受的事情是 show engine innodb status 是查看不到完整的 sql 的,通常显示当前正在等待锁的 sql。

***** (1) WAITING FOR THIS LOCK TO BE GRANTED:

RECORD LOCKS space id 11 page no 5 n bits 72 index idx_stuno of table cw****.****student trx id 2322 lock_mode X waiting

RECORD LOCKS 表示记录锁, 此条内容表示事务 1 正在等待表 student 上的 idx_stuno 的 X 锁,本案例中其实是 Next-Key Lock 。

事务2的 log 和上面分析类似:

2.***** (2) HOLDS THE LOCK(S):

RECORD LOCKS space id 11 page no 5 n bits 72 index idx_stuno of table cw****.****student trx id 2321 lock_mode X

显示事务 2 的 insert into student(stuno,score) values(2,10) 持有了 a=5 的 Lock mode X

 LOCK_gap,不过我们从日志里面看不到事务2执行的

delete from student where stuno=5;

这点也是造成 DBA 仅仅根据日志难以分析死锁的问题的根本原因。

3.***** (2) WAITING FOR THIS LOCK TO BE GRANTED:

RECORD LOCKS space id 11 page no 5 n bits 72 index idx_stuno of table cw****.****student trx id 2321 lock_mode X locks gap before rec insert intention waiting

表示事务 2 的 insert 语句正在等待插入意向锁

lock_mode X locks gap before rec insert intention waiting ( LOCK_X + LOCK_REC_gap )

4、经典案例分析

4.1、事务并发 insert 唯一键冲突

表结构和数据如下所示:

测试用例如下:

日志分析如下:

事务 T2 insert into t7(id,a) values (26,10) 语句 insert 成功,持有 a=10 的 排他行锁( Xlocks rec but no gap )

事务 T1 insert into t7(id,a) values (30,10), 因为T2的第一条 insert 已经插入 a=10 的记录,事务 T1 insert a=10 则发生唯一键冲突,需要申请对冲突的唯一索引加上S Next-key Lock( 即 lock mode S waiting ) 这是一个间隙锁会申请锁住(,10],(10,20]之间的 gap 区域。

事务 T2 insert into t7(id,a) values (40,9)该语句插入的 a=9 的值在事务 T1 申请的 gap 锁4-10之间, 故需事务 T2 的第二条 insert 语句要等待事务 T1 的 S-Next-key Lock 锁释放,在日志中显示

lock_mode X locks gap before rec insert intention waiting 。

4.2、先 update 再 insert 的并发死锁问题

表结构如下,无数据:

测试用例如下:

死锁分析:

可以看到两个事务 update 不存在的记录,先后获得间隙锁( gap 锁),gap 锁之间是兼容的所以在update环节不会阻塞。两者都持有 gap 锁,然后去竞争插入意向锁。当存在其他会话持有 gap 锁的时候,当前会话申请不了插入意向锁,导致死锁。

5、如何尽可能避免死锁

  1. 合理的设计索引,区分度高的列放到组合索引前面,使业务 SQL 尽可能通过索引定位更少的行,减少锁竞争。
  2. 调整业务逻辑 SQL 执行顺序, 避免 update/delete 长时间持有锁的 SQL 在事务前面。
  3. 避免大事务,尽量将大事务拆成多个小事务来处理,小事务发生锁冲突的几率也更小。
  4. 以固定的顺序访问表和行。比如两个更新数据的事务,事务 A 更新数据的顺序为 1,2;事务 B 更新数据的顺序为 2,1。这样更可能会造成死锁。
  5. 在并发比较高的系统中,不要显式加锁,特别是是在事务里显式加锁。如 select … for update 语句,如果是在事务里(运行了 start transaction 或设置了autocommit 等于0),那么就会锁定所查找到的记录。
  6. 尽量按主键/索引去查找记录,范围查找增加了锁冲突的可能性,也不要利用数据库做一些额外额度计算工作。比如有的程序会用到 “select … where … order by rand();”这样的语句,由于类似这样的语句用不到索引,因此将导致整个表的数据都被锁住。
  7. 优化 SQL 和表设计,减少同时占用太多资源的情况。比如说,减少连接的表,将复杂 SQL 分解为多个简单的 SQL。

结尾

以上就是阿里面试MySQL死锁问题的处理的详细内容,更多关于阿里面试MySQL死锁问题的资料请关注我们其它相关文章!

(0)

相关推荐

  • MySQL锁等待与死锁问题分析

    前言: 在 MySQL 运维过程中,锁等待和死锁问题是令各位 DBA 及开发同学非常头痛的事.出现此类问题会造成业务回滚.卡顿等故障,特别是业务繁忙的系统,出现死锁问题后影响会更严重.本篇文章我们一起来学习下什么是锁等待及死锁,出现此类问题又应该如何分析处理呢? 1.了解锁等待与死锁 出现锁等待或死锁的原因是访问数据库需要加锁,那你可能要问了,为啥要加锁呢?原因是为了确保并发更新场景下的数据正确性,保证数据库事务的隔离性. 试想一个场景,如果你要去图书馆借一本<高性能MySQL>,为了防止有人

  • MySQL死锁套路之唯一索引下批量插入顺序不一致

    前言 死锁的本质是资源竞争,批量插入如果顺序不一致很容易导致死锁,我们来分析一下这个情况.为了方便演示,把批量插入改写为了多条 insert. 先来做几个小实验,简化的表结构如下 CREATE TABLE `t1` ( `id` int(11) NOT NULL AUTO_INCREMENT, `a` varchar(5), `b` varchar(5), PRIMARY KEY (`id`), UNIQUE KEY `uk_name` (`a`,`b`) ); 实验1: 在记录不存在的情况下,

  • MySQL死锁问题分析及解决方法实例详解

    MySQL死锁问题是很多程序员在项目开发中常遇到的问题,现就MySQL死锁及解决方法详解如下: 1.MySQL常用存储引擎的锁机制 MyISAM和MEMORY采用表级锁(table-level locking) BDB采用页面锁(page-level locking)或表级锁,默认为页面锁 InnoDB支持行级锁(row-level locking)和表级锁,默认为行级锁 2.各种锁特点 表级锁:开销小,加锁快;不会出现死锁;锁定粒度大,发生锁冲突的概率最高,并发度最低 行级锁:开销大,加锁慢;

  • 详解MySQL(InnoDB)是如何处理死锁的

    一.什么是死锁 官方定义如下:两个事务都持有对方需要的锁,并且在等待对方释放,并且双方都不会释放自己的锁. 这个就好比你有一个人质,对方有一个人质,你们俩去谈判说换人.你让对面放人,对面让你放人. 二.为什么会形成死锁 看到这里,也许你会有这样的疑问,事务和谈判不一样,为什么事务不能使用完锁之后立马释放呢?居然还要操作完了之后一直持有锁?这就涉及到 MySQL 的并发控制了. MySQL的并发控制有两种方式,一个是 MVCC,一个是两阶段锁协议.那么为什么要并发控制呢?是因为多个用户同时操作 M

  • MySQL死锁检查处理的正常方法

    正常情况下,死锁发生时,权重最小的连接将被kill并回滚.但是为了找出语句来优化,启用可启用死锁将死锁信息记录下来. #step 1:窗口一 mysql> start transaction; mysql> update aa set name='aaa' where id = 1; #step 2:窗口二 mysql> start transaction; mysql> update bb set name='bbb' where id = 1; #step 3:窗口一 mysq

  • MySQL死锁的产生原因以及解决方案

    数据库和操作系统一样,是一个多用户使用的共享资源.当多个用户并发地存取数据 时,在数据库中就会产生多个事务同时存取同一数据的情况.若对并发操作不加控制就可能会读取和存储不正确的数据,破坏数据库的一致性.加锁是实现数据库并 发控制的一个非常重要的技术.在实际应用中经常会遇到的与锁相关的异常情况,当两个事务需要一组有冲突的锁,而不能将事务继续下去的话,就会出现死锁,严 重影响应用的正常执行. 在数据库中有两种基本的锁类型:排它锁(Exclusive Locks,即X锁)和共享锁(Share Lock

  • MySQL 死锁套路:唯一索引 S 锁与 X 锁的爱恨情仇

    在初学者从源码理解MySQL死锁问题中介绍了使用调试 MySQL  源码的方式来查看死锁的过程,这篇文章来讲讲一个常见的案例. 毫不夸张的说,有一半以上的死锁问题由唯一索引贡献,后面介绍的很多死锁的问题都跟唯一索引有关.这次我们讲一段唯一索引 S 锁与 X 锁的爱恨情仇 我们来看一个简化过的例子 # 构造数据 CREATE TABLE `t1` ( `id` int(11) NOT NULL AUTO_INCREMENT, `name` varchar(10), `level` int(11),

  • 阿里面试MySQL死锁问题的处理

    目录 1.什么是死锁 2.InnoDB锁类型 2.1.间隙锁(gaplock) 2.2.next-keylock 2.3.意向锁(Intentionlock) 2.4.插入意向锁(InsertIntentionlock) 2.5.锁模式兼容矩阵 3.阅读死锁日志 3.1.日志分析如下: 4.经典案例分析 4.1.事务并发insert唯一键冲突 4.2.先update再insert的并发死锁问题 5.如何尽可能避免死锁 结尾 咱们使用 MySQL 大概率上都会遇到死锁问题,这实在是个令人非常头痛的

  • 阿里面试Nacos配置中心交互模型是push还是pull原理解析

    目录 引言 配置中心 长轮询 Nacos初识 几个概念 客户端源码分析 结束语 引言 对于Nacos大家应该都不太陌生,出身阿里名声在外,能做动态服务发现.配置管理,非常好用的一个工具.然而这样的技术用的人越多面试被问的概率也就越大,如果只停留在使用层面,那面试可能要吃大亏. 比如我们今天要讨论的话题,Nacos在做配置中心的时候,配置数据的交互模式是服务端推过来还是客户端主动拉的? 这里我先抛出答案:客户端主动拉的! 接下来咱们扒一扒Nacos的源码,来看看它具体是如何实现的? 配置中心 聊N

  • 一次神奇的MySQL死锁排查记录

    背景 说起Mysql死锁,之前写过一次有关Mysql加锁的基本介绍,对于一些基本的Mysql锁或者死锁都有一个简单的认识,可以看下这篇文章为什么开发人员需要了解数据库锁.有了上面的经验之后,本以为对于死锁都能手到擒来,没想到再一个阳光明媚的下午报出了一个死锁,但是这一次却没想象的那么简单. 问题初现 在某天下午,突然系统报警,抛出个异常: 仔细一看好像是事务回滚异常,写着的是因为死锁回滚,原来是个死锁问题,由于我对Mysql锁还是有一定了解的,于是开始主动排查这个问题. 首先在数据库中查找Inn

  • 通过唯一索引S锁与X锁来了解MySQL死锁套路

    在初学者从源码理解MySQL死锁问题中介绍了使用调试 MySQL  源码的方式来查看死锁的过程,这篇文章来讲讲一个常见的案例. 这次我们讲一段唯一索引 S 锁与 X 锁的爱恨情仇 我们来看一个简化过的例子 # 构造数据 CREATE TABLE `t1` ( `id` int(11) NOT NULL AUTO_INCREMENT, `name` varchar(10), `level` int(11), PRIMARY KEY (`id`), UNIQUE KEY `uk_name` (`na

  • mysql死锁和分库分表问题详解

    记录生产mysql的问题点. 业务场景与问题描述 请求一个外部接口时,每天的请求量在900万左右. 分为请求项目和回执这两个项目.请求是用来调用外部接口,回执是接收发送的接口. 在发送请求前会先插入数据库. 在请求后,如果接口返回调用失败,会更新数据库状态为失败. 如果发送成功,则会等待上游给出回执消息后,然后更新数据库状态. 而在生产运行过程中,半年出现过两次mysql导致的mq消费者堆积的问题. 问题分析 记录两次不同的原因导致的生产问题及原因分析. mysql死锁问题 查看mq聚合平台TP

  • RC级别下MySQL死锁问题的解决

    目录 背景 死锁分析 死锁解决 背景 在工作中碰到一次死锁问题,业务背景是在mq接收商品主数据时会更新商品其他数据,由于商品主数据和商品其他信息是一对多的关系,所以采用先删后增的方式,结果异常监管平台报出来死锁警告. 这是商品其他信息表,数据库隔离级别是RC,表有一个唯一联合索引,这个唯一索引就是引起死锁的关键. 死锁分析 下面是线上的一个死锁日志 2021-03-15 16:40:49 0x7f17e97ff700 *** (1) TRANSACTION: TRANSACTION 212057

随机推荐