如何解决MySQL中的死锁问题?

数据库 MySQL
虽然锁在一定程度上能够解决并发问题,但稍有不慎,就可能造成死锁。本文介绍死锁的产生及处理。

[[430124]]

本文转载自微信公众号「大数据DT」,作者肖宇 冰河 。转载本文请联系大数据DT公众号。

1 死锁的产生和预防

发生死锁的必要条件有4个,分别为互斥条件、不可剥夺条件、请求与保持条件和循环等待条件,如图1-6所示。

▲图1-6 死锁的必要条件

1. 互斥条件

在一段时间内,计算机中的某个资源只能被一个进程占用。此时,如果其他进程请求该资源,则只能等待。

2. 不可剥夺条件

某个进程获得的资源在使用完毕之前,不能被其他进程强行夺走,只能由获得资源的进程主动释放。

3. 请求与保持条件

进程已经获得了至少一个资源,又要请求其他资源,但请求的资源已经被其他进程占有,此时请求的进程就会被阻塞,并且不会释放自己已获得的资源。

4. 循环等待条件

系统中的进程之间相互等待,同时各自占用的资源又会被下一个进程所请求。例如有进程A、进程B和进程C三个进程,进程A请求的资源被进程B占用,进程B请求的资源被进程C占用,进程C请求的资源被进程A占用,于是形成了循环等待条件,如图1-7所示。

▲图1-7 死锁的循环等待条件

需要注意的是,只有4个必要条件都满足时,才会发生死锁。

处理死锁有4种方法,分别为预防死锁、避免死锁、检测死锁和解除死锁,如图1-8所示。

▲图1-8 处理死锁的方法

  • 预防死锁:处理死锁最直接的方法就是破坏造成死锁的4个必要条件中的一个或多个,以防止死锁的发生。
  • 避免死锁:在系统资源的分配过程中,使用某种策略或者方法防止系统进入不安全状态,从而避免死锁的发生。
  • 检测死锁:这种方法允许系统在运行过程中发生死锁,但是能够检测死锁的发生,并采取适当的措施清除死锁。
  • 解除死锁:当检测出死锁后,采用适当的策略和方法将进程从死锁状态解脱出来。

在实际工作中,通常采用有序资源分配法和银行家算法这两种方式来避免死锁,大家可自行了解。

2 MySQL中的死锁问题

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

接下来,我们看一个MySQL中的死锁案例。

第一步:打开终端A,登录MySQL,将事务隔离级别设置为可重复读,开启事务后为account数据表中id为1的数据添加排他锁,如下所示。

  1. mysql> set session transaction isolation level repeatable read
  2. Query OK, 0 rows affected (0.00 sec) 
  3.  
  4. mysql> start transaction
  5. Query OK, 0 rows affected (0.00 sec) 
  6.  
  7. mysql> select * from account where id =1 for update
  8. +----+--------+---------+ 
  9. | id | name   | balance | 
  10. +----+--------+---------+ 
  11. |  1 | 张三   |     300 | 
  12. +----+--------+---------+ 
  13. 1 row in set (0.00 sec) 

第二步:打开终端B,登录MySQL,将事务隔离级别设置为可重复读,开启事务后为account数据表中id为2的数据添加排他锁,如下所示。

  1. mysql> set session transaction isolation level repeatable read
  2. Query OK, 0 rows affected (0.00 sec) 
  3.  
  4. mysql> start transaction
  5. Query OK, 0 rows affected (0.00 sec) 
  6.  
  7. mysql> select * from account where id =2 for update
  8. +----+--------+---------+ 
  9. | id | name   | balance | 
  10. +----+--------+---------+ 
  11. |  2 | 李四   |     350 | 
  12. +----+--------+---------+ 
  13. 1 row in set (0.00 sec) 

第三步:在终端A为account数据表中id为2的数据添加排他锁,如下所示。

  1. mysql> select * from account where id =2 for update

此时,线程会一直卡住,因为在等待终端B中id为2的数据释放排他锁。

第四步:在终端B中为account数据表中id为1的数据添加排他锁,如下所示。

  1. mysql> select * from account where id =1 for update
  2. ERROR 1213 (40001): Deadlock found when trying to get lock; try restarting transaction 

此时发生了死锁。通过如下命令可以查看死锁的日志信息。

  1. show engine innodb status\G 

通过命令行查看LATEST DETECTED DEADLOCK选项相关的信息,可以发现死锁的相关信息,或者通过配置innodb_print_all_deadlocks(MySQL 5.6.2版本开始提供)参数为ON,将死锁相关信息打印到MySQL错误日志中。

在MySQL中,通常通过以下几种方式来避免死锁。

  1. 尽量让数据表中的数据检索都通过索引来完成,避免无效索引导致行锁升级为表锁。
  2. 合理设计索引,尽量缩小锁的范围。
  3. 尽量减少查询条件的范围,尽量避免间隙锁或缩小间隙锁的范围。
  4. 尽量控制事务的大小,减少一次事务锁定的资源数量,缩短锁定资源的时间。
  5. 如果一条SQL语句涉及事务加锁操作,则尽量将其放在整个事务的最后执行。
  6. 尽可能使用低级别的事务隔离机制。

关于作者:肖宇,分布式事务架构专家,Apache ShenYu(incubating)网关创始人,Dromara开源组织创始人,Hmily、RainCat、Myth等分布式事务框架的作者。Apache ShardingSphere Committer。

冰河,互联网高级技术专家、MySQL技术专家、分布式事务架构专家。多年来,一直致力于分布式系统架构、微服务、分布式数据库、分布式事务与大数据技术的研究,在高并发、高可用、高可扩展性、高可维护性和大数据等领域拥有丰富的架构经验。

 

本文摘编自《深入理解分布式事务:原理与实战》,经出版方授权发布。

 

责任编辑:武晓燕 来源: 大数据DT
相关推荐

2010-04-29 17:46:31

Oracle死锁

2024-01-10 09:44:11

MySQL死锁

2021-06-08 08:38:36

MySQL数据库死锁问题

2023-10-30 18:35:47

MySQL主从延时

2011-08-08 10:29:12

MySQL

2011-06-16 14:12:30

Qt Mysql 驱动

2024-06-21 09:37:57

2017-09-28 10:40:10

深度学习多体问题多代理系统

2017-09-23 22:07:24

深度学习N 体问题GAN

2012-09-05 11:09:15

SELinux操作系统

2019-11-05 14:00:23

Windows 10Outlook附件

2017-10-17 09:21:06

2010-10-08 11:41:38

PHP连接MYSQL

2021-09-26 06:43:07

MySQL深分页优化

2021-09-27 13:33:03

MySQL深分页数据库

2021-11-09 10:20:15

MySQL深分页数据库

2009-09-21 17:10:14

struts Hibe

2011-03-15 13:30:27

IBatis.netMySQL

2010-03-15 11:07:13

Python多线程

2018-01-03 08:42:40

Linux命令磁盘空间
点赞
收藏

51CTO技术栈公众号