MySQL死锁原理及如何定位和解决?

什么是死锁

死锁是指两个或者多个事务在同一资源上相互占用,并请求锁定对方占用的资源,从而导致恶性循环的现象。当多个事务试图以不同的顺序锁定资源时,就可能会产生死锁。多个事务同时锁定同一个资源时,也会产生死锁。例如,设想下面两个事务同时处理StockPrice表:

事务1

START TRANSACTION;
UPDATE StockPrice SET close = 45.50 WHERE stock_id = 4 and date = '2002-05-01';
UPDATE StockPrice SET close = 19.80 WHERE stock_id = 3 and date = '2002-05-02';
COMMIT;

事务2

 START TRANSACTION;
UPDATE StockPrice SET high = 20.12 WHERE stock_id = 3 and date = '2002-05-02';
UPDATE StockPrice SET high = 47.20 WHERE stock_id = 4 and date = '2002-05-01';
COMMIT;

如果凑巧,两个事务都执行了第一条UPDATE语句,更新了一行数据,同时也锁定了该行数据,接着每个事务都尝试去执行第二条UPDATE语句,却发现该行已经被对方锁定,然后两个事务都等待对方释放锁,同时又持有对方需要的锁,则陷入死循环。除非有外部因素介入才可能解除死锁。

为了解决这种问题,数据库系统实现了各种死锁检测和死锁超时机制。越复杂的系统,比如InnoDB存储引擎,越能检测到死锁的循环依赖,并立即返回一个错误。这种解决方式很有效,否则死锁会导致出现非常慢的查询。还有一种解决方式,就是当查询的时间达到锁等待超时的设定后放弃锁请求,这种方式通常来说不太好。InnoDB目前处理死锁的方法是,将持有最少行级排他锁的事务进行回滚(这是相对比较简单的死锁回滚算法)。

锁的行为和顺序是和存储引擎相关的。以同样的顺序执行语句,有些存储引擎会产生死锁,有些则不会。死锁的产生有双重原因:有些是因为真正的数据冲突,这种情况通常很难避免,但有些则完全是由于存储引擎的实现方式导致的。

死锁发生以后,只有部分或者完全回滚其中一个事务,才能打破死锁。对于事务型的系统,这是无法避免的,所以应用程序在设计时必须考虑如何处理死锁。大多数情况下只需要重新执行因死锁回滚的事务即可。

如何避免死锁

  1. 类似的业务逻辑以固定的顺序访问表和行(基本上是可以解决大部分的问题,比如转账,所有的功能接口都是先减钱再加钱)
  2. 大事务拆小,大事务更倾向于死锁,如果业务允许,将大事务拆小
  3. 在同一个事务中,尽可能做到一次锁定所需要的所有资源,减少死锁概率
  4. 降低隔离级别,如果业务允许,将隔离级别调低也是较好的选择(有点扯淡,但是也管用.)
    5 为表添加合理的索引,可以看到如果不走索引将会为表的每一行记录添加上锁(或者说是表锁)
    不加索引的事务语句拿到的锁都是表锁,表锁级别大,死锁概率就高了.

查看死锁日志

死锁场景:
事务A操作 select * from table where id =1 for update
事务B操作 select * from table where id =2 for update
事务A操作 select * from table where id =2 for update
事务B操作 select * from table where id =1 for update
此时就卡住了事务A等着事务B释放锁,事务B等着事务A释放锁.
解决办法很简单,就是找DBA看一下死锁日志就可以了,然后根据对应的SQL,找到对应的代码去改一下就可以了.
show engine innodb status 命令是可以查看死锁的信息的

如何阅读MySQL的死锁日志

https://www.yuque.com/docs/share/016b2375-68b1-4ad8-92e8-62eb71b24792?# 《MySQL如何阅读死锁日志》

死锁详细介绍

https://www.yuque.com/docs/share/e437b034-6813-4171-be8b-712279d7865c?# 《记一次线上SQL死锁事故:如何避免死锁?》

猜你喜欢

转载自blog.csdn.net/qq_41489540/article/details/113795080