Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

fix ch7-transaction: Read Committed #253

Merged
merged 1 commit into from
Jul 30, 2022
Merged
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 5 additions & 5 deletions ch7.md
Original file line number Diff line number Diff line change
Expand Up @@ -233,7 +233,7 @@ SELECT COUNT(*)FROM emails WHERE recipient_id = 2 AND unread_flag = true
最基本的事务隔离级别是 **读已提交(Read Committed)**[^v],它提供了两个保证:

1. 从数据库读时,只能看到已提交的数据(没有 **脏读**,即 dirty reads)。
2. 写入数据库时,只会覆盖已经写入的数据(没有 **脏写**,即 dirty writes)。
2. 写入数据库时,只会覆盖已经提交的数据(没有 **脏写**,即 dirty writes)。

我们来更详细地讨论这两个保证。

Expand Down Expand Up @@ -262,7 +262,7 @@ SELECT COUNT(*)FROM emails WHERE recipient_id = 2 AND unread_flag = true

通过防止脏写,这个隔离级别避免了一些并发问题:

- 如果事务更新多个对象,脏写会导致不好的结果。例如,考虑 [图 7-5](img/fig7-5.png),以一个二手车销售网站为例,Alice 和 Bob 两个人同时试图购买同一辆车。购买汽车需要两次数据库写入:网站上的商品列表需要更新,以反映买家的购买,销售发票需要发送给买家。在 [图 7-5](img/fig7-5.png) 的情况下,销售是属于 Bob 的(因为他成功更新了商品列表),但发票却寄送给了爱丽丝(因为她成功更新了发票表)。读已提交会阻止这样的事故
- 如果事务更新多个对象,脏写会导致不好的结果。例如,考虑 [图 7-5](img/fig7-5.png),以一个二手车销售网站为例,Alice 和 Bob 两个人同时试图购买同一辆车。购买汽车需要两次数据库写入:网站上的商品列表需要更新,以反映买家的购买,销售发票需要发送给买家。在 [图 7-5](img/fig7-5.png) 的情况下,销售是属于 Bob 的(因为他成功更新了商品列表),但发票却寄送给了Alice(因为她成功更新了发票表)。读已提交会防止这样的事故
- 但是,读已提交并不能防止 [图 7-1](img/fig7-1.png) 中两个计数器增量之间的竞争状态。在这种情况下,第二次写入发生在第一个事务提交后,所以它不是一个脏写。这仍然是不正确的,但是出于不同的原因,在 “[防止更新丢失](#防止丢失更新)” 中将讨论如何使这种计数器增量安全。

![](img/fig7-5.png)
Expand All @@ -275,13 +275,13 @@ SELECT COUNT(*)FROM emails WHERE recipient_id = 2 AND unread_flag = true

最常见的情况是,数据库通过使用 **行锁(row-level lock)** 来防止脏写:当事务想要修改特定对象(行或文档)时,它必须首先获得该对象的锁。然后必须持有该锁直到事务被提交或中止。一次只有一个事务可持有任何给定对象的锁;如果另一个事务要写入同一个对象,则必须等到第一个事务提交或中止后,才能获取该锁并继续。这种锁定是读已提交模式(或更强的隔离级别)的数据库自动完成的。

如何防止脏读?一种选择是使用相同的锁,并要求任何想要读取对象的事务来简单地获取该锁,然后在读取之后立即再次释放该锁。这将确保在对象具有脏的、未提交的值时不会发生读取(因为在此期间,锁将由进行写入的事务持有)
如何防止脏读?一种选择是使用相同的锁,并要求任何想要读取对象的事务来简单地获取该锁,然后在读取之后立即再次释放该锁。这将确保在对象具有脏的、未提交的值时不会发生读取因为在此期间,锁将由进行写入的事务持有

但是要求读锁的办法在实践中效果并不好。因为一个长时间运行的写入事务会迫使许多只读事务等到这个慢写入事务完成。这会损失只读事务的响应时间,并且不利于可操作性:因为等待锁,应用某个部分的迟缓可能由于连锁效应,导致其他部分出现问题。
但是要求读锁的办法在实践中效果并不好。因为一个长时间运行的写入事务会迫使许多只读事务等到这个慢写入事务完成。这会影响只读事务的响应时间,并且不利于可操作性:因为等待锁,应用某个部分的迟缓可能由于连锁效应,导致其他部分出现问题。

出于这个原因,大多数数据库 [^vi] 使用 [图 7-4](img/fig7-4.png) 的方式防止脏读:对于写入的每个对象,数据库都会记住旧的已提交值,和由当前持有写入锁的事务设置的新值。当事务正在进行时,任何其他读取对象的事务都会拿到旧值。 只有当新值提交后,事务才会切换到读取新值。

[^vi]: 在撰写本文时,唯一在读已提交隔离级别使用读锁的主流数据库是使用 `read_committed_snapshot = off` 配置的 IBM DB2 和 Microsoft SQL Server 【23,36】。
[^vi]: 在撰写本文时,唯一在读已提交隔离级别使用读锁的主流数据库是 IBM DB2 和使用 `read_committed_snapshot = off` 配置的 Microsoft SQL Server【23,36】。

### 快照隔离和可重复读

Expand Down