程序人生 A log of my life

MySQL

MySQL如果使用事务,常用Innodb引擎。

事务隔离

Innodb的事务缺省隔离级别是REPEATABLE_READ,这个意思是可重复的读,也就是在事务中,任何读取操作都是可重复的,不会因为其他事务的提交而发生变化。同时还包括了几个其他意思:

  • 不会读取其他未提交事务的数据(脏数据)
  • 事务里的第一个读取语句会建立整个数据库的快照(snapshot),后续的读取都基于这个快照。

上面的第二条非常重要,意味这个如果两个事务同时开启,A事务和B事务,如果B事务修改了数据并且提交,A能否在事务中读取到这个数据,取决于A事务是否已经执行了一条select语句,如果有select执行,则读取不到B事务提交的修改数据,否则可以读到,这个特性对并发设计非常重要。

并发

基于上面的缺省事务隔离机制,在并发时,很多应用并不是缺省缺省就高枕无忧了,比如最经典的场景,用户扣款,如果两个线程同时触发扣款,考虑下面的流程,最终的结果就是错误的:

  • A事务开始
  • A事务读取余额
  • B事务开始
  • B事务读取余额
  • A事务校验余额,并扣款保存
  • A事务提交
  • B事务校验余额,并扣款保存
  • B事务提交

最终的结果是,扣款只扣了一次,要解决这个问题有很多方法,我比较推荐的方法是,在读取余额时使用 for update限定,这样上述流程中B事务读取余额会被锁定,直到A提交完毕,并且加了for update之后,B事务可以读取到最新的修改后的数据,不受快照的约束,也就是整个流程会变成:

  • A事务开始
  • A事务读取余额
  • B事务开始
  • A事务校验余额,并扣款保存
  • A事务提交
  • B事务读取余额
  • B事务校验余额,并扣款保存
  • B事务提交