如果两个事务操作的是不同的数据,即不存在数据依赖关系,则它们可以安全地并行执行。只有出现某个事务修改数据而另一个事务同时要读取,或者两个事务同时修改相同数据时,才会引发并发问题。
潜在的并发问题
- 脏写:客户端覆盖了另一个客户端尚未提交的写入。
- 脏读:客户端读到了其他客户端尚未提交的写入。
- 读倾斜(不可重复读):客户端在不同的时间点看到了不同的值。
- 更新丢失
- 写倾斜
- 幻读
可串行化的隔离级别会严重影响性能,而许多数据库却不愿意牺牲性能,因而更多倾向于采用较弱的隔离级别,它可以防止某些但并非全部的并发问题。
弱隔离级别
读-提交
读提交是最基本的事务隔离级别,它只提供两个保证:即防止脏读和脏写。
实现
可以通过行锁防止脏写。
防止脏读有两种方式,最简单的方法是和防止脏写一样,使用锁,但是这样性能太差。因此大多数数据库,对于每个待更新的对象,都维护其旧值和当前持有锁事务将要设置的新值两个版本。在事务提交之前,所有其他读操作都读取其旧值;仅当写事务提交后,才会切换到读取新值。
快照级别隔离与可重复读
在一些需要保证数据一致性的场景中,读-提交隔离级别依然会存在问题。
假设在一个读事务中,对一个对象进行多次读取,如果在此期间,有其他的写事务,更新这个对象,那么这个读事务会在不同的时间读到对象不同的值。这种现象叫做不可重复读(nonrepeatable read)或者读倾斜(read skew)。
除了不可重复读,还存在一些场景,仅通过读-提交隔离是会存在问题的。
备份场景:备份任务要复制整个数据库,这可能需要数小时完成。在备份过程中,可以继续写入数据库。因此,得到的镜像里可能包含部分旧版本数据和部分新版本数据。如果从这样的备份进行恢复,最终就导致了数据的不一致。
这类场景,需要在读事务开启时,保存一份数据库的快照,这样才能保证事务执行过程中的数据一致性。
快照级别隔离可以解决上述两个问题。
实现
为了解决上述两个问题,数据库采用了类似于防止脏读但却更为通用的机制,考虑到多个正在进行的事务可能会在不同的时间点查看数据库状态,所以数据库保留了对象多个不同的提交版本,这种技术因此也被称为多版本并发控制(MVCC)。
如果只是为了提供读-提交级别隔离,只需要保留对象的两个版本就够了。所以,支持快照隔离级别的存储引擎往往直接采用MVCC来实现读-提交隔离,典型做法是在读-提交级别下,对每一个不同的擦汗寻单独创建一个快照,而在快照级别隔离则是使用一个快照来运行整个事务。