序言
上一篇文章我们讨论了未提交读以及它的使用场景,这篇文章我们介绍提交读,提交读是非常流行的事务级别,PostgreSQL, SQL Server 2012 等数据库的默认事务级别都是它,大多数情况下它都可以满足你的应用需求。
避免脏读和脏写
提交读可以避免脏读和脏写的问题,使用这个事务级别的话,一个事务无法看到其他未执行完毕的事务数据,在之前的例子中,如果使用未提交读的话,用户 1 在第一次查询的时候,可以查询到 100 元 的余额。因为 ATM 在之前已经增加了账户余额(虽然没有提交)。
但是在提交读中,因为数据库无法读取未执行完毕的事务数据,所以在用户第一次查询的时候,账户余额还是为 0 元。
如何实现提交读
提交读主要防止了脏读和脏写,如何防止脏写呢?大多数数据库会使用行锁:
- 当事务 1 需要修改记录 A 的时候,会先尝试获取记录 A 的锁,如果锁已经被其他事务持有的话,那么事务 1 需要等待其他事务释放锁之后才能修改记录 A。
- 事务执行完成之后会释放锁。
- 同一个记录的锁同时只能被一个事务持有。
使用行锁就能保证在事务执行的过程中,记录不会被其他事务修改。那么如何防止脏读呢?我们当然可以也使用同样的锁机制,当事务需要读取记录的时候先获取同一个锁,读取完毕之后释放。不过这样的效率显然不高,每次读操作需要等待其他事务的读或者写完成之后才能执行。我们可以观察到单纯的读操作并不会影响数据库的记录,一个更好的方法是每行保持两个版本,一个是事务开始前的版本,一个是事务进行中的版本。当其他事务在进行修改的时候,我们依然可以读取数据,不过我们读取的是旧版本的值,仅有当事务完成之后,才会修改为新版本。例如:
- ATM 增加账户余额 100 元,数据库维护了旧余额 0 元以及新余额 100 元。
- 用户 1 第一次查询余额的时候只会查询到旧余额 0 元。
- ATM 更新账户流水成功。
- 用户 1 第二次查询余额的时候查询到新余额 100 元。
提交读的问题
提交读虽然可以解决脏读和脏写的问题,但是却无法解决另外的并发问题例如更新丢失与不可重复读,我们先介绍不可重复读的问题,这个问题是怎么产生的呢?一个新的例子,用户 1 执行的事务在开始和结束的时候都包含了一次账户余额查询。
- 用户 1 开始事务,查询账户余额为 0 元。
- 用户 1 继续执行其他操作,过程中用户 2 执行另外的事务,向同一账户增加了 100 元。
- 用户 1 再次查询账户余额,发现在同一事务中,账户余额居然从 100 元变成 0 元。
不可重复读就是说,在同一个事务对同一个数据的两次读取,会得到两个不同的结果,这种情况有时候是不能被接受的,例如当你需要进行数据库备份,那么这整个备份过程就是一个事务,但是你绝对不希望在执行的过程中查询同一行数据却获得几个不同的结果。
提交读的应用
像本文最开始说的,提交读大多数情况它都可以满足你的应用需求,如果要解决不可重复读的问题,我们需要更强的事务级别,下一篇文章我们会介绍可重复读。