出现死锁时的处理
- 一种策略是,直接进入等待,直到超时。这个超时时间可以通过参数
innodb_lock_wait_timeout来设置。innodb_lock_wait_timeout的默认值是 50s。 - 另一种策略是,发起死锁检测,发现死锁后,主动回滚死锁链条中的某一个事务,让其他事务得以继续执行。将参数
innodb_deadlock_detect设置为on,表示开启这个逻辑。
死锁检测导致 CPU 大量消耗
每当一个事务被锁的时候,就要看看它所依赖的线程有没有被别人锁住,如此循环,最后判断是否出现了循环等待,也就是死锁。
那如果是我们上面说到的所有事务都要更新同一行的场景呢?
每个新来的被堵住的线程,都要判断会不会由于自己的加入导致了死锁,这是一个时间复杂度是 O(n)的操作。假设有 1000 个并发线程要同时更新同一行,那么死锁检测操作就是 100 万这个量级的。虽然最终检测的结果是没有死锁,但是这期间要消耗大量的 CPU 资源。因此,你就会看到 CPU 利用率很高,但是每秒却执行不了几个事务。
降低死锁检测 CPU 消耗的方法
-
临时把死锁检测关掉。但是这种操作本身带有一定的风险,因为业务设计的时候一般不会把死锁当做一个严重错误,毕竟出现死锁了,就回滚,然后通过业务重试一般就没问题了,这是业务无损的。而关掉死锁检测意味着可能会出现大量的超时,这是业务有损的。
-
控制数据库服务端并发度。控制客户端的并发度是不可行的,否则客户端较多后,汇总到数据库的并发数也非常高。所以并发控制要在数据库服务端进行。如果有中间件,可以使用中间件实现;或者修改 MySQL 源码来控制。
-
将一行改为逻辑的多行实现。例如,购买电影票,电影院的账户总额要增加。可以将账户总额拆分为 10 行,向电影院添加数据时,随机选择一行添加,从而降低了冲突的概率。但是,并不是无损的,毕竟如果要退票的话,数据不能为 0.