事务的隔离级别
提到事务,我们肯定会想到 ACID(Atomicity、Consistency、Isolation、Durability,即原子性、一致性、隔离性、持久性),今天我们来说说其中 I,也就是隔离性。
当数据库上有多个事务同时执行的时候,就可能出现脏读(dirty read)、不可重复读(non-repeatable read)、幻读(phantom read)的问题,为了解决这些问题,就有了隔离级别的概念。
SQL 标准的事务隔离级别包括:读未提交(read uncommitted)、读提交(read committed)、可重复读(repeatable read)和串行化(serializable )。
读未提交
读未提交是指,一个事务还没提交时,它做的变更就能被别的事务看到。
事例:老板要给你工资,你的工资是1万/月。但是发工资时老板不小心按错了数字,按成2万/月,该钱已经打到你的账户,但是事务还没有提交,就在这时,你去查看自己这个月的工资,发现比往常多了1万元,以为涨工资了非常高兴。但是老板及时发现了不对,马上回滚差点就提交了的事务,将数字改成1万再提交。
分析:实际你这个月的工资还是1万,但是你看到的是2万。你看到的是老板还没提交事务时的数据。这就是脏读。
那怎么解决脏读呢?Read committed!读提交,能解决脏读问题。
读已提交
读提交是指,一个事务提交之后,它做的变更才会被其他事务看到。
事例:你拿着信用卡去享受生活(卡里当然是只有1万),当你买单时(你这个事务开启),收费系统事先检测到你的卡里有1万,就在这个时候你的妻子要把钱全部转出充当家用,并提交。当收费系统准备扣款时,再检测卡里的金额,发现已经没钱了(第二次检测金额当然要等待妻子转出金额事务提交完)。你就会很郁闷,明明卡里是有钱的。
分析:这就是读提交,若有事务对数据进行更新(UPDATE)操作时,读操作事务要等待这个更新操作事务提交后才能读取数据,可以解决脏读问题。但在这个事例中,出现了一个事务范围内两个相同的查询却返回了不同数据,这就是不可重复读。
那怎么解决可能的不可重复读问题?Repeatable read !
可重复读
可重复读是指,一个事务执行过程中看到的数据,总是跟这个事务在启动时看到的数据是一致的。当然在可重复读隔离级别下,未提交变更对其他事务也是不可见的。
事例:你拿着信用卡去享受生活(卡里当然是只有1万),当你买单时(事务开启,不允许其他事务的UPDATE修改操作),收费系统事先检测到你的卡里有1万。这个时候你的妻子不能转出金额了。接下来收费系统就可以扣款了。
分析:重复读可以解决不可重复读问题。写到这里,应该明白的一点就是,不可重复读对应的是修改,即UPDATE操作。但是可能还会有幻读问题。因为幻读问题对应的是插入INSERT操作,而不是UPDATE操作。
什么时候会出现幻读?
事例:某一天你去消费,花了2千元,然后你的妻子去查看你今天的消费记录(妻子事务开启),看到确实是花了2千元,就在这个时候,你花了5千买了一部电脑,即新增INSERT了一条消费记录,并提交。当妻子打印你的消费记录清单时(妻子事务提交),发现花了7千元,似乎出现了幻觉,这就是幻读。
那怎么解决幻读问题?Serializable!
串行化
串行化,顾名思义是对于同一行记录,写会加写锁,读会加读锁。当出现读写锁冲突的时候,后访问的事务必须等前一个事务执行完成,才能继续执行。
事务隔离的实现
在 MySQL 中,实际上每条记录在更新的时候都会同时记录一条回滚操作。记录上的最新值,通过回滚操作,都可以得到前一个状态的值。
假设一个值从 a 被按顺序改成了 b、c、d,在回滚日志里面就会有类似下面的记录。
当前值是d,但是在查询这条记录的时候,不同时刻启动的事务会有不同的 视图。如图中看到的在视图 1、2、3 里面,这一个记录的值分别是 a、b、d,同一条记录在系统中可以存在多个版本,就是数据库的多版本并发控制(MVCC)。对于视图1,要得到a,就必须将当前值依次执行图中所有的回滚操作得到。同时你会发现,即使现在有另外一个事务正在将d 改成e,这个事务跟视图1、2、3对应的事务是不会冲突的。