本文的所有内容基于 mysql InnoDB 和 sequelize。
问题
多个并发的事务对同一行数据进行更新,且更新的数据是基于这一行数据更新前的数据计算的结果,造成了此行数据更新的问题。
事务与锁简述
mysql 本身并不具有事务,事务是 InnoDB 引擎所有的功能,事务的隔离级别分为四种:
1、READ_UNCOMMITTED:脏读,一个事务能读到另一个事务未提交的数据,事务的隔离级别最低。
2、READ_COMMITTED:不可重复读,一个事务对一行数据进行更新的过程中,另一个事务对同一行数据进行读取,会在此行数据更新提交前后读取到不一致的结果。避免了脏读的情况,隔离级别比脏读略高一级。
3、REPEATABLE_READ:幻读,同一个事务内读取的数据是保证相同的,但当事务非独立执行时仍然会造成读取的结果不一致。默认的事务隔离级别,比不可重复读高一级。
4、SERIALIZABLE:序列化,事务的隔离级别最高,避免了上述的问题。
两种锁:
1、共享锁:读锁,获取共享锁的事务只能读,不能修改数据,多个事务可同时获取共享锁。
2、排他锁:写锁,一个事务获取写锁后可对数据进行读写,但其他事务无法再获取到写锁直到上一个事务完成。
sequelize 示例
解决方式:使用 SERIALIZABLE 事务隔离级别,但这并不够,我们仍然需要保证多个事务并发下读取的原始数据一定是之前事务提交更新之后的数据,因此还需要使用排他锁。
以下图片使用了 async/await 的写法,包含了事务的操作和 lock 锁的使用,仅供参考,sequelize 模型的定义可参考上一篇文章 -- 数据库时间类型数据的处理 ,不必深究具体的业务实现:
需要注意的是,使用排他锁时,如果查询操作不是根据主键或索引,那么会造成表锁,这会对数据库读写性能造成很大的影响,显然这并不是我想要的,我们更需要的是行锁,所以在使用排他锁时,应该使用主键或索引进行操作。
结语
除了在数据库层面上解决这个问题之外,还有另一种方法就是将这些操作同一行数据的并发事务改为串行执行。
另一个问题是 pm2 的集群模式下的并发事务会发生什么呢?