2
0
Fork 0
mirror of https://github.com/Vonng/ddia.git synced 2026-06-21 00:47:05 +08:00

Compare commits

..

No commits in common. "fd372384673a64117716da180d72fa49226a8647" and "f81763bc1e83805b67cbee461c96f767c483cbaf" have entirely different histories.

14 changed files with 54 additions and 58 deletions

View file

@ -151,9 +151,6 @@
| ISSUE & Pull Requests | USER | Title |
| ----------------------------------------------- | ------------------------------------------------------------ | ------------------------------------------------------------ |
| [273](https://github.com/Vonng/ddia/pull/273) | [@Sdot-Python](https://github.com/Sdot-Python) | ch7: 统一了 write skew 的翻译 |
| [271](https://github.com/Vonng/ddia/pull/271) | [@Makonike](https://github.com/Makonike) | ch6: 统一了 rebalancing 的翻译 |
| [270](https://github.com/Vonng/ddia/pull/270) | [@Ynjxsjmh](https://github.com/Ynjxsjmh) | ch7: 修正不一致的翻译 |
| [263](https://github.com/Vonng/ddia/pull/263) | [@zydmayday](https://github.com/zydmayday) | ch5: 修正译文中的重复单词 |
| [260](https://github.com/Vonng/ddia/pull/260) | [@haifeiWu](https://github.com/haifeiWu) | ch4: 修正部分不准确的翻译 |
| [258](https://github.com/Vonng/ddia/pull/258) | [@bestgrc](https://github.com/bestgrc) | ch3: 修正一处翻译错误 |
@ -218,6 +215,7 @@
| [128](https://github.com/Vonng/ddia/pull/128) | [@meilin96](https://github.com/meilin96) | ch5: 修正一处错误的引用 |
| [126](https://github.com/Vonng/ddia/pull/126) | [@cwr31](https://github.com/cwr31) | ch10: 修正一处错误的翻译(功能 -> 函数) |
| [125](https://github.com/Vonng/ddia/pull/125) | [@dch1228](https://github.com/dch1228) | ch2: 优化 how best 的翻译(如何以最佳方式) |
| [124](https://github.com/Vonng/ddia/pull/124) | [@yingang](https://github.com/yingang) | translation updates (chapter 10) |
| [123](https://github.com/Vonng/ddia/pull/123) | [@yingang](https://github.com/yingang) | translation updates (chapter 9, TOC in readme, glossary, etc.) |
| [121](https://github.com/Vonng/ddia/pull/121) | [@yingang](https://github.com/yingang) | translation updates (chapter 5 to chapter 8) |
| [120](https://github.com/Vonng/ddia/pull/120) | [@jiong-han](https://github.com/jiong-han) | Typo fix: 呲之以鼻 -> 嗤之以鼻 |

View file

@ -28,7 +28,7 @@ Web 和越来越多的基于 HTTP/REST 的 API 使交互的请求 / 响应风格
流处理介于在线和离线(批处理)之间,所以有时候被称为 **准实时near-real-time****准在线nearline** 处理。像批处理系统一样,流处理消费输入并产生输出(并不需要响应请求)。但是,流式作业在事件发生后不久就会对事件进行操作,而批处理作业则需等待固定的一组输入数据。这种差异使流处理系统比起批处理系统具有更低的延迟。由于流处理基于批处理,我们将在 [第十一章](ch11.md) 讨论它。
正如我们将在本章中看到的那样批处理是构建可靠、可伸缩和可维护应用程序的重要组成部分。例如2004 年发布的批处理算法 Map-Reduce可能被过分热情地被称为 “造就 Google 大规模可伸缩性的算法”【2】。随后在各种开源数据系统中得到应用包括 HadoopCouchDB 和 MongoDB。
正如我们将在本章中看到的那样批处理是构建可靠、可伸缩和可维护应用程序的重要组成部分。例如2004 年发布的批处理算法 Map-Reduce可能被过分热情地被称为 “造就 Google 大规模可伸缩性的算法”【2】。随后在各种开源数据系统中得到应用包括 HadoopCouchDB 和 MongoDB。
与多年前为数据仓库开发的并行处理系统【3,4】相比MapReduce 是一个相当低级别的编程模型,但它使得在商用硬件上能进行的处理规模迈上一个新的台阶。虽然 MapReduce 的重要性正在下降【5】但它仍然值得去理解因为它描绘了一幅关于批处理为什么有用以及如何做到有用的清晰图景。

View file

@ -482,7 +482,7 @@ BEGIN TRANSACTION;
COMMIT;
```
[例 12-2]() 依赖于 `request_id` 列上的唯一约束。如果一个事务尝试插入一个已经存在的 ID那么 `INSERT` 失败,事务被中止,使其无法生效两次。即使在较弱的隔离级别下,关系数据库也能正确地维护唯一性约束(而在 “[写入偏差与幻读](ch7.md#写入偏差与幻读)” 中讨论过,应用级别的 **检查 - 然后 - 插入** 可能会在不可串行化的隔离下失败)。
[例 12-2]() 依赖于 `request_id` 列上的唯一约束。如果一个事务尝试插入一个已经存在的 ID那么 `INSERT` 失败,事务被中止,使其无法生效两次。即使在较弱的隔离级别下,关系数据库也能正确地维护唯一性约束(而在 “[写入偏斜与幻读](ch7.md#写入偏斜与幻读)” 中讨论过,应用级别的 **检查 - 然后 - 插入** 可能会在不可串行化的隔离下失败)。
除了抑制重复的请求之外,[例 12-2]() 中的请求表表现得就像一种事件日志,暗示着事件溯源的想法(请参阅 “[事件溯源](ch11.md#事件溯源)”)。更新账户余额事实上不必与插入事件发生在同一个事务中,因为它们是冗余的,而能由下游消费者从请求事件中衍生出来 —— 只要该事件被恰好处理一次,这又一次可以使用请求 ID 来强制执行。
@ -543,7 +543,7 @@ COMMIT;
该算法基本上与 “[使用全序广播实现线性一致的存储](ch9.md#使用全序广播实现线性一致的存储)” 中的算法相同。它可以简单地通过增加分区数伸缩至较大的请求吞吐量,因为每个分区都可以被独立处理。
该方法不仅适用于唯一性约束,而且适用于许多其他类型的约束。其基本原理是,任何可能冲突的写入都会路由到相同的分区并按顺序处理。正如 “[什么是冲突?](ch5.md#什么是冲突?)” 与 “[写入偏差与幻读](ch7.md#写入偏差与幻读)” 中所述,冲突的定义可能取决于应用,但流处理器可以使用任意逻辑来验证请求。这个想法与 Bayou 在 90 年代开创的方法类似【58】。
该方法不仅适用于唯一性约束,而且适用于许多其他类型的约束。其基本原理是,任何可能冲突的写入都会路由到相同的分区并按顺序处理。正如 “[什么是冲突?](ch5.md#什么是冲突?)” 与 “[写入偏斜与幻读](ch7.md#写入偏斜与幻读)” 中所述,冲突的定义可能取决于应用,但流处理器可以使用任意逻辑来验证请求。这个想法与 Bayou 在 90 年代开创的方法类似【58】。
#### 多分区请求处理
@ -657,7 +657,7 @@ ACID 事务通常既提供及时性(例如线性一致性)也提供完整性
#### 维护完整性尽管软件有Bug
除了这些硬件问题之外,总是存在软件 Bug 的风险,这些错误不会被较低层次的网络、内存或文件系统校验和所捕获。即使广泛使用的数据库软件也有 Bug即使像 MySQL 与 PostgreSQL 这样稳健、口碑良好、多年来被许多人充分测试过的软件,就我个人所见也有 Bug比如 MySQL 未能正确维护唯一约束【65】以及 PostgreSQL 的可串行化隔离等级存在特定的写入偏差异常【66】。对于不那么成熟的软件来说情况可能要糟糕得多。
除了这些硬件问题之外,总是存在软件 Bug 的风险,这些错误不会被较低层次的网络、内存或文件系统校验和所捕获。即使广泛使用的数据库软件也有 Bug即使像 MySQL 与 PostgreSQL 这样稳健、口碑良好、多年来被许多人充分测试过的软件,就我个人所见也有 Bug比如 MySQL 未能正确维护唯一约束【65】以及 PostgreSQL 的可串行化隔离等级存在特定的写偏斜异常【66】。对于不那么成熟的软件来说情况可能要糟糕得多。
尽管在仔细设计,测试,以及审查上做出很多努力,但 Bug 仍然会在不知不觉中产生。尽管它们很少,而且最终会被发现并被修复,但总会有那么一段时间,这些 Bug 可能会损坏数据。

18
ch6.md
View file

@ -90,7 +90,7 @@
> #### 一致性哈希
>
> 一致性哈希由 Karger 等人定义。【7】 用于跨互联网级别的缓存系统,例如 CDN 中,是一种能均匀分配负载的方法。它使用随机选择的 **分区边界partition boundaries** 来避免中央控制或分布式共识的需要。 请注意,这里的一致性与复制一致性(请参阅 [第五章](ch5.md))或 ACID 一致性(请参阅 [第七章](ch7.md))无关,而只是描述了一种再平衡rebalancing的特定方法。
> 一致性哈希由 Karger 等人定义。【7】 用于跨互联网级别的缓存系统,例如 CDN 中,是一种能均匀分配负载的方法。它使用随机选择的 **分区边界partition boundaries** 来避免中央控制或分布式共识的需要。 请注意,这里的一致性与复制一致性(请参阅 [第五章](ch5.md))或 ACID 一致性(请参阅 [第七章](ch7.md))无关,而只是描述了一种重新平衡reblancing的特定方法。
>
> 正如我们将在 “[分区再平衡](#分区再平衡)” 中所看到的,这种特殊的方法对于数据库实际上并不是很好,所以在实际中很少使用(某些数据库的文档仍然会使用一致性哈希的说法,但是它往往是不准确的)。 因为有可能产生混淆,所以最好避免使用一致性哈希这个术语,而只是把它称为 **散列分区hash partitioning**
@ -193,7 +193,7 @@ Cassandra 采取了折衷的策略【11, 12, 13】。 Cassandra 中的表可以
也许你想知道为什么我们不使用 ***取模mod***(许多编程语言中的 % 运算符)。例如,`hash(key) mod 10` 会返回一个介于 0 和 9 之间的数字(如果我们将散列写为十进制数,散列模 10 将是最后一个数字)。如果我们有 10 个节点,编号为 0 到 9这似乎是将每个键分配给一个节点的简单方法。
模 N$mod N$)方法的问题是,如果节点数量 N 发生变化,大多数键将需要从一个节点移动到另一个节点。例如,假设 $hash(key)=123456$。如果最初有 10 个节点,那么这个键一开始放在节点 6 上(因为 $123456\ mod\ 10 = 6$)。当你增长到 11 个节点时,键需要移动到节点 3$123456\ mod\ 11 = 3$),当你增长到 12 个节点时,需要移动到节点 0$123456\ mod\ 12 = 0$)。这种频繁的举动使得再平衡的成本过高
模 N$mod N$)方法的问题是,如果节点数量 N 发生变化,大多数键将需要从一个节点移动到另一个节点。例如,假设 $hash(key)=123456$。如果最初有 10 个节点,那么这个键一开始放在节点 6 上(因为 $123456\ mod\ 10 = 6$)。当你增长到 11 个节点时,键需要移动到节点 3$123456\ mod\ 11 = 3$),当你增长到 12 个节点时,需要移动到节点 0$123456\ mod\ 12 = 0$)。这种频繁的举动使得重新平衡过于昂贵
我们需要一种只移动必需数据的方法。
@ -243,17 +243,17 @@ Cassandra 和 Ketama 使用的第三种方法是使分区数与节点数成正
关于再平衡有一个重要问题:自动还是手动进行?
在全自动平衡系统自动决定何时将分区从一个节点移动到另一个节点无须人工干预和完全手动分区指派给节点由管理员明确配置仅在管理员明确重新配置时才会更改之间有一个权衡。例如Couchbase、Riak 和 Voldemort 会自动生成建议的分区分配,但需要管理员提交才能生效。
在全自动重新平衡系统自动决定何时将分区从一个节点移动到另一个节点无须人工干预和完全手动分区指派给节点由管理员明确配置仅在管理员明确重新配置时才会更改之间有一个权衡。例如Couchbase、Riak 和 Voldemort 会自动生成建议的分区分配,但需要管理员提交才能生效。
全自动再平衡可以很方便,因为正常维护的操作工作较少。然而,它可能是不可预测的。再平衡是一个昂贵的操作,因为它需要重新路由请求并将大量数据从一个节点移动到另一个节点。如果没有做好,这个过程可能会使网络或节点负载过重,降低其他请求的性能。
全自动重新平衡可以很方便,因为正常维护的操作工作较少。但是,这可能是不可预测的。再平衡是一个昂贵的操作,因为它需要重新路由请求并将大量数据从一个节点移动到另一个节点。如果没有做好,这个过程可能会使网络或节点负载过重,降低其他请求的性能。
这种自动化与自动故障检测相结合可能十分危险。例如,假设一个节点过载,并且对请求的响应暂时很慢。其他节点得出结论:过载的节点已经死亡,并自动重新平衡集群,使负载离开它。这会对已经超负荷的节点,其他节点和网络造成额外的负载,从而使情况变得更糟,并可能导致级联失败。
出于这个原因,再平衡的过程中有人参与是一件好事。这比全自动的过程慢,但可以帮助防止运维意外。
出于这个原因,再平衡的过程中有人参与是一件好事。这比全自动的过程慢,但可以帮助防止运维意外。
## 请求路由
现在我们已经将数据集分割到多个机器上运行的多个节点上。但是仍然存在一个悬而未决的问题:当客户想要发出请求时,如何知道要连接哪个节点?随着分区重新平衡,分区对节点的分配也发生变化。为了回答这个问题,需要有人知晓这些变化:如果我想读或写键 “foo”需要连接哪个 IP 地址和端口号?
现在我们已经将数据集分割到多个机器上运行的多个节点上。但是仍然存在一个悬而未决的问题:当客户想要发出请求时,如何知道要连接哪个节点?随着分区重新平衡,分区对节点的分配也发生变化。为了回答这个问题,需要有人知晓这些变化:如果我想读或写键 “foo”需要连接哪个 IP 地址和端口号?
这个问题可以概括为 **服务发现service discovery** 它不仅限于数据库。任何可通过网络访问的软件都有这个问题特别是如果它的目标是高可用性在多台机器上运行冗余配置。许多公司已经编写了自己的内部服务发现工具其中许多已经作为开源发布【30】。
@ -281,7 +281,7 @@ Cassandra 和 Ketama 使用的第三种方法是使分区数与节点数成正
Cassandra 和 Riak 采取不同的方法:他们在节点之间使用 **流言协议gossip protocol** 来传播集群状态的变化。请求可以发送到任意节点,该节点会转发到包含所请求的分区的适当节点([图 6-7](img/fig6-7.png) 中的方法 1。这个模型在数据库节点中增加了更多的复杂性但是避免了对像 ZooKeeper 这样的外部协调服务的依赖。
Couchbase 不会自动进行再平衡,这简化了设计。通常情况下,它配置了一个名为 moxi 的路由层它会从集群节点了解路由变化【32】。
Couchbase 不会自动重新平衡,这简化了设计。通常情况下,它配置了一个名为 moxi 的路由层它会从集群节点了解路由变化【32】。
当使用路由层或向随机节点发送请求时,客户端仍然需要找到要连接的 IP 地址。这些地址并不像分区的节点分布变化的那么快,所以使用 DNS 通常就足够了。
@ -295,7 +295,7 @@ Couchbase 不会自动进行再平衡,这简化了设计。通常情况下,
## 本章小结
在本章中,我们探讨了将大数据集划分成更小的子集的不同方法。数据量非常大的时候,在单台机器上存储和处理不再可行,而分区则十分必要。分区的目标是在多台机器上均匀分布数据和查询负载,避免出现热点(负载不成比例的节点)。这需要选择适合于你的数据的分区方案,并在将节点添加到集群或从集群删除时重新平衡分区
在本章中,我们探讨了将大数据集划分成更小的子集的不同方法。数据量非常大的时候,在单台机器上存储和处理不再可行,而分区则十分必要。分区的目标是在多台机器上均匀分布数据和查询负载,避免出现热点(负载不成比例的节点)。这需要选择适合于你的数据的分区方案,并在将节点添加到集群或从集群删除时进行分区再平衡
我们讨论了两种主要的分区方法:
@ -303,7 +303,7 @@ Couchbase 不会自动进行再平衡,这简化了设计。通常情况下,
其中键是有序的,并且分区拥有从某个最小值到某个最大值的所有键。排序的优势在于可以进行有效的范围查询,但是如果应用程序经常访问相邻的键,则存在热点的风险。
在这种方法中,当分区变得太大时,通常将分区分成两个子分区来动态地重新平衡分区。
在这种方法中,当分区变得太大时,通常将分区分成两个子分区,动态地再平衡分区。
* 散列分区

20
ch7.md
View file

@ -263,7 +263,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 的(因为他成功更新了商品列表),但发票却寄送给了 Alice因为她成功更新了发票表。读已提交会防止这样的事故。
- 但是,读已提交并不能防止 [图 7-1](img/fig7-1.png) 中两个计数器增量之间的竞争状态。在这种情况下,第二次写入发生在第一个事务提交后,所以它不是一个脏写。这仍然是不正确的,但是出于不同的原因,在 “[防止丢失更新](#防止丢失更新)” 中将讨论如何使这种计数器增量安全。
- 但是,读已提交并不能防止 [图 7-1](img/fig7-1.png) 中两个计数器增量之间的竞争状态。在这种情况下,第二次写入发生在第一个事务提交后,所以它不是一个脏写。这仍然是不正确的,但是出于不同的原因,在 “[防止更新丢失](#防止丢失更新)” 中将讨论如何使这种计数器增量安全。
![](img/fig7-5.png)
@ -462,7 +462,7 @@ UPDATE wiki_pages SET content = '新内容'
另一方面最后写入胜利LWW的冲突解决方法很容易丢失更新如 “[最后写入胜利(丢弃并发写入)](ch5.md#最后写入胜利(丢弃并发写入))” 中所述。不幸的是LWW 是许多复制数据库中的默认方案。
### 写入偏与幻读
### 写入偏与幻读
前面的章节中,我们看到了 **脏写****丢失更新**,当不同的事务并发地尝试写入相同的对象时,会出现这两种竞争条件。为了避免数据损坏,这些竞争条件需要被阻止 —— 既可以由数据库自动执行,也可以通过锁和原子写操作这类手动安全措施来防止。
@ -478,13 +478,13 @@ UPDATE wiki_pages SET content = '新内容'
在两个事务中,应用首先检查是否有两个或以上的医生正在值班;如果是的话,它就假定一名医生可以安全地休班。由于数据库使用快照隔离,两次检查都返回 2 所以两个事务都进入下一个阶段。Alice 更新自己的记录休班了,而 Bob 也做了一样的事情。两个事务都成功提交了,现在没有医生值班了。违反了至少有一名医生在值班的要求。
#### 写偏差的特征
#### 写偏差的特征
这种异常称为 **写偏差**【28】。它既不是 **脏写**,也不是 **丢失更新**因为这两个事务正在更新两个不同的对象Alice 和 Bob 各自的待命记录)。在这里发生的冲突并不是那么明显,但是这显然是一个竞争条件:如果两个事务一个接一个地运行,那么第二个医生就不能歇班了。异常行为只有在事务并发进行时才有可能发生。
这种异常称为 **写偏差**【28】。它既不是 **脏写**,也不是 **丢失更新**因为这两个事务正在更新两个不同的对象Alice 和 Bob 各自的待命记录)。在这里发生的冲突并不是那么明显,但是这显然是一个竞争条件:如果两个事务一个接一个地运行,那么第二个医生就不能歇班了。异常行为只有在事务并发进行时才有可能发生。
可以将写入偏差视为丢失更新问题的一般化。如果两个事务读取相同的对象,然后更新其中一些对象(不同的事务可能更新不同的对象),则可能发生写入偏差。在多个事务更新同一个对象的特殊情况下,就会发生脏写或丢失更新(取决于时序)。
我们已经看到,有各种不同的方法来防止丢失的更新。但对于写偏差,我们的选择更受限制:
我们已经看到,有各种不同的方法来防止丢失的更新。但对于写偏差,我们的选择更受限制:
* 由于涉及多个对象,单对象的原子操作不起作用。
* 不幸的是,在一些快照隔离的实现中,自动检测丢失更新对此并没有帮助。在 PostgreSQL 的可重复读MySQL/InnoDB 的可重复读Oracle 可串行化或 SQL Server 的快照隔离级别中都不会自动检测写入偏差【23】。自动防止写入偏差需要真正的可串行化隔离请参阅 “[可串行化](#可串行化)”)。
@ -507,9 +507,9 @@ COMMIT;
* 和以前一样,`FOR UPDATE` 告诉数据库锁定返回的所有行以用于更新。
#### 写偏差的更多例子
#### 写偏差的更多例子
偏差乍看像是一个深奥的问题,但一旦意识到这一点,很容易会注意到它可能发生在更多场景下。以下是一些例子:
写偏差乍看像是一个深奥的问题,但一旦意识到这一点,很容易会注意到它可能发生在更多场景下。以下是一些例子:
* 会议室预订系统
@ -771,7 +771,7 @@ WHERE room_id = 123 AND
#### 基于过时前提的决策
先前讨论了快照隔离中的写入偏差(请参阅 “[写入偏差与幻读](#写入偏差与幻读)”)时,我们观察到一个循环模式:事务从数据库读取一些数据,检查查询的结果,并根据它看到的结果决定采取一些操作(写入数据库)。但是,在快照隔离的情况下,原始查询的结果在事务提交时可能不再是最新的,因为数据可能在同一时间被修改。
先前讨论了快照隔离中的写入偏差(请参阅 “[写入偏斜与幻读](#写入偏斜与幻读)”)时,我们观察到一个循环模式:事务从数据库读取一些数据,检查查询的结果,并根据它看到的结果决定采取一些操作(写入数据库)。但是,在快照隔离的情况下,原始查询的结果在事务提交时可能不再是最新的,因为数据可能在同一时间被修改。
换句话说,事务基于一个 **前提premise** 采取行动(事务开始时候的事实,例如:“目前有两名医生正在值班”)。之后当事务要提交时,原始数据可能已经改变 —— 前提可能不再成立。
@ -845,11 +845,11 @@ WHERE room_id = 123 AND
在同一个事务中,客户端在不同的时间点会看见数据库的不同状态。**快照隔离** 经常用于解决这个问题,它允许事务从一个特定时间点的一致性快照中读取数据。快照隔离通常使用 **多版本并发控制MVCC** 来实现。
* 丢失更新
* 更新丢失
两个客户端同时执行 **读取 - 修改 - 写入序列**。其中一个写操作,在没有合并另一个写入变更情况下,直接覆盖了另一个写操作的结果。所以导致数据丢失。快照隔离的一些实现可以自动防止这种异常,而另一些实现则需要手动锁定(`SELECT FOR UPDATE`)。
* 写偏差
* 写偏差
一个事务读取一些东西,根据它所看到的值作出决定,并将该决定写入数据库。但是,写入时,该决定的前提不再是真实的。只有可串行化的隔离才能防止这种异常。

4
ch9.md
View file

@ -140,7 +140,7 @@
>
> ***线性一致性***
>
> **线性一致性Linearizability** 是读取和写入寄存器(单个对象)的 **新鲜度保证**。它不会将操作组合为事务,因此它也不会阻止写入偏差等问题(请参阅 “[写入偏差和幻读](ch7.md#写入偏与幻读)”),除非采取其他措施(例如 [物化冲突](ch7.md#物化冲突))。
> **线性一致性Linearizability** 是读取和写入寄存器(单个对象)的 **新鲜度保证**。它不会将操作组合为事务,因此它也不会阻止写入偏差等问题(请参阅 “[写入偏差和幻读](ch7.md#写入偏与幻读)”),除非采取其他措施(例如 [物化冲突](ch7.md#物化冲突))。
>
> 一个数据库可以提供可串行化和线性一致性,这种组合被称为严格的可串行化或 **强的单副本可串行化strong-1SR**【4,13】。基于两阶段锁定的可串行化实现请参阅 “[两阶段锁定](ch7.md#两阶段锁定)” 一节)或 **真的串行执行**(请参阅 “[真的串行执行](ch7.md#真的串行执行)”一节)通常是线性一致性的。
>
@ -318,7 +318,7 @@ CAP 定理的正式定义仅限于很狭隘的范围【30】它只考虑了
* [图 5-9](img/fig5-9.png) 中出现了类似的模式,我们看到三位领导者之间的复制,并注意到由于网络延迟,一些写入可能会 “压倒” 其他写入。从其中一个副本的角度来看,好像有一个对尚不存在的记录的更新操作。这里的因果意味着,一条记录必须先被创建,然后才能被更新。
* 在 “[检测并发写入](ch5.md#检测并发写入)” 中我们观察到,如果有两个操作 A 和 B则存在三种可能性A 发生在 B 之前,或 B 发生在 A 之前,或者 A 和 B**并发**。这种 **此前发生happened before** 关系是因果关系的另一种表述:如果 A 在 B 前发生,那么意味着 B 可能已经知道了 A或者建立在 A 的基础上,或者依赖于 A。如果 A 和 B 是 **并发** 的,那么它们之间并没有因果联系;换句话说,我们确信 A 和 B 不知道彼此。
* 在事务快照隔离的上下文中(“[快照隔离和可重复读](ch7.md#快照隔离和可重复读)”),我们说事务是从一致性快照中读取的。但此语境中 “一致” 到底又是什么意思?这意味着 **与因果关系保持一致consistent with causality**如果快照包含答案它也必须包含被回答的问题【48】。在某个时间点观察整个数据库与因果关系保持一致意味着因果上在该时间点之前发生的所有操作其影响都是可见的但因果上在该时间点之后发生的操作其影响对观察者不可见。**读偏差read skew** 意味着读取的数据处于违反因果关系的状态(不可重复读,如 [图 7-6](img/fig7-6.png) 所示)。
* 事务之间 **写偏差write skew** 的例子(请参阅 “[写入偏差与幻读](ch7.md#写入偏差与幻读)”)也说明了因果依赖:在 [图 7-8](img/fig7-8.png) 中,爱丽丝被允许离班,因为事务认为鲍勃仍在值班,反之亦然。在这种情况下,离班的动作因果依赖于对当前值班情况的观察。[可串行化快照隔离](ch7.md#可串行化快照隔离) 通过跟踪事务之间的因果依赖来检测写偏差。
* 事务之间 **写偏差write skew** 的例子(请参阅 “[写入偏斜与幻读](ch7.md#写入偏斜与幻读)”)也说明了因果依赖:在 [图 7-8](img/fig7-8.png) 中,爱丽丝被允许离班,因为事务认为鲍勃仍在值班,反之亦然。在这种情况下,离班的动作因果依赖于对当前值班情况的观察。[可串行化快照隔离](ch7.md#可串行化快照隔离) 通过跟踪事务之间的因果依赖来检测写偏差。
* 在爱丽丝和鲍勃看球的例子中([图 9-1](img/fig9-1.png)),在听到爱丽丝惊呼比赛结果后,鲍勃从服务器得到陈旧结果的事实违背了因果关系:爱丽丝的惊呼因果依赖于得分宣告,所以鲍勃应该也能在听到爱丽斯惊呼后查询到比分。相同的模式在 “[跨信道的时序依赖](#跨信道的时序依赖)” 一节中,以 “图像大小调整服务” 的伪装再次出现。
因果关系对事件施加了一种 **顺序**:因在果之前;消息发送在消息收取之前。而且就像现实生活中一样,一件事会导致另一件事:某个节点读取了一些数据然后写入一些结果,另一个节点读取其写入的内容,并依次写入一些其他内容,等等。这些因果依赖的操作链定义了系统中的因果顺序,即,什么在什么之前发生。

View file

@ -205,7 +205,7 @@
各分区负载不平衡,例如某些分区有大量请求或数据,而其他分区则少得多。也被称为热点。请参阅“[负载偏斜和热点消除](ch6.md#负载偏斜和热点消除)”和“[处理偏斜](ch10.md#处理偏斜)”。
时间线异常导致事件以不期望的顺序出现。 请参阅“[快照隔离和可重复读](ch7.md#快照隔离和可重复读)”中的关于读取偏差的讨论,“[写入偏差与幻读](ch7.md#写入偏差与幻读)”中的写入偏差以及“[有序事件的时间戳](ch8.md#有序事件的时间戳)”中的时钟偏斜。
时间线异常导致事件以不期望的顺序出现。 请参阅“[快照隔离和可重复读](ch7.md#快照隔离和可重复读)”中的关于读取偏斜的讨论,“[写入偏斜与幻读](ch7.md#写入偏斜与幻读)”中的写入偏斜以及“[有序事件的时间戳](ch8.md#有序事件的时间戳)”中的时钟偏斜。
* **脑裂split brain**

View file

@ -151,9 +151,6 @@
| ISSUE & Pull Requests | USER | Title |
| ----------------------------------------------- | ------------------------------------------------------------ | ------------------------------------------------------------ |
| [273](https://github.com/Vonng/ddia/pull/273) | [@Sdot-Python](https://github.com/Sdot-Python) | ch7: 統一了 write skew 的翻譯 |
| [271](https://github.com/Vonng/ddia/pull/271) | [@Makonike](https://github.com/Makonike) | ch6: 統一了 rebalancing 的翻譯 |
| [270](https://github.com/Vonng/ddia/pull/270) | [@Ynjxsjmh](https://github.com/Ynjxsjmh) | ch7: 修正不一致的翻譯 |
| [263](https://github.com/Vonng/ddia/pull/263) | [@zydmayday](https://github.com/zydmayday) | ch5: 修正譯文中的重複單詞 |
| [260](https://github.com/Vonng/ddia/pull/260) | [@haifeiWu](https://github.com/haifeiWu) | ch4: 修正部分不準確的翻譯 |
| [258](https://github.com/Vonng/ddia/pull/258) | [@bestgrc](https://github.com/bestgrc) | ch3: 修正一處翻譯錯誤 |
@ -218,6 +215,7 @@
| [128](https://github.com/Vonng/ddia/pull/128) | [@meilin96](https://github.com/meilin96) | ch5: 修正一處錯誤的引用 |
| [126](https://github.com/Vonng/ddia/pull/126) | [@cwr31](https://github.com/cwr31) | ch10: 修正一處錯誤的翻譯(功能 -> 函式) |
| [125](https://github.com/Vonng/ddia/pull/125) | [@dch1228](https://github.com/dch1228) | ch2: 最佳化 how best 的翻譯(如何以最佳方式) |
| [124](https://github.com/Vonng/ddia/pull/124) | [@yingang](https://github.com/yingang) | translation updates (chapter 10) |
| [123](https://github.com/Vonng/ddia/pull/123) | [@yingang](https://github.com/yingang) | translation updates (chapter 9, TOC in readme, glossary, etc.) |
| [121](https://github.com/Vonng/ddia/pull/121) | [@yingang](https://github.com/yingang) | translation updates (chapter 5 to chapter 8) |
| [120](https://github.com/Vonng/ddia/pull/120) | [@jiong-han](https://github.com/jiong-han) | Typo fix: 呲之以鼻 -> 嗤之以鼻 |

View file

@ -28,7 +28,7 @@ Web 和越來越多的基於 HTTP/REST 的 API 使互動的請求 / 響應風格
流處理介於線上和離線(批處理)之間,所以有時候被稱為 **準實時near-real-time****準線上nearline** 處理。像批處理系統一樣,流處理消費輸入併產生輸出(並不需要響應請求)。但是,流式作業在事件發生後不久就會對事件進行操作,而批處理作業則需等待固定的一組輸入資料。這種差異使流處理系統比起批處理系統具有更低的延遲。由於流處理基於批處理,我們將在 [第十一章](ch11.md) 討論它。
正如我們將在本章中看到的那樣批處理是構建可靠、可伸縮和可維護應用程式的重要組成部分。例如2004 年釋出的批處理演算法 Map-Reduce可能被過分熱情地被稱為 “造就 Google 大規模可伸縮性的演算法”【2】。隨後在各種開源資料系統中得到應用包括 HadoopCouchDB 和 MongoDB。
正如我們將在本章中看到的那樣批處理是構建可靠、可伸縮和可維護應用程式的重要組成部分。例如2004 年釋出的批處理演算法 Map-Reduce可能被過分熱情地被稱為 “造就 Google 大規模可伸縮性的演算法”【2】。隨後在各種開源資料系統中得到應用包括 HadoopCouchDB 和 MongoDB。
與多年前為資料倉庫開發的並行處理系統【3,4】相比MapReduce 是一個相當低級別的程式設計模型,但它使得在商用硬體上能進行的處理規模邁上一個新的臺階。雖然 MapReduce 的重要性正在下降【5】但它仍然值得去理解因為它描繪了一幅關於批處理為什麼有用以及如何做到有用的清晰圖景。

View file

@ -482,7 +482,7 @@ BEGIN TRANSACTION;
COMMIT;
```
[例 12-2]() 依賴於 `request_id` 列上的唯一約束。如果一個事務嘗試插入一個已經存在的 ID那麼 `INSERT` 失敗,事務被中止,使其無法生效兩次。即使在較弱的隔離級別下,關係資料庫也能正確地維護唯一性約束(而在 “[寫入偏差與幻讀](ch7.md#寫入偏差與幻讀)” 中討論過,應用級別的 **檢查 - 然後 - 插入** 可能會在不可序列化的隔離下失敗)。
[例 12-2]() 依賴於 `request_id` 列上的唯一約束。如果一個事務嘗試插入一個已經存在的 ID那麼 `INSERT` 失敗,事務被中止,使其無法生效兩次。即使在較弱的隔離級別下,關係資料庫也能正確地維護唯一性約束(而在 “[寫入偏斜與幻讀](ch7.md#寫入偏斜與幻讀)” 中討論過,應用級別的 **檢查 - 然後 - 插入** 可能會在不可序列化的隔離下失敗)。
除了抑制重複的請求之外,[例 12-2]() 中的請求表表現得就像一種事件日誌,暗示著事件溯源的想法(請參閱 “[事件溯源](ch11.md#事件溯源)”)。更新賬戶餘額事實上不必與插入事件發生在同一個事務中,因為它們是冗餘的,而能由下游消費者從請求事件中衍生出來 —— 只要該事件被恰好處理一次,這又一次可以使用請求 ID 來強制執行。
@ -543,7 +543,7 @@ COMMIT;
該演算法基本上與 “[使用全序廣播實現線性一致的儲存](ch9.md#使用全序廣播實現線性一致的儲存)” 中的演算法相同。它可以簡單地透過增加分割槽數伸縮至較大的請求吞吐量,因為每個分割槽都可以被獨立處理。
該方法不僅適用於唯一性約束,而且適用於許多其他型別的約束。其基本原理是,任何可能衝突的寫入都會路由到相同的分割槽並按順序處理。正如 “[什麼是衝突?](ch5.md#什麼是衝突?)” 與 “[寫入偏差與幻讀](ch7.md#寫入偏差與幻讀)” 中所述,衝突的定義可能取決於應用,但流處理器可以使用任意邏輯來驗證請求。這個想法與 Bayou 在 90 年代開創的方法類似【58】。
該方法不僅適用於唯一性約束,而且適用於許多其他型別的約束。其基本原理是,任何可能衝突的寫入都會路由到相同的分割槽並按順序處理。正如 “[什麼是衝突?](ch5.md#什麼是衝突?)” 與 “[寫入偏斜與幻讀](ch7.md#寫入偏斜與幻讀)” 中所述,衝突的定義可能取決於應用,但流處理器可以使用任意邏輯來驗證請求。這個想法與 Bayou 在 90 年代開創的方法類似【58】。
#### 多分割槽請求處理
@ -657,7 +657,7 @@ ACID 事務通常既提供及時性(例如線性一致性)也提供完整性
#### 維護完整性儘管軟體有Bug
除了這些硬體問題之外,總是存在軟體 Bug 的風險,這些錯誤不會被較低層次的網路、記憶體或檔案系統校驗和所捕獲。即使廣泛使用的資料庫軟體也有 Bug即使像 MySQL 與 PostgreSQL 這樣穩健、口碑良好、多年來被許多人充分測試過的軟體,就我個人所見也有 Bug比如 MySQL 未能正確維護唯一約束【65】以及 PostgreSQL 的可序列化隔離等級存在特定的寫入偏差異常【66】。對於不那麼成熟的軟體來說情況可能要糟糕得多。
除了這些硬體問題之外,總是存在軟體 Bug 的風險,這些錯誤不會被較低層次的網路、記憶體或檔案系統校驗和所捕獲。即使廣泛使用的資料庫軟體也有 Bug即使像 MySQL 與 PostgreSQL 這樣穩健、口碑良好、多年來被許多人充分測試過的軟體,就我個人所見也有 Bug比如 MySQL 未能正確維護唯一約束【65】以及 PostgreSQL 的可序列化隔離等級存在特定的寫偏斜異常【66】。對於不那麼成熟的軟體來說情況可能要糟糕得多。
儘管在仔細設計,測試,以及審查上做出很多努力,但 Bug 仍然會在不知不覺中產生。儘管它們很少,而且最終會被發現並被修復,但總會有那麼一段時間,這些 Bug 可能會損壞資料。

View file

@ -90,7 +90,7 @@
> #### 一致性雜湊
>
> 一致性雜湊由 Karger 等人定義。【7】 用於跨網際網路級別的快取系統,例如 CDN 中,是一種能均勻分配負載的方法。它使用隨機選擇的 **分割槽邊界partition boundaries** 來避免中央控制或分散式共識的需要。 請注意,這裡的一致性與複製一致性(請參閱 [第五章](ch5.md))或 ACID 一致性(請參閱 [第七章](ch7.md))無關,而只是描述了一種再平衡rebalancing的特定方法。
> 一致性雜湊由 Karger 等人定義。【7】 用於跨網際網路級別的快取系統,例如 CDN 中,是一種能均勻分配負載的方法。它使用隨機選擇的 **分割槽邊界partition boundaries** 來避免中央控制或分散式共識的需要。 請注意,這裡的一致性與複製一致性(請參閱 [第五章](ch5.md))或 ACID 一致性(請參閱 [第七章](ch7.md))無關,而只是描述了一種重新平衡reblancing的特定方法。
>
> 正如我們將在 “[分割槽再平衡](#分割槽再平衡)” 中所看到的,這種特殊的方法對於資料庫實際上並不是很好,所以在實際中很少使用(某些資料庫的文件仍然會使用一致性雜湊的說法,但是它往往是不準確的)。 因為有可能產生混淆,所以最好避免使用一致性雜湊這個術語,而只是把它稱為 **雜湊分割槽hash partitioning**
@ -193,7 +193,7 @@ Cassandra 採取了折衷的策略【11, 12, 13】。 Cassandra 中的表可以
也許你想知道為什麼我們不使用 ***取模mod***(許多程式語言中的 % 運算子)。例如,`hash(key) mod 10` 會返回一個介於 0 和 9 之間的數字(如果我們將雜湊寫為十進位制數,雜湊模 10 將是最後一個數字)。如果我們有 10 個節點,編號為 0 到 9這似乎是將每個鍵分配給一個節點的簡單方法。
模 N$mod N$)方法的問題是,如果節點數量 N 發生變化,大多數鍵將需要從一個節點移動到另一個節點。例如,假設 $hash(key)=123456$。如果最初有 10 個節點,那麼這個鍵一開始放在節點 6 上(因為 $123456\ mod\ 10 = 6$)。當你增長到 11 個節點時,鍵需要移動到節點 3$123456\ mod\ 11 = 3$),當你增長到 12 個節點時,需要移動到節點 0$123456\ mod\ 12 = 0$)。這種頻繁的舉動使得再平衡的成本過高
模 N$mod N$)方法的問題是,如果節點數量 N 發生變化,大多數鍵將需要從一個節點移動到另一個節點。例如,假設 $hash(key)=123456$。如果最初有 10 個節點,那麼這個鍵一開始放在節點 6 上(因為 $123456\ mod\ 10 = 6$)。當你增長到 11 個節點時,鍵需要移動到節點 3$123456\ mod\ 11 = 3$),當你增長到 12 個節點時,需要移動到節點 0$123456\ mod\ 12 = 0$)。這種頻繁的舉動使得重新平衡過於昂貴
我們需要一種只移動必需資料的方法。
@ -243,17 +243,17 @@ Cassandra 和 Ketama 使用的第三種方法是使分割槽數與節點數成
關於再平衡有一個重要問題:自動還是手動進行?
在全自動平衡系統自動決定何時將分割槽從一個節點移動到另一個節點無須人工干預和完全手動分割槽指派給節點由管理員明確配置僅在管理員明確重新配置時才會更改之間有一個權衡。例如Couchbase、Riak 和 Voldemort 會自動生成建議的分割槽分配,但需要管理員提交才能生效。
在全自動重新平衡系統自動決定何時將分割槽從一個節點移動到另一個節點無須人工干預和完全手動分割槽指派給節點由管理員明確配置僅在管理員明確重新配置時才會更改之間有一個權衡。例如Couchbase、Riak 和 Voldemort 會自動生成建議的分割槽分配,但需要管理員提交才能生效。
全自動再平衡可以很方便,因為正常維護的操作工作較少。然而,它可能是不可預測的。再平衡是一個昂貴的操作,因為它需要重新路由請求並將大量資料從一個節點移動到另一個節點。如果沒有做好,這個過程可能會使網路或節點負載過重,降低其他請求的效能。
全自動重新平衡可以很方便,因為正常維護的操作工作較少。但是,這可能是不可預測的。再平衡是一個昂貴的操作,因為它需要重新路由請求並將大量資料從一個節點移動到另一個節點。如果沒有做好,這個過程可能會使網路或節點負載過重,降低其他請求的效能。
這種自動化與自動故障檢測相結合可能十分危險。例如,假設一個節點過載,並且對請求的響應暫時很慢。其他節點得出結論:過載的節點已經死亡,並自動重新平衡叢集,使負載離開它。這會對已經超負荷的節點,其他節點和網路造成額外的負載,從而使情況變得更糟,並可能導致級聯失敗。
出於這個原因,再平衡的過程中有人参與是一件好事。這比全自動的過程慢,但可以幫助防止運維意外。
出於這個原因,再平衡的過程中有人参與是一件好事。這比全自動的過程慢,但可以幫助防止運維意外。
## 請求路由
現在我們已經將資料集分割到多個機器上執行的多個節點上。但是仍然存在一個懸而未決的問題:當客戶想要發出請求時,如何知道要連線哪個節點?隨著分割槽重新平衡,分割槽對節點的分配也發生變化。為了回答這個問題,需要有人知曉這些變化:如果我想讀或寫鍵 “foo”需要連線哪個 IP 地址和埠號?
現在我們已經將資料集分割到多個機器上執行的多個節點上。但是仍然存在一個懸而未決的問題:當客戶想要發出請求時,如何知道要連線哪個節點?隨著分割槽重新平衡,分割槽對節點的分配也發生變化。為了回答這個問題,需要有人知曉這些變化:如果我想讀或寫鍵 “foo”需要連線哪個 IP 地址和埠號?
這個問題可以概括為 **服務發現service discovery** 它不僅限於資料庫。任何可透過網路訪問的軟體都有這個問題特別是如果它的目標是高可用性在多臺機器上執行冗餘配置。許多公司已經編寫了自己的內部服務發現工具其中許多已經作為開源釋出【30】。
@ -281,7 +281,7 @@ Cassandra 和 Ketama 使用的第三種方法是使分割槽數與節點數成
Cassandra 和 Riak 採取不同的方法:他們在節點之間使用 **流言協議gossip protocol** 來傳播叢集狀態的變化。請求可以傳送到任意節點,該節點會轉發到包含所請求的分割槽的適當節點([圖 6-7](../img/fig6-7.png) 中的方法 1。這個模型在資料庫節點中增加了更多的複雜性但是避免了對像 ZooKeeper 這樣的外部協調服務的依賴。
Couchbase 不會自動進行再平衡,這簡化了設計。通常情況下,它配置了一個名為 moxi 的路由層它會從叢集節點了解路由變化【32】。
Couchbase 不會自動重新平衡,這簡化了設計。通常情況下,它配置了一個名為 moxi 的路由層它會從叢集節點了解路由變化【32】。
當使用路由層或向隨機節點發送請求時,客戶端仍然需要找到要連線的 IP 地址。這些地址並不像分割槽的節點分佈變化的那麼快,所以使用 DNS 通常就足夠了。
@ -295,7 +295,7 @@ Couchbase 不會自動進行再平衡,這簡化了設計。通常情況下,
## 本章小結
在本章中,我們探討了將大資料集劃分成更小的子集的不同方法。資料量非常大的時候,在單臺機器上儲存和處理不再可行,而分割槽則十分必要。分割槽的目標是在多臺機器上均勻分佈資料和查詢負載,避免出現熱點(負載不成比例的節點)。這需要選擇適合於你的資料的分割槽方案,並在將節點新增到叢集或從叢集刪除時重新平衡分割槽
在本章中,我們探討了將大資料集劃分成更小的子集的不同方法。資料量非常大的時候,在單臺機器上儲存和處理不再可行,而分割槽則十分必要。分割槽的目標是在多臺機器上均勻分佈資料和查詢負載,避免出現熱點(負載不成比例的節點)。這需要選擇適合於你的資料的分割槽方案,並在將節點新增到叢集或從叢集刪除時進行分割槽再平衡
我們討論了兩種主要的分割槽方法:
@ -303,7 +303,7 @@ Couchbase 不會自動進行再平衡,這簡化了設計。通常情況下,
其中鍵是有序的,並且分割槽擁有從某個最小值到某個最大值的所有鍵。排序的優勢在於可以進行有效的範圍查詢,但是如果應用程式經常訪問相鄰的鍵,則存在熱點的風險。
在這種方法中,當分割槽變得太大時,通常將分割槽分成兩個子分割槽來動態地重新平衡分割槽。
在這種方法中,當分割槽變得太大時,通常將分割槽分成兩個子分割槽,動態地再平衡分割槽。
* 雜湊分割槽

View file

@ -263,7 +263,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 的(因為他成功更新了商品列表),但發票卻寄送給了 Alice因為她成功更新了發票表。讀已提交會防止這樣的事故。
- 但是,讀已提交併不能防止 [圖 7-1](../img/fig7-1.png) 中兩個計數器增量之間的競爭狀態。在這種情況下,第二次寫入發生在第一個事務提交後,所以它不是一個髒寫。這仍然是不正確的,但是出於不同的原因,在 “[防止丟失更新](#防止丟失更新)” 中將討論如何使這種計數器增量安全。
- 但是,讀已提交併不能防止 [圖 7-1](../img/fig7-1.png) 中兩個計數器增量之間的競爭狀態。在這種情況下,第二次寫入發生在第一個事務提交後,所以它不是一個髒寫。這仍然是不正確的,但是出於不同的原因,在 “[防止更新丟失](#防止丟失更新)” 中將討論如何使這種計數器增量安全。
![](../img/fig7-5.png)
@ -462,7 +462,7 @@ UPDATE wiki_pages SET content = '新內容'
另一方面最後寫入勝利LWW的衝突解決方法很容易丟失更新如 “[最後寫入勝利(丟棄併發寫入)](ch5.md#最後寫入勝利(丟棄併發寫入))” 中所述。不幸的是LWW 是許多複製資料庫中的預設方案。
### 寫入偏與幻讀
### 寫入偏與幻讀
前面的章節中,我們看到了 **髒寫****丟失更新**,當不同的事務併發地嘗試寫入相同的物件時,會出現這兩種競爭條件。為了避免資料損壞,這些競爭條件需要被阻止 —— 既可以由資料庫自動執行,也可以透過鎖和原子寫操作這類手動安全措施來防止。
@ -478,13 +478,13 @@ UPDATE wiki_pages SET content = '新內容'
在兩個事務中,應用首先檢查是否有兩個或以上的醫生正在值班;如果是的話,它就假定一名醫生可以安全地休班。由於資料庫使用快照隔離,兩次檢查都返回 2 所以兩個事務都進入下一個階段。Alice 更新自己的記錄休班了,而 Bob 也做了一樣的事情。兩個事務都成功提交了,現在沒有醫生值班了。違反了至少有一名醫生在值班的要求。
#### 寫偏差的特徵
#### 寫偏差的特徵
這種異常稱為 **寫偏差**【28】。它既不是 **髒寫**,也不是 **丟失更新**因為這兩個事務正在更新兩個不同的物件Alice 和 Bob 各自的待命記錄)。在這裡發生的衝突並不是那麼明顯,但是這顯然是一個競爭條件:如果兩個事務一個接一個地執行,那麼第二個醫生就不能歇班了。異常行為只有在事務併發進行時才有可能發生。
這種異常稱為 **寫偏差**【28】。它既不是 **髒寫**,也不是 **丟失更新**因為這兩個事務正在更新兩個不同的物件Alice 和 Bob 各自的待命記錄)。在這裡發生的衝突並不是那麼明顯,但是這顯然是一個競爭條件:如果兩個事務一個接一個地執行,那麼第二個醫生就不能歇班了。異常行為只有在事務併發進行時才有可能發生。
可以將寫入偏差視為丟失更新問題的一般化。如果兩個事務讀取相同的物件,然後更新其中一些物件(不同的事務可能更新不同的物件),則可能發生寫入偏差。在多個事務更新同一個物件的特殊情況下,就會發生髒寫或丟失更新(取決於時序)。
我們已經看到,有各種不同的方法來防止丟失的更新。但對於寫偏差,我們的選擇更受限制:
我們已經看到,有各種不同的方法來防止丟失的更新。但對於寫偏差,我們的選擇更受限制:
* 由於涉及多個物件,單物件的原子操作不起作用。
* 不幸的是,在一些快照隔離的實現中,自動檢測丟失更新對此並沒有幫助。在 PostgreSQL 的可重複讀MySQL/InnoDB 的可重複讀Oracle 可序列化或 SQL Server 的快照隔離級別中都不會自動檢測寫入偏差【23】。自動防止寫入偏差需要真正的可序列化隔離請參閱 “[可序列化](#可序列化)”)。
@ -507,9 +507,9 @@ COMMIT;
* 和以前一樣,`FOR UPDATE` 告訴資料庫鎖定返回的所有行以用於更新。
#### 寫偏差的更多例子
#### 寫偏差的更多例子
偏差乍看像是一個深奧的問題,但一旦意識到這一點,很容易會注意到它可能發生在更多場景下。以下是一些例子:
寫偏差乍看像是一個深奧的問題,但一旦意識到這一點,很容易會注意到它可能發生在更多場景下。以下是一些例子:
* 會議室預訂系統
@ -771,7 +771,7 @@ WHERE room_id = 123 AND
#### 基於過時前提的決策
先前討論了快照隔離中的寫入偏差(請參閱 “[寫入偏差與幻讀](#寫入偏差與幻讀)”)時,我們觀察到一個迴圈模式:事務從資料庫讀取一些資料,檢查查詢的結果,並根據它看到的結果決定採取一些操作(寫入資料庫)。但是,在快照隔離的情況下,原始查詢的結果在事務提交時可能不再是最新的,因為資料可能在同一時間被修改。
先前討論了快照隔離中的寫入偏差(請參閱 “[寫入偏斜與幻讀](#寫入偏斜與幻讀)”)時,我們觀察到一個迴圈模式:事務從資料庫讀取一些資料,檢查查詢的結果,並根據它看到的結果決定採取一些操作(寫入資料庫)。但是,在快照隔離的情況下,原始查詢的結果在事務提交時可能不再是最新的,因為資料可能在同一時間被修改。
換句話說,事務基於一個 **前提premise** 採取行動(事務開始時候的事實,例如:“目前有兩名醫生正在值班”)。之後當事務要提交時,原始資料可能已經改變 —— 前提可能不再成立。
@ -845,11 +845,11 @@ WHERE room_id = 123 AND
在同一個事務中,客戶端在不同的時間點會看見資料庫的不同狀態。**快照隔離** 經常用於解決這個問題,它允許事務從一個特定時間點的一致性快照中讀取資料。快照隔離通常使用 **多版本併發控制MVCC** 來實現。
* 丟失更新
* 更新丟失
兩個客戶端同時執行 **讀取 - 修改 - 寫入序列**。其中一個寫操作,在沒有合併另一個寫入變更情況下,直接覆蓋了另一個寫操作的結果。所以導致資料丟失。快照隔離的一些實現可以自動防止這種異常,而另一些實現則需要手動鎖定(`SELECT FOR UPDATE`)。
* 寫偏差
* 寫偏差
一個事務讀取一些東西,根據它所看到的值作出決定,並將該決定寫入資料庫。但是,寫入時,該決定的前提不再是真實的。只有可序列化的隔離才能防止這種異常。

View file

@ -140,7 +140,7 @@
>
> ***線性一致性***
>
> **線性一致性Linearizability** 是讀取和寫入暫存器(單個物件)的 **新鮮度保證**。它不會將操作組合為事務,因此它也不會阻止寫入偏差等問題(請參閱 “[寫入偏差和幻讀](ch7.md#寫入偏與幻讀)”),除非採取其他措施(例如 [物化衝突](ch7.md#物化衝突))。
> **線性一致性Linearizability** 是讀取和寫入暫存器(單個物件)的 **新鮮度保證**。它不會將操作組合為事務,因此它也不會阻止寫入偏差等問題(請參閱 “[寫入偏差和幻讀](ch7.md#寫入偏與幻讀)”),除非採取其他措施(例如 [物化衝突](ch7.md#物化衝突))。
>
> 一個數據庫可以提供可序列化和線性一致性,這種組合被稱為嚴格的可序列化或 **強的單副本可序列化strong-1SR**【4,13】。基於兩階段鎖定的可序列化實現請參閱 “[兩階段鎖定](ch7.md#兩階段鎖定)” 一節)或 **真的序列執行**(請參閱 “[真的序列執行](ch7.md#真的序列執行)”一節)通常是線性一致性的。
>
@ -318,7 +318,7 @@ CAP 定理的正式定義僅限於很狹隘的範圍【30】它只考慮了
* [圖 5-9](../img/fig5-9.png) 中出現了類似的模式,我們看到三位領導者之間的複製,並注意到由於網路延遲,一些寫入可能會 “壓倒” 其他寫入。從其中一個副本的角度來看,好像有一個對尚不存在的記錄的更新操作。這裡的因果意味著,一條記錄必須先被建立,然後才能被更新。
* 在 “[檢測併發寫入](ch5.md#檢測併發寫入)” 中我們觀察到,如果有兩個操作 A 和 B則存在三種可能性A 發生在 B 之前,或 B 發生在 A 之前,或者 A 和 B**併發**。這種 **此前發生happened before** 關係是因果關係的另一種表述:如果 A 在 B 前發生,那麼意味著 B 可能已經知道了 A或者建立在 A 的基礎上,或者依賴於 A。如果 A 和 B 是 **併發** 的,那麼它們之間並沒有因果聯絡;換句話說,我們確信 A 和 B 不知道彼此。
* 在事務快照隔離的上下文中(“[快照隔離和可重複讀](ch7.md#快照隔離和可重複讀)”),我們說事務是從一致性快照中讀取的。但此語境中 “一致” 到底又是什麼意思?這意味著 **與因果關係保持一致consistent with causality**如果快照包含答案它也必須包含被回答的問題【48】。在某個時間點觀察整個資料庫與因果關係保持一致意味著因果上在該時間點之前發生的所有操作其影響都是可見的但因果上在該時間點之後發生的操作其影響對觀察者不可見。**讀偏差read skew** 意味著讀取的資料處於違反因果關係的狀態(不可重複讀,如 [圖 7-6](../img/fig7-6.png) 所示)。
* 事務之間 **寫偏差write skew** 的例子(請參閱 “[寫入偏差與幻讀](ch7.md#寫入偏差與幻讀)”)也說明了因果依賴:在 [圖 7-8](../img/fig7-8.png) 中,愛麗絲被允許離班,因為事務認為鮑勃仍在值班,反之亦然。在這種情況下,離班的動作因果依賴於對當前值班情況的觀察。[可序列化快照隔離](ch7.md#可序列化快照隔離) 透過跟蹤事務之間的因果依賴來檢測寫偏差。
* 事務之間 **寫偏差write skew** 的例子(請參閱 “[寫入偏斜與幻讀](ch7.md#寫入偏斜與幻讀)”)也說明了因果依賴:在 [圖 7-8](../img/fig7-8.png) 中,愛麗絲被允許離班,因為事務認為鮑勃仍在值班,反之亦然。在這種情況下,離班的動作因果依賴於對當前值班情況的觀察。[可序列化快照隔離](ch7.md#可序列化快照隔離) 透過跟蹤事務之間的因果依賴來檢測寫偏差。
* 在愛麗絲和鮑勃看球的例子中([圖 9-1](../img/fig9-1.png)),在聽到愛麗絲驚呼比賽結果後,鮑勃從伺服器得到陳舊結果的事實違背了因果關係:愛麗絲的驚呼因果依賴於得分宣告,所以鮑勃應該也能在聽到愛麗斯驚呼後查詢到比分。相同的模式在 “[跨通道的時序依賴](#跨通道的時序依賴)” 一節中,以 “影象大小調整服務” 的偽裝再次出現。
因果關係對事件施加了一種 **順序**:因在果之前;訊息傳送在訊息收取之前。而且就像現實生活中一樣,一件事會導致另一件事:某個節點讀取了一些資料然後寫入一些結果,另一個節點讀取其寫入的內容,並依次寫入一些其他內容,等等。這些因果依賴的操作鏈定義了系統中的因果順序,即,什麼在什麼之前發生。

View file

@ -205,7 +205,7 @@
各分割槽負載不平衡,例如某些分割槽有大量請求或資料,而其他分割槽則少得多。也被稱為熱點。請參閱“[負載偏斜和熱點消除](ch6.md#負載偏斜和熱點消除)”和“[處理偏斜](ch10.md#處理偏斜)”。
時間線異常導致事件以不期望的順序出現。 請參閱“[快照隔離和可重複讀](ch7.md#快照隔離和可重複讀)”中的關於讀取偏差的討論,“[寫入偏差與幻讀](ch7.md#寫入偏差與幻讀)”中的寫入偏差以及“[有序事件的時間戳](ch8.md#有序事件的時間戳)”中的時鐘偏斜。
時間線異常導致事件以不期望的順序出現。 請參閱“[快照隔離和可重複讀](ch7.md#快照隔離和可重複讀)”中的關於讀取偏斜的討論,“[寫入偏斜與幻讀](ch7.md#寫入偏斜與幻讀)”中的寫入偏斜以及“[有序事件的時間戳](ch8.md#有序事件的時間戳)”中的時鐘偏斜。
* **腦裂split brain**