MySQL死锁是什么,如何解决?

1 什么是mysql死锁?

死锁是指两个或多个事务在执行过程中,因争夺锁资源而造成的相互等待的现象,若无外力干涉它们都将无法继续执行。

通俗来说,就是两个或多个事务在等待对方释放锁,从而造成僵持不下,使得整个系统陷入停滞状态。

2 从操作的粒度进行的mysql锁的分类

从操作的粒度可分为表级锁、行级锁和页级锁。

▌表级锁:

每次操作锁住整张表锁定粒度大,发生锁冲突的概率最高,并发度最低

应用在MyISAM、InnoDB、BDB 等存储引擎中。

表锁的特点:

  • 开销小,加锁快
  • 不会出现死锁
  • 锁定粒度大,发生锁冲突的概率最高,并发度最低

▌行级锁:

每次操作锁住一行数据锁定粒度最小,发生锁冲突的概率最低,并发度最高

应用在InnoDB 存储引擎中。

行锁的特点:

  • 开销大,加锁慢
  • 会出现死锁
  • 锁定粒度小,发生锁冲突的概率最低,并发度最高

▌页级锁:

每次锁定相邻的一组记录,锁定粒度界于表锁和行锁之间,开销和加锁时间界于表锁和行锁之间,并发度一般。

页锁的特点:

  • 开销和加锁时间介于表锁和行锁之间
  • 会出现死锁
  • 锁定粒度介于表锁和行锁之间,并发度一般

3 从操作的类型进行的mysql锁的分类

从操作的类型可分为读锁和写锁。

▌读锁(S锁)

读锁(S锁):共享锁,针对同一份数据,多个读操作可以同时进行而不会互相影响。

S锁:事务A对记录添加了S锁,可以对记录进行读操作,不能做修改,其他事务可以对该记录追加S锁,但是不能追加X锁,要追加X锁,需要等记录的S锁全部释放。

▌写锁(X锁)

写锁(X锁):排他锁,当前写操作没有完成前,它会阻断其他写锁和读锁

X锁:事务A对记录添加了X锁,可以对记录进行读和修改操作,其他事务不能对记录做读和修改操作。

▌意向锁

  • IS: 意向共享锁,表级锁,已加S锁的表,肯定会有IS锁,反过来,有IS锁的表,不一定会有S锁
  • IX: 意向排它锁,表级锁,已加X锁的表,肯定会有IX锁,反过来,有IX锁的表,不一定会有X锁

▌4 从操作的性能进行的mysql锁的分类

从操作的性能可分为乐观锁和悲观锁。

  • 乐观锁:一般的实现方式是对记录数据版本进行比对,在数据更新提交的时候才会进行冲突检测,如果发现冲突了,则提示错误信息。
  • 悲观锁:在对一条数据修改的时候,为了避免同时被其他人修改,在修改数据之前先锁定,再修改的控制方式。共享锁和排他锁是悲观锁的不同实现,但都属于悲观锁范畴

5 InnoDB存储引擎三种行锁模式

InnoDB引擎行锁是通过对索引数据页上的记录加锁实现的,主要实现算法有 3 种:Record Lock、Gap Lock 和 Next-key Lock,也就是InnoDB的三种行锁模式。

  • RecordLock锁(行锁):锁定单个行记录的锁。(RecordLock锁 是记录锁,RC、RR隔离级别都支持)
  • GapLock锁:间隙锁,锁定索引记录间隙(不包括记录本身),确保索引记录的间隙不变。(GapLock是范围锁,RR隔离级别支持。RC隔离级别不支持)
  • Next-key Lock 锁(临键锁):记录锁和间隙锁组合,同时锁住数据,并且锁住数据前后范围。(记录锁+范围锁,RR隔离级别支持。RC隔离级别不支持)
▌5.1 记录锁(Record Locks)

(1)记录锁, 仅仅锁住索引记录的一行,在单条索引记录上加锁。 (2)record lock锁住的永远是索引,而非记录本身,即使该表上没有任何索引,那么innodb会在后台创建一个隐藏的聚集主键索引,那么锁住的就是这个隐藏的聚集主键索引。

所以说当一条sql没有走任何索引时,那么将会在每一条聚合索引后面加X锁,这个类似于表锁,但原理上和表锁应该是完全不同的。

▌5.2 间隙锁(Gap Locks)

(1)区间锁, 仅仅锁住一个索引区间(开区间,不包括双端端点)。 (2)在索引记录之间的间隙中加锁,或者是在某一条索引记录之前或者之后加锁,并不包括该索引记录本身。

(3)间隙锁可用于防止幻读,保证索引间的不会被插入数据

比如在 100、10000中,间隙锁的可能值有 (∞, 100),(100, 10000),(10000, ∞)

5.3 行锁:临键锁(Next-Key Locks)

(1)record lock + gap lock, 左开右闭区间。

(2)默认情况下,innodb使用next-key locks来锁定记录。select … for update (3)但当查询的索引含有唯一属性的时候,Next-Key Lock 会进行优化,将其降级为Record Lock,即仅锁住索引本身,不是范围。 (4)Next-Key Lock在不同的场景中会退化:

比如在 100、10000中,临键锁(Next-Key Locks)的可能有 (∞, 100],(100, 10000]这里的关键是左开右闭

6 事务隔离级别和锁的关系

▌6.1 数据库事务的隔离级别

先来回顾一下,数据库事务的隔离级别,目前数据库事务的隔离级别一共有 4 种,由低到高分别为:

事务的四个隔离级别:

  • 未提交读(READ UNCOMMITTED):所有事务都可以看到其他事务未提交的修改。一般很少使用;
  • 读已提交(READ COMMITTED):Oracle默认隔离级别,事务之间只能看到彼此已提交的变更修改;
  • 可重复读(REPEATABLE READ):MySQL默认隔离级别,同一事务中的多次查询会看到相同的数据行;可以解决不可重复读,但可能出现幻读;
  • 可串行化(SERIALIZABLE):最高的隔离级别,事务串行的执行,前一个事务执行完,后面的事务会执行。读取每条数据都会加锁,会导致大量的超时和锁争用问题;

数据库一般默认的隔离级别为 读已提交 RC ,比如 Oracle,

也有一些数据的默认隔离级别为 可重复读 RR,比如 Mysql。

"可重复读"(Repeatable Read)这个级别确保了对同一字段的多次读取结果是一致的,除非数据是被本身事务自己所修改。

RR它能够防止脏读、不可重复读,但可能会遇到幻读的情况。

6.2 事务隔离级别和锁的关系

  1. 事务隔离级别是SQL92定制的标准,相当于事务并发控制的整体解决方案,本质上是对锁和MVCC使用的封装,隐藏了底层细节。
  2. 锁是数据库实现并发控制的基础,事务隔离性是采用锁来实现,对相应操作加不同的锁,就可以防止其他事务同时对数据进行读写操作。
  3. 对用户来讲,首先选择使用隔离级别,当选用的隔离级别不能解决并发问题或需求时,才有必要在开发中手动的设置锁。
  4. MySQL 默认隔离级别:可重复读, 一般建议改为 RC 读已提交
  5. Oracle、SQLServer默认隔离级别:读已提交

7 死锁产生原因和解决方案

InnoDB与MyISAM的最大不同有两点

  • 支持事务
  • 采用行锁

行级锁和表级锁本来就有许多不同之处,另外,事务的引入也带来了一些问题 ,比如 死锁。

▌7.1 查看Innodb行锁争用情况

通过show status like 'innodb_row_lock_%'; 命令可以查询MySQL整体的锁状态,如下:

  • Innodb_row_lock_current_waits:当前正在阻塞等待锁的事务数量。
  • Innodb_row_lock_time:MySQL启动到现在,所有事务总共阻塞等待的总时长。
  • Innodb_row_lock_time_avg:平均每次事务阻塞等待锁时,其平均阻塞时长。
  • Innodb_row_lock_time_max:MySQL启动至今,最长的一次阻塞时间。
  • Innodb_row_lock_waits:MySQL启动到现在,所有事务总共阻塞等待的总次数。

▌7.2 详细介绍 死锁的概念

什么是死锁DeadLock? :

是指两个或两个以上的进程在执行过程中, 因争夺资源而造成的一种互相等待的现象,

若无外力作用,它们都将无法推进下去.

此时称系统处于死锁状态或系统产生了死锁,这些永远在互相等待的进程称为死锁进程。

一个的形象举例:

假设有两个事务 A 和 B,它们同时试图获取对方持有的资源,但又都在等待对方释放资源,导致了僵持不下的局面。

7.3 表级锁死锁

▌a)产生原因:

  • 用户A先访问表1(锁住了表1),然后再访问表2;
  • 用户B先访问表2(锁住了表2),然后再企图访问表1;
  • 用户A和用户B加锁的顺序如下:
  • 用户A--》表1(表锁)--》表2(表锁)
  • 用户B--》表2(表锁)--》表1(表锁)
  • 这时
  • 用户A由于用户B已经锁住表2,它必须等待用户B释放表2才能继续
  • 同样用户B要等用户A释放表1才能继续

8: InnoDB预防死锁策略

InnoDB引擎内部(或者说是所有的数据库内部),有多种锁类型:事务锁(行锁、表锁),Mutex(保护内部的共享变量操作)、RWLock(又称之为Latch,保护内部的页面读取与修改)。

InnoDB每个页面为16K,读取一个页面时,需要对页面加S锁(共享锁),更新一个页面时,需要对页面加上X锁(排他锁)。

任何情况下,操作一个页面,都会对页面加锁,页面锁加上之后,页面内存储的索引记录才不会被并发修改。

因此,为了修改一条记录,InnoDB内部如何处理:

  • 根据给定的查询条件,找到对应的记录所在页面;
  • 对页面加上X锁(RWLock),然后在页面内寻找满足条件的记录;
  • 在持有页面锁的情况下,对满足条件的记录加事务锁(行锁:根据记录是否满足查询条件,记录是否已经被删除,分别对应于上面提到的3种加锁策略之一);

相对于事务锁,页面锁是一个短期持有的锁,而事务锁(行锁、表锁)是长期持有的锁

▌InnoDB预防死锁策略

因此,为了防止页面锁与事务锁之间产生死锁,InnoDB做了死锁预防的策略:

  • 持有事务锁(行锁、表锁),可以等待获取页面锁
  • 但反之,持有页面锁,不能等待持有事务锁。

根据死锁预防策略,在持有页面锁,加行锁的时候,如果行锁需要等待,则释放页面锁,然后等待行锁。

此时,行锁获取没有任何锁保护,因此加上行锁之后,记录可能已经被并发修改。因此,此时要重新加回页面锁,重新判断记录的状态,重新在页面锁的保护下,对记录加锁。

如果此时记录未被并发修改,那么第二次加锁能够很快完成,因为已经持有了相同模式的锁。但是,如果记录已经被并发修改,那么,就有可能导致死锁问题。

在数据库系统中,死锁的检测和解决通常是通过**锁管理器(Lock Manager)**来实现的。

  • 当一个事务请求某个数据页的锁时,锁管理器会检查当前锁的状态以及其他事务是否持有或等待相同的锁。
  • 如果存在潜在的死锁风险,系统会通过死锁检测算法来检测并解决死锁。其中,常用的死锁检测算法包括等待图(Wait-for graph)算法和超时算法。

在数据库系统的实现中,锁管理器会维护一个锁表(Lock Table),用于记录当前数据页的锁状态以及事务之间的关系。

当一个事务请求锁时,锁管理器会根据锁定顺序来判断是否存在死锁风险,并根据具体情况采取相应的措施,比如阻塞等待或者回滚事务。

在数据库系统的源代码级别,锁管理器通常是数据库引擎的一部分,具体实现方式会根据不同的数据库系统而有所不同。例如,MySQL、PostgreSQL、Oracle等数据库系统都有自己的锁管理器实现,通常会涉及到并发控制、事务管理等核心模块的代码。

总之,在MySQL 5.5.5及以上版本中,MySQL的默认存储引擎是InnoDB。该存储引擎使用的是行级锁,在某种情况下会产生死锁问题,所以InnoDB存储引擎采用了一种叫作等待图(wait-for graph)的方法来自动检测死锁,如果发现死锁,就会自动回滚一个事务

10 : 死锁产生的前提和建议

前提

  • 互斥:不能共享
  • 持有并等待:当前事务保持至少一个资源,同时在等待获取其他资源。
  • **不可剥夺 **:已获得的资源不能被强制释放,只能由获取该资源的事务主动释放。
  • **循环等待:**系统中若干事务之间形成了一个循环等待资源的链。
  • 死锁的关键在于:两个(或以上)的Session加锁的顺序不一致。
  • 那么对应的解决死锁问题的关键就是:让不同的session加锁有次序

建议:

  • 一致性排序
  • 对索引加锁顺序的不一致很可能会导致死锁, 所以如果可以, 尽量以相同的顺序来访问索引记录和表.
  • 在程序以批量方式处理数据的时候, 如果事先对数据排序, 保证每个线程按固定的顺序来处理记录, 也可以大大降低出现死锁的可能.
  • 间隙锁
  • 往往是程序中导致死锁的真凶, 由于默认情况下 MySQL 的隔离级别是 RR(Repeatable Read,可重复读),所以如果能确定幻读和不可重复读对应用的影响不大, 可以考虑将隔离级别改成 RC, 可以避免 Gap 锁导致的死锁.
  • 为表添加合理的索引, 如果不走索引将会为表的每一行记录加锁, 死锁的概率就会大大增大.
  • 避免大事务, 尽量将大事务拆成多个小事务来处理.
  • 因为大事务占用资源多, 耗时长, 与其他事务冲突的概率也会变高.
  • 避免在同一时间点运行多个对同一表进行读写的脚本, 特别注意加锁且操作数据量比较大的语句.
  • 超时和重试机制设置锁等待超时参数
  • innodb_lock_wait_timeout,在并发访问比较高的情况下,如果大量事务因无法立即获得所需的锁而挂起,会占用大量计算机资源,造成严重性能问题,甚至拖跨数据库。
  • 我们通过设置合适的锁等待超时阈值,可以避免这种情况发生。

▌11: 线上发生了死锁,应该如何具体操作?

数据库的死锁是指不同的事务在获取资源时相互等待,导致无法继续执行的情况。

MySQL中可能发生死锁的情况包括事务同时更新多个表、事务嵌套、索引顺序不一致以及不同事务同时更新相同的索引等。

虽然数据库有死锁的预防策略,以及自动的处理措施。 但是,在线上很多场景下, 数据的的死锁预防策略和回滚策略 , 通常达不到预期的效果。

如果线上发生了死锁,我们应该采取以下步骤进行处理:

▌11.1 监控死锁

通过数据库的监控工具或命令查看是否存在死锁情况,了解死锁的具体情况,包括死锁的事务和死锁的资源。

step1: 查看当前正在等待锁的事务

SELECT * FROM INFORMATION_SCHEMA.INNODB_LOCK_WAITS;

运行以上SQL语句,可以查看当前正在等待锁的事务列表。

根据返回结果,可以分析哪些事务在等待哪些锁,以及等待锁的具体类型。

step2:查看当前持有的锁信息

SELECT * FROM INFORMATION_SCHEMA.INNODB_LOCKS;

运行以上SQL语句,可以查看当前数据库中的锁信息。

通过分析返回结果,可以了解哪些锁正在被持有,以及锁的持有者和锁的类型。

step3:查看当前的死锁信息

SHOW ENGINE INNODB STATUS;

运行以上SQL语句,可以显示当前的InnoDB存储引擎的状态信息。

其中包括死锁检测结果。如果存在死锁,可以通过分析该信息来解决死锁问题。

11.2 终止死锁事务

一旦发现死锁,需要找到造成死锁的事务,并选择其中一个事务终止。可以根据事务的执行时间、影响行数、优先级等因素进行终止决策。

可以采取以下方法来解决死锁问题:

  • 回滚事务:
  • 使用以下命令回滚某个事务以解除死锁:
  • ROLLBACK;
     
  • 杀死进程:
  • 使用以下命令查找引起死锁的进程:
  • SHOW PROCESSLIST;
     
  • 找到引起死锁的进程ID后,使用以下命令杀死该进程:
  • KILL <process_id>;

11.3 重试事务

终止死锁事务后,需要重新执行被终止的事务。

重试事务 之前,需要调整事务顺序

这可能需要一些逻辑处理,例如对数据进行回滚或者重新执行一些操作。

▌11.4 防止死锁再次发生

通过数据库的日志和监控信息,分析死锁的原因。

可以根据死锁原因对数据库的设计和代码进行优化,以尽量减少死锁的发生。

根据分析结果,针对性地进行数据库结构调整、索引优化、事务隔离级别调整等措施,以降低死锁的概率

  • 12
    点赞
  • 9
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值