云原生集成开发环境——TitanIDE
通过网页在任何地方更安全、更高效地编码2023-06-27
878
数据库死锁对业务来说是一个非常严重的问题,它一定一定一定是代码的执行流程处理不当造成的。
但是重构庞大的业务代码不是说了就能轻易做到的事情,下面给出了一些方案,由浅入深,告诉大家解决死锁问题的正确之道。
死锁问题产生的原因和条件
死锁问题一般发生在短时间内多个并发任务对同一组表进行修改的场景。
数据库在更新数据时会加锁,以确保不会发生并发写数据。
MySQL 加锁的机制是根据索引情况加锁,可能是行锁,也有可能是间隔锁(详情可查看文档:https://dev.mysql.com/doc/refman/8.0/en/innodb-locks-set.html)
并发修改表A时,只有一个请求能够获取到锁,其余的请求需要等待它处理完成释放锁,才能进行下一轮竞争。
如果此时获取到表A锁的线程T1同时需要修改表B,但表B的锁被线程T2获取,且T2正在等待表A的锁,这就造成了两个线程相互等待,进而死锁。
解决办法
1、给表增加索引
MySQL给表加锁时,锁的范围受索引影响。如果更新表时,where条件恰好有索引覆盖,那么MySQL可以精确地对满足条件的行记录加锁,而不会对一组范围内的数据(甚至是全表)加锁。
给需要更新的表添加索引,覆盖where条件里的字段,能够一定程度上缓解锁的碰撞概率,降低死锁发生次数。
2、使用主键更新表
这个办法与办法1的原理是相同的,也是利用索引让数据库精确地加锁。因为主键天然就是一个索引,所以不需要给表添加额外的索引。
3、缩短事务时长或取消事务
许多数据库框架或库都会提供事务管理机制,在默认情况下,它们采用AOP的方式来切入事务管理行为。
但是,这种事务管理行为是很粗糙的,并且会带来一个问题:如果方法执行的时间相当长,那么这个事务也会持续相当长的时间。事务越长也就意味着锁的碰撞概率越大。
如果开发人员对框架和库足够熟悉,并且对数据库事务有良好的理解,可以考虑手动控制事务,并采用编程式的事务管理方法,让应用程序在需要访问数据库的时候才开启事务。
或者,如果系统不强调瞬态一致性,也没有并发访问数据冲突,那么可以完全取消事务。
4、使用合理的编程范式
123方法只能缓解数据库表锁的碰撞概率,但不是解决死锁问题的根本之道。
死锁问题更深层次的原因是:应用程序无序地访问数据库,流程管控不合理。
一个清晰又整洁的编程范式,即可以有效提高代码的可读性,又可以降低数据库(以及外部服务)的访问次数,达到较高的程序效能。
//1.收集数据
var requiredData = queryFromDatabase()
//2.处理数据
process(requiredData)
//3.持久化处理后的数据
saveToDatabase(requiredData)
另外,尽量不要在循环里访问内数据库,在循环开始前收集所有必要的信息,在循环结束后统一持久化到数据库。
5、使用缓存处理热度数据
RMDB处理密集小事务的能力并不弱,但是如果并发处理的数据有重合,就会存在死锁的隐患。如果要解决RMDB在这方面的问题,就要投入不少精力,优化代码流程,精细化控制事务。
如果使用缓存来处理热度数据,可以巧妙地避开RMDB在这方面的缺陷。
缓存可以使用本地缓存,也可以使用分布式缓存,由系统的设计和架构决定。
6、其他方法
除了上面的5种方法,解决事务死锁问题的办法还有很多,例如:其他高级编程模型、特定的中间件支持,可以开放性思考。但是再多的优化技巧,也不如设计一个高效的程序结构
更多云原生干货文章,请点击查阅>>