如何保证账户系统中流水和余额数据一致?
账户系统用于记录每个用户的余额,为了保证数据的可追溯性,通常都是需要记录账户流水的。
“对不上账”是一个常见的问题,意思是账户系统中流水和余额数据不一致,它的本质问题是,冗余数据的一致性问题。
那从技术上,保证账户系统中流水和余额数据一致呢?
-->使用数据库事务来保证数据一致性
流水记录只能新增,任何情况下都不允许修改和删除,每次交易的时候需要把流水和余额放在同一个事务中一起更新。
事务具备ACID (原子性、一致性、隔离性和持久性)四种基本特性,也就是 ACID,它可以保证在一个事务中执行的数据更新,要么都成功,要么都失败。并且在事务执行过程中,中间状态的数据对其他事务是不可见的。
ACID 是一种理想情况,特别是要完美地实现 CI,会导致数据库性能严重下降,所以 MySQL 提供的四种可选的隔离级别,牺牲一定的隔离性和一致性(AD:事务的原子性和持久性是必须要保证的),用于换取高性能。这四种隔离级别中,只有 RC 和 RR 这两种隔离级别是常用的,它们的唯一区别是在进行的事务中,其他事务对数据的更新是否可见。
对账户系统和其他大多数交易系统来说,事务的原子性和持久性是必须要保证的,否则就失去了使用事务的意义,而一致性和隔离性其实可以做适当牺牲,来换取性能。
在一个事务执行过程中,它能不能读到其他已提交事务对数据的更新,如果能读到数据变化(在同一个事务内两次读取同一条数据,读到的结果可能会不一样),这就是不可重复读,否则就是“可重复读”。
在会话 A 中开启一个事务,准备插入一条 ID 为 1000 的流水记录。查询一下当前流水,不存在 ID 为 1000 的记录,可以安全地插入数据。这时候,另外一个会话抢先插入了这条 ID 为 1000 的流水记录。然后会话 A 再执行相同的插入语句时,就会报主键冲突错误,但是由于事务的隔离性,它执行查询的时候,却查不到这条 ID 为 1000 的流水,就像出现了“幻觉”一样,这就是幻读。
一种兼顾并发、性能和数据一致性的交易实现
这个实现在隔离级别为 RC 和 RR 时,都是安全的。
1,给账户余额表增加一个 log_id 属性,记录最后一笔交易的流水号。,
2,首先开启事务,查询并记录当前账户的余额和最后一笔交易的流水号。
3,然后写入流水记录。
4,再更新账户余额,需要在更新语句的 WHERE 条件中限定,只有流水号等于之前查询出的流水号时才更新。
5,然后检查更新余额的返回值,如果更新成功(变更的行数等于 1)就提交事务,否则回滚事务。
网友评论