死锁
死锁的必要条件
- 多个并发事务(2个或者以上)。
- 每个事务都持有锁(或者是已经在等待锁)。
- 每个事务都需要再继续持有锁(为了完成事务逻辑,还必须更新更多的行)。
- 事务之间产生加锁的循环等待,形成死锁。
总结:当两个或多个事务相互持有对方需要的锁时,就会产生死锁。
死锁的检测(8.0版本增加)
当死锁检测启用时(默认),InnoDB会自动检测事务死锁并回滚一个或多个事务来打破死锁。InnoDB尝试选择小事务进行回滚,其中事务的大小由插入、更新或删除的行数决定。
InnoDB在innodb_table_locks = 1(默认值)和autocommit = 0时知道表锁,它上面的MySQL层知道行锁。否则,InnoDB无法检测到由MySQL锁表语句设置的表锁,或由InnoDB以外的存储引擎设置的锁。通过设置innodb_lock_wait_timeout系统变量的值来解决这些情况。
如果InnoDB监视器输出的最新检测到的死锁部分包含一条消息,“在锁表等待图中搜索太深或太长,我们将在事务之后回滚”,这表明等待列表中的事务数量已经达到了200的上限。超过200个事务的等待列表被视为死锁,试图检查等待列表的事务被回滚。如果锁定线程必须查看等待列表中事务拥有的超过1,000,000个锁,也可能会发生同样的错误。
死锁检测机制 - wait-for graph
核心就是数据库会把事务单元锁维持的锁和它所等待的锁都记录下来,Innodb提供了wait-for graph算法来主动进行死锁检测,每当加锁请求无法立即满足需要进入等待时,wait-for graph算法都会被触发。当数据库检测到两个事务不同方向地给同一个资源加锁(产生循序),它就认为发生了死锁,触发wait-for graph算法。比如,事务1给A加锁,事务2给B加锁,同时事务1给B加锁(等待),事务2给A加锁就发生了死锁。那么死锁解决办法就是终止一边事务的执行即可,这种效率一般来说是最高的,也是主流数据库采用的办法。

Innodb目前处理死锁的方法就是将持有最少行级排他锁的事务进行回滚。这也是相对比较简单的死锁回滚方式。死锁发生以后,只有部分或者完全回滚其中一个事务,才能打破死锁。对于事务型的系统,这是无法避免的,所以应用程序在设计必须考虑如何处理死锁。大多数情况下只需要重新执行因死锁回滚的事务即可。
wait-for graph原理
我们怎么知道图中四辆车是死锁的?

他们相互等待对方的资源,而且形成环路!我们将每辆车看为一个节点,当节点1需要等待节点2的资源时,就生成一条有向边指向节点2,最后形成一个有向图。我们只要检测这个有向图是否出现环路即可,出现环路就是死锁!这就是wait-for graph算法。

Innodb将各个事务看为一个个节点,资源就是各个事务占用的锁,当事务1需要等待事务2的锁时,就生成一条有向边从1指向2,最后行成一个有向图。
禁用死锁检测
在高并发性系统中,当多个线程等待同一锁时,死锁检测可能导致速度下降。有时,禁用死锁检测并依赖于innodb_lock_wait_timeout设置在发生死锁时执行事务回滚可能更有效。可以使用innodb_deadlock_detect配置选项禁用死锁检测。
避免死锁
死锁是事务性数据库中的一个典型问题,但它们并不危险,除非它们非常频繁以至于您根本无法运行某些事务。通常,您必须编写应用程序,以便在事务因死锁而回滚时,它们始终准备重新发出事务。
InnoDB使用自动行级锁定。即使在只插入或删除单行的事务中,也会出现死锁。这是因为这些操作并不是真正的“原子”操作;它们自动设置插入或删除行的索引记录(可能有几个)的锁。
你可以使用以下技巧来处理死锁,并降低发生死锁的可能性:
- 在任何时候,发出显示引擎INNODB状态命令来确定最近死锁的原因。这可以帮助您优化应用程序以避免死锁。如果经常出现死锁警告,那么可以通过启用innodb_print_all_deadlocks配置选项来收集更多的调试信息。关于每个死锁的信息,而不仅仅是最近的死锁,都记录在MySQL错误日志中。完成调试后禁用此选项。
- 对索引加锁顺序的不一致很可能会导致死锁,所以如果可以,尽量以相同的顺序来访问索引记录和表。在程序以批量方式处理数据的时候,如果事先对数据排序,保证每个线程按固定的顺序来处理记录,也可以大大降低出现死锁的可能;
- Gap 锁往往是程序中导致死锁的真凶,由于默认情况下 MySQL 的隔离级别是 RR,所以如果能确定幻读和不可重复读对应用的影响不大,可以考虑将隔离级别改成 RC,可以避免 Gap 锁导致的死锁;
- 为表添加合理的索引,如果不走索引将会为表的每一行记录加锁,死锁的概率就会大大增大;
- 我们知道 MyISAM 只支持表锁,它采用一次封锁技术来保证事务之间不会发生死锁,所以,我们也可以使用同样的思想,在事务中一次锁定所需要的所有资源,减少死锁概率;
- 避免大事务,尽量将大事务拆成多个小事务来处理;因为大事务占用资源多,耗时长,与其他事务冲突的概率也会变高;
- 避免在同一时间点运行多个对同一表进行读写的脚本,特别注意加锁且操作数据量比较大的语句;我们经常会有一些定时脚本,避免它们在同一时间点运行;
- 禁用死锁检测:使用innodb_deadlock_detect配置选项禁用死锁检测。
- 设置锁等待超时参数:innodb_lock_wait_timeout,这个参数并不是只用来解决死锁问题,在并发访问比较高的情况下,如果大量事务因无法立即获得所需的锁而挂起,会占用大量计算机资源,造成严重性能问题,甚至拖跨数据库。我们通过设置合适的锁等待超时阈值,可以避免这种情况发生。InnoDB事务在放弃前等待行锁的时间(秒)。innodb_lock_wait_timeout默认值为50秒。当有试图访问被另一行锁定的行的事务InnoDB事务在发出以下错误:
ERROR 1205 (HY000): Lock wait timeout exceeded; try restarting transaction
当发生锁等待超时时,将回滚当前语句 (而不是整个事务)。要回滚整个事务,请使用innodb_rollback_on_timeout开启值为:ON(默认值OFF)。
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· Manus重磅发布:全球首款通用AI代理技术深度解析与实战指南
· 被坑几百块钱后,我竟然真的恢复了删除的微信聊天记录!
· 没有Manus邀请码?试试免邀请码的MGX或者开源的OpenManus吧
· 【自荐】一款简洁、开源的在线白板工具 Drawnix
· 园子的第一款AI主题卫衣上架——"HELLO! HOW CAN I ASSIST YOU TODAY