事发现场
报错信息如图,Lock wait timeout exceeded(锁等待超时),第一次遇到的时候,同事通过增加innodb_lock_wait_timeout参数到100s(默认是50s)试图来解决这个问题,情况有所好转。但是好景不长,之后再次抛出该异常,可见之前的解决方式并不完美。在比较深入地学习了mysql的相关课程之后(强烈推荐 MYSQL实战45讲),对该问题有了解决思路。
分析
innodb_deadlock_detect(死锁检测) | innodb_lock_wait_timeout(默认50s) |
---|---|
on(默认开启) | 生效 |
of | 不生效 |
Lock wait timeout的异常存在两种可能性
- 正常的锁等待
- 死锁造成锁等待
判断的依据是,查看数据库参数innodb_deadlock_detect 是否是开启状态,如果是开启状态,那么当发生死锁的时候抛出的异常必然是类似于这样的异常:deadlock when… 所以,如果死锁检测是开启的,那么该异常不会是死锁造成的所等待,如果死锁检测是关闭的,那么是正常的锁等待,必然是有一个长事务存在,占用了锁,长时间没有释放。这里分别对这两种异常场景提供解决思路。
正常的锁等待
该种场景导致的锁获取超时,必然对应了一个长事务。解决思路是缩短加锁到解锁的整个过程。
- 调整sql执行熟悉,将增删改的sql放到最后
- 如果存在多个增删改的sql,将并发最多的操作放到最后
- 优化代码,缩短事务begin到commit的时间
死锁
- 缩短加锁到解锁的整个过程(正常的锁等待的解决方案)
- 开启死锁检测,程序捕获到异常后重试(类似CAS锁,快速失败+重试),缺点是死锁检测会消耗cpu,如果innodb存在大量的事务线程,会导致cpu 100%,每秒处理不了几个事务
- 关闭死锁检测,设置innodb_lock_wait_timeout,捕获到异常后重试,缺点是等待一段时间才抛出异常,响应过慢,业务上是有损的
- 控制事务的并发量(通过客户端或者中间件控制)
- 分库分表,减少冲突