1、数据库死锁示例
| 发生时间编号 | 事务A | 事务B |
|---|---|---|
| 1 | BEGIN; | |
| 2 | BEGIN; | |
| 3 | SELECT * from role where id = 11 for update; | |
| 4 | SELECT * from role where id = 12 for update; | |
| 5 | SELECT * from role where id = 12 for update; | |
| 6 | SELECT * from role where id = 11 for update; |
事务 A 在等待事务 B 释放 id=12 的行锁,而事务 B 在等待事务 A 释放 id=11 的行锁。 事务 A 和事务 B 在互相等待对方的资源释放,就是进入了死锁状态。
数据库返回信息:
SELECT * from role where id = 11 for update> 1213 - Deadlock found when trying to get lock; try restarting transaction> 时间: 0s
2、死锁解决策略
- 直接进入等待,直到超时(超时时间可以通过参数
** innodb_lock_wait_timeout **来设置,默认50S) - 发起死锁检测,发现死锁后,主动回滚死锁链条中的某一个事务,让其他事务得以继续执行(参数
**innodb_deadlock_detect **设置为 on,表示开启死锁检测,数据库默认开启死锁检测)
正常情况下采用第二种策略,第一个被锁住的线程要过 50s 才会超时退出,然后其他线程才有可能继续执行。对于在线服务来说,这个等待时间往往是无法接受的。
主动死锁检测在发生死锁的时候,是能够快速发现并进行处理的,但是它也是有额外负担的。每当一个事务被锁的时候,就要看看它所依赖的线程有没有被别人锁住,如此循环,最后判断是否出现了循环等待,也就是死锁。当并发量很高时,死锁的检测要消费大量的CPU资源。
热点行更新导致的性能问题
每个新来的被堵住的线程,都要判断会不会由于自己的加入导致了死锁,这是一个时间复杂度是 O(n) 的操作。假设有 1000 个并发线程要同时更新同一行,那么死锁检测操作就是 100 万这个量级的。虽然最终检测的结果是没有死锁,但是这期间要消耗大量的 CPU 资源。因此,你就会看到 CPU 利用率很高,但是每秒却执行不了几个事务。
怎么解决由这种热点行更新导致的性能问题呢?
1、如果能确保这个业务一定不会出现死锁,可以临时把死锁检测关掉。
2、控制并发度
- 在客户端做并发控制
- 中间件实现并发控制
- 修改 MySQL 源码,对于相同行的更新,在进入引擎之前排队
- 将一行改成逻辑上的多行来减少锁冲突
