update several redundant referenced headings

This commit is contained in:
Gang Yin 2021-08-24 09:24:10 +08:00
parent b258b24067
commit 5e5b9a15a5
10 changed files with 72 additions and 72 deletions

10
ch11.md
View File

@ -54,7 +54,7 @@
如果消息被缓存在队列中那么理解队列增长会发生什么是很重要的。当队列装不进内存时系统会崩溃吗还是将消息写入磁盘如果是这样磁盘访问又会如何影响消息传递系统的性能【6】
2. **如果节点崩溃或暂时脱机,会发生什么情况? —— 是否会有消息丢失?**与数据库一样,持久性可能需要写入磁盘和/或复制的某种组合(请参阅“[复制和持久性](ch7.md#复制和持久性)”),这是有代价的。如果你能接受有时消息会丢失,则可能在同一硬件上获得更高的吞吐量和更低的延迟。
2. **如果节点崩溃或暂时脱机,会发生什么情况? —— 是否会有消息丢失?**与数据库一样,持久性可能需要写入磁盘和/或复制的某种组合(请参阅“[复制与持久性](ch7.md#复制与持久性)”),这是有代价的。如果你能接受有时消息会丢失,则可能在同一硬件上获得更高的吞吐量和更低的延迟。
是否可以接受消息丢失取决于应用。例如对于周期传输的传感器读数和指标偶尔丢失的数据点可能并不重要因为更新的值会在短时间内发出。但要注意如果大量的消息被丢弃可能无法立刻意识到指标已经不正确了【7】。如果你正在对事件计数那么它们能够可靠送达是更重要的因为每个丢失的消息都意味着使计数器的错误扩大。
@ -231,7 +231,7 @@
除非有一些额外的并发检测机制,例如我们在“[检测并发写入](ch5.md#检测并发写入)”中讨论的版本向量,否则你甚至不会意识到发生了并发写入 —— 一个值将简单地以无提示方式覆盖另一个值。
双重写入的另一个问题是,其中一个写入可能会失败,而另一个成功。这是一个容错问题,而不是一个并发问题,但也会造成两个系统互相不一致的结果。确保它们要么都成功要么都失败,是原子提交问题的一个例子,解决这个问题的代价是昂贵的(请参阅“[原子提交与两阶段提交2PC](ch7.md#原子提交与两阶段提交2PC)”)。
双重写入的另一个问题是,其中一个写入可能会失败,而另一个成功。这是一个容错问题,而不是一个并发问题,但也会造成两个系统互相不一致的结果。确保它们要么都成功要么都失败,是原子提交问题的一个例子,解决这个问题的代价是昂贵的(请参阅“[原子提交与两阶段提交](ch7.md#原子提交与两阶段提交)”)。
如果你只有一个单领导者复制的数据库,那么这个领导者决定了写入顺序,而状态机复制方法可以在数据库副本上工作。然而,在[图11-4](img/fig11-4.png)中,没有单个主库:数据库可能有一个领导者,搜索索引也可能有一个领导者,但是两者都不追随对方,所以可能会发生冲突(请参阅“[多主复制](ch5.md#多主复制)“)。
@ -321,7 +321,7 @@
* 用于记录更新的CDC事件通常包含记录的**完整新版本**,因此主键的当前值完全由该主键的最近事件确定,而日志压缩可以丢弃相同主键的先前事件。
* 另一方面,事件溯源在更高层次进行建模:事件通常表示用户操作的意图,而不是因为操作而发生的状态更新机制。在这种情况下,后面的事件通常不会覆盖先前的事件,所以你需要完整的历史事件来重新构建最终状态。这里进行同样的日志压缩是不可能的。
使用事件溯源的应用通常有一些机制,用于存储从事件日志中导出的当前状态快照,因此它们不需要重复处理完整的日志。然而这只是一种性能优化,用来加速读取,提高从崩溃中恢复的速度;真正的目的是系统能够永久存储所有原始事件,并在需要时重新处理完整的事件日志。我们将在“[不变性的限制](#不变性的限制)”中讨论这个假设。
使用事件溯源的应用通常有一些机制,用于存储从事件日志中导出的当前状态快照,因此它们不需要重复处理完整的日志。然而这只是一种性能优化,用来加速读取,提高从崩溃中恢复的速度;真正的目的是系统能够永久存储所有原始事件,并在需要时重新处理完整的事件日志。我们将在“[不变性的局限性](#不变性的局限性)”中讨论这个假设。
#### 命令和事件
@ -392,7 +392,7 @@ $$
如果事件日志与应用状态以相同的方式分区例如处理分区3中的客户事件只需要更新分区3中的应用状态那么直接使用单线程日志消费者就不需要写入并发控制了。它从设计上一次只处理一个事件请参阅“[真的串行执行](ch7.md#真的串行执行)”。日志通过在分区中定义事件的序列顺序消除了并发性的不确定性【24】。如果一个事件触及多个状态分区那么需要做更多的工作我们将在[第十二章](ch12.md)讨论。
#### 不变性的限制
#### 不变性的局限性
许多不使用事件溯源模型的系统也还是依赖不可变性:各种数据库在内部使用不可变的数据结构或多版本数据来支持时间点快照(请参阅“[索引和快照隔离](ch7.md#索引和快照隔离)” )。 GitMercurial和Fossil等版本控制系统也依靠不可变的数据来保存文件的版本历史记录。
@ -633,7 +633,7 @@ GROUP BY follows.follower_id
在本章的最后一节中,让我们看一看流处理是如何容错的。我们在[第十章](ch10.md)中看到批处理框架可以很容易地容错如果MapReduce作业中的任务失败可以简单地在另一台机器上再次启动并且丢弃失败任务的输出。这种透明的重试是可能的因为输入文件是不可变的每个任务都将其输出写入到HDFS上的独立文件中而输出仅当任务成功完成后可见。
特别是,批处理容错方法可确保批处理作业的输出与没有出错的情况相同,即使实际上某些任务失败了。看起来好像每条输入记录都被处理了恰好一次 —— 没有记录被跳过,而且没有记录被处理两次。尽管重启任务意味着实际上可能会多次处理记录,但输出中的可见效果看上去就像只处理过一次。这个原则被称为**恰好一次语义exactly-once semantics**,尽管**效一次effectively-once** 可能会是一个更写实的术语【90】。
特别是,批处理容错方法可确保批处理作业的输出与没有出错的情况相同,即使实际上某些任务失败了。看起来好像每条输入记录都被处理了恰好一次 —— 没有记录被跳过,而且没有记录被处理两次。尽管重启任务意味着实际上可能会多次处理记录,但输出中的可见效果看上去就像只处理过一次。这个原则被称为**恰好一次语义exactly-once semantics**,尽管**效一次effectively-once** 可能会是一个更写实的术语【90】。
在流处理中也出现了同样的容错问题,但是处理起来没有那么直观:等待某个任务完成之后再使其输出可见并不是一个可行选项,因为你永远无法处理完一个无限的流。

28
ch7.md
View File

@ -47,19 +47,19 @@
### ACID的含义
事务所提供的安全保证通常由众所周知的首字母缩略词ACID来描述ACID代表**原子性Atomicity****一致性Consistency****隔离性Isolation**和**持久性Durability**。它由TheoHärder和Andreas Reuter于1983年提出旨在为数据库中的容错机制建立精确的术语。
事务所提供的安全保证通常由众所周知的首字母缩略词ACID来描述ACID代表**原子性Atomicity****一致性Consistency****隔离性Isolation**和**持久性Durability**。它由Theo Härder和Andreas Reuter于1983年提出旨在为数据库中的容错机制建立精确的术语。
但实际上不同数据库的ACID实现并不相同。例如我们将会看到关于**隔离性Isolation** 的含义就有许多含糊不清【8】。高层次上的想法很美好但魔鬼隐藏在细节里。今天当一个系统声称自己“符合ACID”时实际上能期待的是什么保证并不清楚。不幸的是ACID现在几乎已经变成了一个营销术语。
但实际上不同数据库的ACID实现并不相同。例如我们将会看到关于**隔离性**的含义就有许多含糊不清【8】。高层次上的想法很美好但魔鬼隐藏在细节里。今天当一个系统声称自己“符合ACID”时实际上能期待的是什么保证并不清楚。不幸的是ACID现在几乎已经变成了一个营销术语。
不符合ACID标准的系统有时被称为BASE它代表**基本可用性Basically Available****软状态Soft State**和**最终一致性Eventual consistency**【9】这比ACID的定义更加模糊似乎BASE的唯一合理的定义是“不是ACID”即它几乎可以代表任何你想要的东西。
让我们深入了解原子性,一致性,隔离性和持久性的定义,这可以让我们提炼出事务的思想。
#### 原子性Atomicity
#### 原子性
一般来说,原子是指不能分解成小部分的东西。这个词在计算机的不同领域中意味着相似但又微妙不同的东西。例如,在多线程编程中,如果一个线程执行一个原子操作,这意味着另一个线程无法看到该操作的一半结果。系统只能处于操作之前或操作之后的状态,而不是介于两者之间的状态。
相比之下ACID的原子性并**不**是关于 **并发concurrent** 的。它并不是在描述如果几个进程试图同时访问相同的数据会发生什么情况,这种情况包含在缩写 ***I*** 中,即[**隔离性Isolation**](#隔离性Isolation)
相比之下ACID的原子性并**不**是关于 **并发concurrent** 的。它并不是在描述如果几个进程试图同时访问相同的数据会发生什么情况,这种情况包含在缩写 ***I*** 中,即[**隔离性**](#隔离性)
ACID的原子性描述了当客户想进行多次写入但在一些写操作处理完之后出现故障的情况。例如进程崩溃网络连接中断磁盘变满或者某种完整性约束被违反。如果这些写操作被分组到一个原子事务中并且该事务由于错误而不能完成提交则该事务将被中止并且数据库必须丢弃或撤消该事务中迄今为止所做的任何写入。
@ -67,7 +67,7 @@ ACID的原子性描述了当客户想进行多次写入但在一些写操作
ACID原子性的定义特征是**能够在错误时中止事务,丢弃该事务进行的所有写入变更的能力。** 或许 **可中止性abortability** 是更好的术语,但本书将继续使用原子性,因为这是惯用词。
#### 一致性Consistency
#### 一致性
一致性这个词被赋予太多含义:
@ -86,7 +86,7 @@ ACID一致性的概念是**对数据的一组特定约束必须始终成立**
[^i]: 乔·海勒斯坦Joe Hellerstein指出在论Härder与Reuter的论文中“ACID中的C”是被“扔进去凑缩写单词的”【7】而且那时候大家都不怎么在乎一致性。
#### 隔离性Isolation
#### 隔离性
大多数数据库都会同时被多个客户端访问。如果它们各自读写数据库的不同部分,这是没有问题的,但是如果它们访问相同的数据库记录,则可能会遇到**并发**问题(**竞争条件race conditions**)。
@ -100,7 +100,7 @@ ACID意义上的隔离性意味着**同时执行的事务是相互隔离的**
然而实践中很少会使用可串行的隔离因为它有性能损失。一些流行的数据库如Oracle 11g甚至没有实现它。在Oracle中有一个名为“可串行的”隔离级别但实际上它实现了一种叫做**快照隔离snapshot isolation** 的功能,**这是一种比可串行化更弱的保证**【8,11】。我们将在“[弱隔离级别](#弱隔离级别)”中研究快照隔离和其他形式的隔离。
#### 持久性Durability
#### 持久性
数据库系统的目的是,提供一个安全的地方存储数据,而不用担心丢失。**持久性** 是一个承诺,即一旦事务成功完成,即使发生硬件故障或数据库崩溃,写入的任何数据也不会丢失。
@ -108,7 +108,7 @@ ACID意义上的隔离性意味着**同时执行的事务是相互隔离的**
如“[可靠性](ch1.md#可靠性)”一节所述,**完美的持久性是不存在的** :如果所有硬盘和所有备份同时被销毁,那显然没有任何数据库能救得了你。
> #### 复制持久性
> #### 复制持久性
>
> 在历史上持久性意味着写入归档磁带。后来它被理解为写入磁盘或SSD。再后来它又有了新的内涵即“复制replication”。哪种实现更好一些
>
@ -161,7 +161,7 @@ SELECT COUNT*FROM emails WHERE recipient_id = 2 AND unread_flag = true
多对象事务需要某种方式来确定哪些读写操作属于同一个事务。在关系型数据库中通常基于客户端与数据库服务器的TCP连接在任何特定连接上`BEGIN TRANSACTION` 和 `COMMIT` 语句之间的所有内容,被认为是同一事务的一部分.[^iii]
[^iii]: 这并不完美。如果TCP连接中断则事务必须中止。如果中断发生在客户端请求提交之后但在服务器确认提交发生之前客户端并不知道事务是否已提交。为了解决这个问题事务管理器可以通过一个唯一事务标识符来对操作进行分组这个标识符并未绑定到特定TCP连接。后续再“[数据库端到端的争论](ch12.md#数据库端到端的争论)”一节将回到这个主题。
[^iii]: 这并不完美。如果TCP连接中断则事务必须中止。如果中断发生在客户端请求提交之后但在服务器确认提交发生之前客户端并不知道事务是否已提交。为了解决这个问题事务管理器可以通过一个唯一事务标识符来对操作进行分组这个标识符并未绑定到特定TCP连接。后续再“[数据库的端到端原则](ch12.md#数据库的端到端原则)”一节将回到这个主题。
另一方面许多非关系数据库并没有将这些操作组合在一起的方法。即使存在多对象API例如某键值存储可能具有在一个操作中更新几个键的multi-put操作但这并不一定意味着它具有事务语义该命令可能在一些键上成功在其他的键上失败使数据库处于部分更新的状态。
@ -208,7 +208,7 @@ SELECT COUNT*FROM emails WHERE recipient_id = 2 AND unread_flag = true
- 如果事务实际上成功了,但是在服务器试图向客户端确认提交成功时网络发生故障(所以客户端认为提交失败了),那么重试事务会导致事务被执行两次——除非你有一个额外的应用级除重机制。
- 如果错误是由于负载过大造成的,则重试事务将使问题变得更糟,而不是更好。为了避免这种正反馈循环,可以限制重试次数,使用指数退避算法,并单独处理与过载相关的错误(如果允许)。
- 仅在临时性错误(例如,由于死锁,异常情况,临时性网络中断和故障切换)后才值得重试。在发生永久性错误(例如,违反约束)之后重试是毫无意义的。
- 如果事务在数据库之外也有副作用,即使事务被中止,也可能发生这些副作用。例如,如果你正在发送电子邮件,那你肯定不希望每次重试事务时都重新发送电子邮件。如果你想确保几个不同的系统一起提交或放弃,**两阶段提交2PC, two-phase commit** 可以提供帮助(“[原子提交与两阶段提交2PC](ch9.md#原子提交与两阶段提交2PC)”中将讨论这个问题)。
- 如果事务在数据库之外也有副作用,即使事务被中止,也可能发生这些副作用。例如,如果你正在发送电子邮件,那你肯定不希望每次重试事务时都重新发送电子邮件。如果你想确保几个不同的系统一起提交或放弃,**两阶段提交2PC, two-phase commit** 可以提供帮助(“[原子提交与两阶段提交](ch9.md#原子提交与两阶段提交)”中将讨论这个问题)。
- 如果客户端进程在重试中失效,任何试图写入数据库的数据都将丢失。
## 弱隔离级别
@ -593,8 +593,8 @@ COMMIT;
但如果可串行化隔离级别比弱隔离级别的烂摊子要好得多,那为什么没有人见人爱?为了回答这个问题,我们需要看看实现可串行化的选项,以及它们如何执行。目前大多数提供可串行化的数据库都使用了三种技术之一,本章的剩余部分将会介绍这些技术。
- 字面意义上地串行顺序执行事务(请参阅“[真的串行执行](#真的串行执行)”)
- **两阶段锁定2PL, two-phase locking**,几十年来唯一可行的选择。(请参阅“[两阶段锁定2PL](#两阶段锁定2PL)”)
- 乐观并发控制技术,例如**可串行化快照隔离serializable snapshot isolation**(请参阅“[可串行化快照隔离SSI](#可串行化快照隔离SSI)”
- **两阶段锁定2PL, two-phase locking**,几十年来唯一可行的选择。(请参阅“[两阶段锁定](#两阶段锁定)”)
- 乐观并发控制技术,例如**可串行化快照隔离serializable snapshot isolation**(请参阅“[可串行化快照隔离](#可串行化快照隔离)”
现在将主要在单节点数据库的背景下讨论这些技术;在[第九章](ch9.md)中,我们将研究如何将它们推广到涉及分布式系统中多个节点的事务。
@ -664,7 +664,7 @@ VoltDB还使用存储过程进行复制但不是将事务的写入结果从
[^x]: 如果事务需要访问不在内存中的数据,最好的解决方案可能是中止事务,异步地将数据提取到内存中,同时继续处理其他事务,然后在数据加载完毕时重新启动事务。这种方法被称为**反缓存anti-caching**,正如前面在“[在内存中存储一切](ch3.md#在内存中存储一切)”中所述。
### 两阶段锁定2PL
### 两阶段锁定
大约30年来在数据库中只有一种广泛使用的串行化算法**两阶段锁定2PLtwo-phase locking** [^xi]
@ -748,7 +748,7 @@ WHERE room_id = 123 AND
如果没有可以挂载间隙锁的索引,数据库可以退化到使用整个表上的共享锁。这对性能不利,因为它会阻止所有其他事务写入表格,但这是一个安全的回退位置。
### 可串行化快照隔离SSI
### 可串行化快照隔离
本章描绘了数据库中并发控制的黯淡画面。一方面我们实现了性能不好2PL或者伸缩性不好串行执行的可串行化隔离级别。另一方面我们有性能良好的弱隔离级别但容易出现各种竞争条件丢失更新写入偏差幻读等。串行化的隔离级别和高性能是从根本上相互矛盾的吗

2
ch8.md
View File

@ -86,7 +86,7 @@
>
> 虽然这个系统可以比它的底层部分更可靠但它的可靠性总是有限的。例如纠错码可以处理少量的单比特错误但是如果你的信号被干扰所淹没那么通过信道可以得到多少数据是有根本性的限制的【13】。 TCP可以隐藏数据包的丢失重复和重新排序但是它不能神奇地消除网络中的延迟。
>
> 虽然更可靠的高级系统并不完美,但它仍然有用,因为它处理了一些棘手的低级错误,所以其余的错误通常更容易推理和处理。我们将在“[数据库端到端的争论](ch12.md#数据库端到端的争论)”中进一步探讨这个问题。
> 虽然更可靠的高级系统并不完美,但它仍然有用,因为它处理了一些棘手的低级错误,所以其余的错误通常更容易推理和处理。我们将在“[数据库的端到端原则](ch12.md#数据库的端到端原则)”中进一步探讨这个问题。

20
ch9.md
View File

@ -145,9 +145,9 @@
>
> **线性一致性Linearizability**是读取和写入寄存器(单个对象)的**新鲜度保证**。它不会将操作组合为事务,因此它也不会阻止写入偏差等问题(请参阅“[写入偏差和幻读](ch7.md#写入偏斜与幻读)”),除非采取其他措施(例如[物化冲突](ch7.md#物化冲突))。
>
> 一个数据库可以提供可串行化和线性一致性,这种组合被称为严格的可串行化或**强的单副本可串行化strong-1SR**【4,13】。基于两阶段锁定的可串行化实现请参阅“[两阶段锁定2PL](ch7.md#两阶段锁定2PL)”一节)或**真的串行执行**(请参阅第“[真的串行执行](ch7.md#真的串行执行)”)通常是线性一致性的。
> 一个数据库可以提供可串行化和线性一致性,这种组合被称为严格的可串行化或**强的单副本可串行化strong-1SR**【4,13】。基于两阶段锁定的可串行化实现请参阅“[两阶段锁定](ch7.md#两阶段锁定)”一节)或**真的串行执行**(请参阅第“[真的串行执行](ch7.md#真的串行执行)”)通常是线性一致性的。
>
> 但是,可串行化的快照隔离(请参阅“[可串行化快照隔离SSI](ch7.md#可串行化快照隔离SSI)”)不是线性一致性的:按照设计,它从一致的快照中进行读取,以避免读者和写者之间的锁竞争。一致性快照的要点就在于**它不会包括该快照之后的写入**,因此从快照读取不是线性一致性的。
> 但是,可串行化的快照隔离(请参阅“[可串行化快照隔离](ch7.md#可串行化快照隔离)”)不是线性一致性的:按照设计,它从一致的快照中进行读取,以避免读者和写者之间的锁竞争。一致性快照的要点就在于**它不会包括该快照之后的写入**,因此从快照读取不是线性一致性的。
### 依赖线性一致性
@ -323,7 +323,7 @@
* [图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)中,爱丽丝被允许离班,因为事务认为鲍勃仍在值班,反之亦然。在这种情况下,离班的动作因果依赖于对当前值班情况的观察。[可串行化快照隔离SSI](ch7.md#可串行化快照隔离SSI)通过跟踪事务之间的因果依赖来检测写偏差。
* 事务之间**写偏差write skew**的例子(请参阅“[写入偏斜与幻读](ch7.md#写入偏斜与幻读)”)也说明了因果依赖:在[图7-8](img/fig7-8.png)中,爱丽丝被允许离班,因为事务认为鲍勃仍在值班,反之亦然。在这种情况下,离班的动作因果依赖于对当前值班情况的观察。[可串行化快照隔离](ch7.md#可串行化快照隔离)通过跟踪事务之间的因果依赖来检测写偏差。
* 在爱丽丝和鲍勃看球的例子中([图9-1](img/fig9-1.png)),在听到爱丽丝惊呼比赛结果后,鲍勃从服务器得到陈旧结果的事实违背了因果关系:爱丽丝的惊呼因果依赖于得分宣告,所以鲍勃应该也能在听到爱丽斯惊呼后查询到比分。相同的模式在“[跨信道的时序依赖](#跨信道的时序依赖)”一节中,以“图像大小调整服务”的伪装再次出现。
因果关系对事件施加了一种**顺序**:因在果之前;消息发送在消息收取之前。而且就像现实生活中一样,一件事会导致另一件事:某个节点读取了一些数据然后写入一些结果,另一个节点读取其写入的内容,并依次写入一些其他内容,等等。这些因果依赖的操作链定义了系统中的因果顺序,即,什么在什么之前发生。
@ -378,7 +378,7 @@
用于确定*哪些操作发生在其他操作之前* 的技术,与我们在“[检测并发写入](ch5.md#检测并发写入)”中所讨论的内容类似。那一节讨论了无领导者数据存储中的因果性为了防止丢失更新我们需要检测到对同一个键的并发写入。因果一致性则更进一步它需要跟踪整个数据库中的因果依赖而不仅仅是一个键。可以推广版本向量以解决此类问题【54】。
为了确定因果顺序,数据库需要知道应用读取了哪个版本的数据。这就是为什么在 [图5-13 ](img/fig5-13.png)中来自先前操作的版本号在写入时被传回到数据库的原因。在SSI 的冲突检测中会出现类似的想法,如“[可串行化快照隔离SSI](ch7.md#可串行化快照隔离SSI)”中所述:当事务要提交时,数据库将检查它所读取的数据版本是否仍然是最新的。为此,数据库跟踪哪些数据被哪些事务所读取。
为了确定因果顺序,数据库需要知道应用读取了哪个版本的数据。这就是为什么在 [图5-13 ](img/fig5-13.png)中来自先前操作的版本号在写入时被传回到数据库的原因。在SSI 的冲突检测中会出现类似的想法,如“[可串行化快照隔离](ch7.md#可串行化快照隔离)”中所述:当事务要提交时,数据库将检查它所读取的数据版本是否仍然是最新的。为此,数据库跟踪哪些数据被哪些事务所读取。
@ -555,7 +555,7 @@
***原子提交***
在支持跨多节点或跨多分区事务的数据库中一个事务可能在某些节点上失败但在其他节点上成功。如果我们想要维护事务的原子性就ACID而言请参阅“[原子性Atomicity](ch7.md#原子性Atomicity)”),我们必须让所有节点对事务的结果达成一致:要么全部中止/回滚(如果出现任何错误),要么它们全部提交(如果没有出错)。这个共识的例子被称为**原子提交atomic commit**问题[^xii]。
在支持跨多节点或跨多分区事务的数据库中一个事务可能在某些节点上失败但在其他节点上成功。如果我们想要维护事务的原子性就ACID而言请参阅“[原子性](ch7.md#原子性)”),我们必须让所有节点对事务的结果达成一致:要么全部中止/回滚(如果出现任何错误),要么它们全部提交(如果没有出错)。这个共识的例子被称为**原子提交atomic commit**问题[^xii]。
[^xii]: 原子提交的形式化与共识稍有不同:原子事务只有在**所有**参与者投票提交的情况下才能提交,如果有任何参与者需要中止,则必须中止。 共识则允许就**任意一个**被参与者提出的候选值达成一致。 然而原子提交和共识可以相互简化为对方【70,71】。 **非阻塞**原子提交则要比共识更为困难 —— 请参阅“[三阶段提交](#三阶段提交)”。
@ -574,7 +574,7 @@
### 原子提交与两阶段提交2PC
### 原子提交与两阶段提交
在[第七章](ch7.md)中我们了解到,事务原子性的目的是在多次写操作中途出错的情况下,提供一种简单的语义。事务的结果要么是成功提交,在这种情况下,事务的所有写入都是持久化的;要么是中止,在这种情况下,事务的所有写入都被回滚(即撤消或丢弃)。
@ -612,7 +612,7 @@
> #### 不要把2PC和2PL搞混了
>
> 两阶段提交2PC和两阶段锁定请参阅“[两阶段锁定2PL](ch7.md#两阶段锁定2PL)”)是两个完全不同的东西。 2PC在分布式数据库中提供原子提交而2PL提供可串行化的隔离等级。为了避免混淆最好把它们看作完全独立的概念并忽略名称中不幸的相似性。
> 两阶段提交2PC和两阶段锁定请参阅“[两阶段锁定](ch7.md#两阶段锁定)”)是两个完全不同的东西。 2PC在分布式数据库中提供原子提交而2PL提供可串行化的隔离等级。为了避免混淆最好把它们看作完全独立的概念并忽略名称中不幸的相似性。
2PC使用一个通常不会出现在单节点事务中的新组件**协调者coordinator**(也称为**事务管理器transaction manager**。协调者通常在请求事务的相同应用进程中以库的形式实现例如嵌入在Java EE容器中但也可以是单独的进程或服务。这种协调者的例子包括Narayana、JOTM、BTM或MSDTC。
@ -709,7 +709,7 @@
为什么我们这么关心存疑事务?系统的其他部分就不能继续正常工作,无视那些终将被清理的存疑事务吗?
问题在于**锁locking**。正如在“[读已提交](ch7.md#读已提交)”中所讨论的那样,数据库事务通常获取待修改的行上的**行级排他锁**,以防止脏写。此外,如果要使用可串行化的隔离等级,则使用两阶段锁定的数据库也必须为事务所读取的行加上共享锁(请参阅“[两阶段锁定2PL](ch7.md#两阶段锁定2PL)”)。
问题在于**锁locking**。正如在“[读已提交](ch7.md#读已提交)”中所讨论的那样,数据库事务通常获取待修改的行上的**行级排他锁**,以防止脏写。此外,如果要使用可串行化的隔离等级,则使用两阶段锁定的数据库也必须为事务所读取的行加上共享锁(请参阅“[两阶段锁定](ch7.md#两阶段锁定)”)。
在事务提交或中止之前,数据库不能释放这些锁(如[图9-9](img/fig9-9.png)中的阴影区域所示。因此在使用两阶段提交时事务必须在整个存疑期间持有这些锁。如果协调者已经崩溃需要20分钟才能重启那么这些锁将会被持有20分钟。如果协调者的日志由于某种原因彻底丢失这些锁将被永久持有 —— 或至少在管理员手动解决该情况之前。
@ -731,7 +731,7 @@
* 如果协调者没有复制,而是只在单台机器上运行,那么它是整个系统的失效单点(因为它的失效会导致其他应用服务器阻塞在存疑事务持有的锁上)。令人惊讶的是,许多协调者实现默认情况下并不是高可用的,或者只有基本的复制支持。
* 许多服务器端应用都是使用无状态模式开发的受HTTP的青睐所有持久状态都存储在数据库中因此具有应用服务器可随意按需添加删除的优点。但是当协调者成为应用服务器的一部分时它会改变部署的性质。突然间协调者的日志成为持久系统状态的关键部分—— 与数据库本身一样重要,因为协调者日志是为了在崩溃后恢复存疑事务所必需的。这样的应用服务器不再是无状态的了。
* 由于XA需要兼容各种数据系统因此它必须是所有系统的最小公分母。例如它不能检测不同系统间的死锁因为这将需要一个标准协议来让系统交换每个事务正在等待的锁的信息而且它无法与SSI请参阅[可串行化快照隔离SSI](ch7.md#可串行化快照隔离SSI ))协同工作,因为这需要一个跨系统定位冲突的协议。
* 由于XA需要兼容各种数据系统因此它必须是所有系统的最小公分母。例如它不能检测不同系统间的死锁因为这将需要一个标准协议来让系统交换每个事务正在等待的锁的信息而且它无法与SSI请参阅[可串行化快照隔离](ch7.md#可串行化快照隔离 ))协同工作,因为这需要一个跨系统定位冲突的协议。
* 对于数据库内部的分布式事务不是XA限制没有这么大 —— 例如分布式版本的SSI是可能的。然而仍然存在问题2PC成功提交一个事务需要所有参与者的响应。因此如果系统的**任何**部分损坏,事务也会失败。因此,分布式事务又有**扩大失效amplifying failures**的趋势,这又与我们构建容错系统的目标背道而驰。
这些事实是否意味着我们应该放弃保持几个系统相互一致的所有希望?不完全是 —— 还有其他的办法,可以让我们在没有异构分布式事务的痛苦的情况下实现同样的事情。我们将在[第十一章](ch11.md) 和[第十二章](ch12.md) 回到这些话题。但首先,我们应该概括一下关于**共识**的话题。
@ -794,7 +794,7 @@
视图戳复制Raft和Zab直接实现了全序广播因为这样做比重复**一次一值one value a time**的共识更高效。在Paxos的情况下这种优化被称为Multi-Paxos。
#### 单领导者复制共识
#### 单领导者复制共识
在[第五章](ch5.md)中,我们讨论了单领导者复制(请参阅“[领导者与追随者](ch5.md#领导者与追随者)”),它将所有的写入操作都交给主库,并以相同的顺序将它们应用到从库,从而使副本保持在最新状态。这实际上不就是一个全序广播吗?为什么我们在[第五章](ch5.md)里一点都没担心过共识问题呢?

View File

@ -18,7 +18,7 @@
1.在并发操作的上下文中:描述一个在单个时间点看起来生效的操作,所以另一个并发进程永远不会遇到处于“半完成”状态的操作。另见隔离。
2.在事务的上下文中:将一些写入操作分为一组,这组写入要么全部提交成功,要么遇到错误时全部回滚。请参阅“[原子性Atomicity](ch7.md#原子性Atomicity)”和“[原子提交与两阶段提交2PC](ch9.md#原子提交与两阶段提交2PC)”。
2.在事务的上下文中:将一些写入操作分为一组,这组写入要么全部提交成功,要么遇到错误时全部回滚。请参阅“[原子性](ch7.md#原子性)”和“[原子提交与两阶段提交](ch9.md#原子提交与两阶段提交)”。
@ -108,7 +108,7 @@
### 持久durable
以某种方式存储数据,即使发生各种故障,也不会丢失数据。请参阅“[持久性Durability](ch7.md#持久性Durability)”。
以某种方式存储数据,即使发生各种故障,也不会丢失数据。请参阅“[持久性](ch7.md#持久性)”。
@ -174,7 +174,7 @@
### 隔离性isolation
在事务上下文中,用于描述并发执行事务的互相干扰程度。串行运行具有最强的隔离性,不过其它程度的隔离也通常被使用。请参阅“[隔离性Isolation](ch7.md#隔离性Isolation)”。
在事务上下文中,用于描述并发执行事务的互相干扰程度。串行运行具有最强的隔离性,不过其它程度的隔离也通常被使用。请参阅“[隔离性](ch7.md#隔离性)”。
@ -204,7 +204,7 @@
### 锁lock
一种保证只有一个线程、节点或事务可以访问的机制,如果其它线程、节点或事务想访问相同元素,则必须等待锁被释放。请参阅“[两阶段锁定2PL](ch7.md#两阶段锁定2PL)”和“[领导者和锁](ch8.md#领导者和锁)”。
一种保证只有一个线程、节点或事务可以访问的机制,如果其它线程、节点或事务想访问相同元素,则必须等待锁被释放。请参阅“[两阶段锁定](ch7.md#两阶段锁定)”和“[领导者和锁](ch8.md#领导者和锁)”。
@ -362,13 +362,13 @@
### 两阶段提交2PC, two-phase commit
一种确保多个数据库节点全部提交或全部中止事务的算法。 请参阅[原子提交与两阶段提交2PC](ch9.md#原子提交与两阶段提交2PC)”。
一种确保多个数据库节点全部提交或全部中止事务的算法。 请参阅[原子提交与两阶段提交](ch9.md#原子提交与两阶段提交)”。
### 两阶段锁定2PL, two-phase locking
一种用于实现可序列化隔离的算法,该算法通过事务获取对其读取或写入的所有数据的锁,直到事务结束。 请参阅“[两阶段锁定2PL](ch7.md#两阶段锁定2PL)”。
一种用于实现可串行化隔离的算法,该算法通过事务获取对其读取或写入的所有数据的锁,直到事务结束。 请参阅“[两阶段锁定](ch7.md#两阶段锁定)”。

View File

@ -54,7 +54,7 @@
如果訊息被快取在佇列中那麼理解佇列增長會發生什麼是很重要的。當佇列裝不進記憶體時系統會崩潰嗎還是將訊息寫入磁碟如果是這樣磁碟訪問又會如何影響訊息傳遞系統的效能【6】
2. **如果節點崩潰或暫時離線,會發生什麼情況? —— 是否會有訊息丟失?**與資料庫一樣,永續性可能需要寫入磁碟和/或複製的某種組合(請參閱“[複製和永續性](ch7.md#複製和永續性)”),這是有代價的。如果你能接受有時訊息會丟失,則可能在同一硬體上獲得更高的吞吐量和更低的延遲。
2. **如果節點崩潰或暫時離線,會發生什麼情況? —— 是否會有訊息丟失?**與資料庫一樣,永續性可能需要寫入磁碟和/或複製的某種組合(請參閱“[複製與永續性](ch7.md#複製與永續性)”),這是有代價的。如果你能接受有時訊息會丟失,則可能在同一硬體上獲得更高的吞吐量和更低的延遲。
是否可以接受訊息丟失取決於應用。例如對於週期傳輸的感測器讀數和指標偶爾丟失的資料點可能並不重要因為更新的值會在短時間內發出。但要注意如果大量的訊息被丟棄可能無法立刻意識到指標已經不正確了【7】。如果你正在對事件計數那麼它們能夠可靠送達是更重要的因為每個丟失的訊息都意味著使計數器的錯誤擴大。
@ -231,7 +231,7 @@
除非有一些額外的併發檢測機制,例如我們在“[檢測併發寫入](ch5.md#檢測併發寫入)”中討論的版本向量,否則你甚至不會意識到發生了併發寫入 —— 一個值將簡單地以無提示方式覆蓋另一個值。
雙重寫入的另一個問題是,其中一個寫入可能會失敗,而另一個成功。這是一個容錯問題,而不是一個併發問題,但也會造成兩個系統互相不一致的結果。確保它們要麼都成功要麼都失敗,是原子提交問題的一個例子,解決這個問題的代價是昂貴的(請參閱“[原子提交與兩階段提交2PC](ch7.md#原子提交與兩階段提交2PC)”)。
雙重寫入的另一個問題是,其中一個寫入可能會失敗,而另一個成功。這是一個容錯問題,而不是一個併發問題,但也會造成兩個系統互相不一致的結果。確保它們要麼都成功要麼都失敗,是原子提交問題的一個例子,解決這個問題的代價是昂貴的(請參閱“[原子提交與兩階段提交](ch7.md#原子提交與兩階段提交)”)。
如果你只有一個單領導者複製的資料庫,那麼這個領導者決定了寫入順序,而狀態機複製方法可以在資料庫副本上工作。然而,在[圖11-4](../img/fig11-4.png)中,沒有單個主庫:資料庫可能有一個領導者,搜尋索引也可能有一個領導者,但是兩者都不追隨對方,所以可能會發生衝突(請參閱“[多主複製](ch5.md#多主複製)“)。
@ -321,7 +321,7 @@
* 用於記錄更新的CDC事件通常包含記錄的**完整新版本**,因此主鍵的當前值完全由該主鍵的最近事件確定,而日誌壓縮可以丟棄相同主鍵的先前事件。
* 另一方面,事件溯源在更高層次進行建模:事件通常表示使用者操作的意圖,而不是因為操作而發生的狀態更新機制。在這種情況下,後面的事件通常不會覆蓋先前的事件,所以你需要完整的歷史事件來重新構建最終狀態。這裡進行同樣的日誌壓縮是不可能的。
使用事件溯源的應用通常有一些機制,用於儲存從事件日誌中匯出的當前狀態快照,因此它們不需要重複處理完整的日誌。然而這只是一種效能最佳化,用來加速讀取,提高從崩潰中恢復的速度;真正的目的是系統能夠永久儲存所有原始事件,並在需要時重新處理完整的事件日誌。我們將在“[不變性的限制](#不變性的限制)”中討論這個假設。
使用事件溯源的應用通常有一些機制,用於儲存從事件日誌中匯出的當前狀態快照,因此它們不需要重複處理完整的日誌。然而這只是一種效能最佳化,用來加速讀取,提高從崩潰中恢復的速度;真正的目的是系統能夠永久儲存所有原始事件,並在需要時重新處理完整的事件日誌。我們將在“[不變性的侷限性](#不變性的侷限性)”中討論這個假設。
#### 命令和事件
@ -392,7 +392,7 @@ $$
如果事件日誌與應用狀態以相同的方式分割槽例如處理分割槽3中的客戶事件只需要更新分割槽3中的應用狀態那麼直接使用單執行緒日誌消費者就不需要寫入併發控制了。它從設計上一次只處理一個事件請參閱“[真的序列執行](ch7.md#真的序列執行)”。日誌透過在分割槽中定義事件的序列順序消除了併發性的不確定性【24】。如果一個事件觸及多個狀態分割槽那麼需要做更多的工作我們將在[第十二章](ch12.md)討論。
#### 不變性的限制
#### 不變性的侷限性
許多不使用事件溯源模型的系統也還是依賴不可變性:各種資料庫在內部使用不可變的資料結構或多版本資料來支援時間點快照(請參閱“[索引和快照隔離](ch7.md#索引和快照隔離)” )。 GitMercurial和Fossil等版本控制系統也依靠不可變的資料來儲存檔案的版本歷史記錄。
@ -633,7 +633,7 @@ GROUP BY follows.follower_id
在本章的最後一節中,讓我們看一看流處理是如何容錯的。我們在[第十章](ch10.md)中看到批處理框架可以很容易地容錯如果MapReduce作業中的任務失敗可以簡單地在另一臺機器上再次啟動並且丟棄失敗任務的輸出。這種透明的重試是可能的因為輸入檔案是不可變的每個任務都將其輸出寫入到HDFS上的獨立檔案中而輸出僅當任務成功完成後可見。
特別是,批處理容錯方法可確保批處理作業的輸出與沒有出錯的情況相同,即使實際上某些任務失敗了。看起來好像每條輸入記錄都被處理了恰好一次 —— 沒有記錄被跳過,而且沒有記錄被處理兩次。儘管重啟任務意味著實際上可能會多次處理記錄,但輸出中的可見效果看上去就像只處理過一次。這個原則被稱為**恰好一次語義exactly-once semantics**,儘管**效一次effectively-once** 可能會是一個更寫實的術語【90】。
特別是,批處理容錯方法可確保批處理作業的輸出與沒有出錯的情況相同,即使實際上某些任務失敗了。看起來好像每條輸入記錄都被處理了恰好一次 —— 沒有記錄被跳過,而且沒有記錄被處理兩次。儘管重啟任務意味著實際上可能會多次處理記錄,但輸出中的可見效果看上去就像只處理過一次。這個原則被稱為**恰好一次語義exactly-once semantics**,儘管**效一次effectively-once** 可能會是一個更寫實的術語【90】。
在流處理中也出現了同樣的容錯問題,但是處理起來沒有那麼直觀:等待某個任務完成之後再使其輸出可見並不是一個可行選項,因為你永遠無法處理完一個無限的流。

View File

@ -47,19 +47,19 @@
### ACID的含義
事務所提供的安全保證通常由眾所周知的首字母縮略詞ACID來描述ACID代表**原子性Atomicity****一致性Consistency****隔離性Isolation**和**永續性Durability**。它由TheoHärder和Andreas Reuter於1983年提出旨在為資料庫中的容錯機制建立精確的術語。
事務所提供的安全保證通常由眾所周知的首字母縮略詞ACID來描述ACID代表**原子性Atomicity****一致性Consistency****隔離性Isolation**和**永續性Durability**。它由Theo Härder和Andreas Reuter於1983年提出旨在為資料庫中的容錯機制建立精確的術語。
但實際上不同資料庫的ACID實現並不相同。例如我們將會看到關於**隔離性Isolation** 的含義就有許多含糊不清【8】。高層次上的想法很美好但魔鬼隱藏在細節裡。今天當一個系統聲稱自己“符合ACID”時實際上能期待的是什麼保證並不清楚。不幸的是ACID現在幾乎已經變成了一個營銷術語。
但實際上不同資料庫的ACID實現並不相同。例如我們將會看到關於**隔離性**的含義就有許多含糊不清【8】。高層次上的想法很美好但魔鬼隱藏在細節裡。今天當一個系統聲稱自己“符合ACID”時實際上能期待的是什麼保證並不清楚。不幸的是ACID現在幾乎已經變成了一個營銷術語。
不符合ACID標準的系統有時被稱為BASE它代表**基本可用性Basically Available****軟狀態Soft State**和**最終一致性Eventual consistency**【9】這比ACID的定義更加模糊似乎BASE的唯一合理的定義是“不是ACID”即它幾乎可以代表任何你想要的東西。
讓我們深入瞭解原子性,一致性,隔離性和永續性的定義,這可以讓我們提煉出事務的思想。
#### 原子性Atomicity
#### 原子性
一般來說,原子是指不能分解成小部分的東西。這個詞在計算機的不同領域中意味著相似但又微妙不同的東西。例如,在多執行緒程式設計中,如果一個執行緒執行一個原子操作,這意味著另一個執行緒無法看到該操作的一半結果。系統只能處於操作之前或操作之後的狀態,而不是介於兩者之間的狀態。
相比之下ACID的原子性並**不**是關於 **併發concurrent** 的。它並不是在描述如果幾個程序試圖同時訪問相同的資料會發生什麼情況,這種情況包含在縮寫 ***I*** 中,即[**隔離性Isolation**](#隔離性Isolation)
相比之下ACID的原子性並**不**是關於 **併發concurrent** 的。它並不是在描述如果幾個程序試圖同時訪問相同的資料會發生什麼情況,這種情況包含在縮寫 ***I*** 中,即[**隔離性**](#隔離性)
ACID的原子性描述了當客戶想進行多次寫入但在一些寫操作處理完之後出現故障的情況。例如程序崩潰網路連線中斷磁碟變滿或者某種完整性約束被違反。如果這些寫操作被分組到一個原子事務中並且該事務由於錯誤而不能完成提交則該事務將被中止並且資料庫必須丟棄或撤消該事務中迄今為止所做的任何寫入。
@ -67,7 +67,7 @@ ACID的原子性描述了當客戶想進行多次寫入但在一些寫操作
ACID原子性的定義特徵是**能夠在錯誤時中止事務,丟棄該事務進行的所有寫入變更的能力。** 或許 **可中止性abortability** 是更好的術語,但本書將繼續使用原子性,因為這是慣用詞。
#### 一致性Consistency
#### 一致性
一致性這個詞被賦予太多含義:
@ -86,7 +86,7 @@ ACID一致性的概念是**對資料的一組特定約束必須始終成立**
[^i]: 喬·海勒斯坦Joe Hellerstein指出在論Härder與Reuter的論文中“ACID中的C”是被“扔進去湊縮寫單詞的”【7】而且那時候大家都不怎麼在乎一致性。
#### 隔離性Isolation
#### 隔離性
大多數資料庫都會同時被多個客戶端訪問。如果它們各自讀寫資料庫的不同部分,這是沒有問題的,但是如果它們訪問相同的資料庫記錄,則可能會遇到**併發**問題(**競爭條件race conditions**)。
@ -100,7 +100,7 @@ ACID意義上的隔離性意味著**同時執行的事務是相互隔離的**
然而實踐中很少會使用可序列的隔離因為它有效能損失。一些流行的資料庫如Oracle 11g甚至沒有實現它。在Oracle中有一個名為“可序列的”隔離級別但實際上它實現了一種叫做**快照隔離snapshot isolation** 的功能,**這是一種比可序列化更弱的保證**【8,11】。我們將在“[弱隔離級別](#弱隔離級別)”中研究快照隔離和其他形式的隔離。
#### 永續性Durability
#### 永續性
資料庫系統的目的是,提供一個安全的地方儲存資料,而不用擔心丟失。**永續性** 是一個承諾,即一旦事務成功完成,即使發生硬體故障或資料庫崩潰,寫入的任何資料也不會丟失。
@ -108,7 +108,7 @@ ACID意義上的隔離性意味著**同時執行的事務是相互隔離的**
如“[可靠性](ch1.md#可靠性)”一節所述,**完美的永續性是不存在的** :如果所有硬碟和所有備份同時被銷燬,那顯然沒有任何資料庫能救得了你。
> #### 複製永續性
> #### 複製永續性
>
> 在歷史上永續性意味著寫入歸檔磁帶。後來它被理解為寫入磁碟或SSD。再後來它又有了新的內涵即“複製replication”。哪種實現更好一些
>
@ -161,7 +161,7 @@ SELECT COUNT*FROM emails WHERE recipient_id = 2 AND unread_flag = true
多物件事務需要某種方式來確定哪些讀寫操作屬於同一個事務。在關係型資料庫中通常基於客戶端與資料庫伺服器的TCP連線在任何特定連線上`BEGIN TRANSACTION` 和 `COMMIT` 語句之間的所有內容,被認為是同一事務的一部分.[^iii]
[^iii]: 這並不完美。如果TCP連線中斷則事務必須中止。如果中斷髮生在客戶端請求提交之後但在伺服器確認提交發生之前客戶端並不知道事務是否已提交。為了解決這個問題事務管理器可以透過一個唯一事務識別符號來對操作進行分組這個識別符號並未繫結到特定TCP連線。後續再“[資料庫端到端的爭論](ch12.md#資料庫端到端的爭論)”一節將回到這個主題。
[^iii]: 這並不完美。如果TCP連線中斷則事務必須中止。如果中斷髮生在客戶端請求提交之後但在伺服器確認提交發生之前客戶端並不知道事務是否已提交。為了解決這個問題事務管理器可以透過一個唯一事務識別符號來對操作進行分組這個識別符號並未繫結到特定TCP連線。後續再“[資料庫的端到端原則](ch12.md#資料庫的端到端原則)”一節將回到這個主題。
另一方面許多非關係資料庫並沒有將這些操作組合在一起的方法。即使存在多物件API例如某鍵值儲存可能具有在一個操作中更新幾個鍵的multi-put操作但這並不一定意味著它具有事務語義該命令可能在一些鍵上成功在其他的鍵上失敗使資料庫處於部分更新的狀態。
@ -208,7 +208,7 @@ SELECT COUNT*FROM emails WHERE recipient_id = 2 AND unread_flag = true
- 如果事務實際上成功了,但是在伺服器試圖向客戶端確認提交成功時網路發生故障(所以客戶端認為提交失敗了),那麼重試事務會導致事務被執行兩次——除非你有一個額外的應用級除重機制。
- 如果錯誤是由於負載過大造成的,則重試事務將使問題變得更糟,而不是更好。為了避免這種正反饋迴圈,可以限制重試次數,使用指數退避演算法,並單獨處理與過載相關的錯誤(如果允許)。
- 僅在臨時性錯誤(例如,由於死鎖,異常情況,臨時性網路中斷和故障切換)後才值得重試。在發生永久性錯誤(例如,違反約束)之後重試是毫無意義的。
- 如果事務在資料庫之外也有副作用,即使事務被中止,也可能發生這些副作用。例如,如果你正在傳送電子郵件,那你肯定不希望每次重試事務時都重新發送電子郵件。如果你想確保幾個不同的系統一起提交或放棄,**兩階段提交2PC, two-phase commit** 可以提供幫助(“[原子提交與兩階段提交2PC](ch9.md#原子提交與兩階段提交2PC)”中將討論這個問題)。
- 如果事務在資料庫之外也有副作用,即使事務被中止,也可能發生這些副作用。例如,如果你正在傳送電子郵件,那你肯定不希望每次重試事務時都重新發送電子郵件。如果你想確保幾個不同的系統一起提交或放棄,**兩階段提交2PC, two-phase commit** 可以提供幫助(“[原子提交與兩階段提交](ch9.md#原子提交與兩階段提交)”中將討論這個問題)。
- 如果客戶端程序在重試中失效,任何試圖寫入資料庫的資料都將丟失。
## 弱隔離級別
@ -593,8 +593,8 @@ COMMIT;
但如果可序列化隔離級別比弱隔離級別的爛攤子要好得多,那為什麼沒有人見人愛?為了回答這個問題,我們需要看看實現可序列化的選項,以及它們如何執行。目前大多數提供可序列化的資料庫都使用了三種技術之一,本章的剩餘部分將會介紹這些技術。
- 字面意義上地序列順序執行事務(請參閱“[真的序列執行](#真的序列執行)”)
- **兩階段鎖定2PL, two-phase locking**,幾十年來唯一可行的選擇。(請參閱“[兩階段鎖定2PL](#兩階段鎖定2PL)”)
- 樂觀併發控制技術,例如**可序列化快照隔離serializable snapshot isolation**(請參閱“[可序列化快照隔離SSI](#可序列化快照隔離SSI)”
- **兩階段鎖定2PL, two-phase locking**,幾十年來唯一可行的選擇。(請參閱“[兩階段鎖定](#兩階段鎖定)”)
- 樂觀併發控制技術,例如**可序列化快照隔離serializable snapshot isolation**(請參閱“[可序列化快照隔離](#可序列化快照隔離)”
現在將主要在單節點資料庫的背景下討論這些技術;在[第九章](ch9.md)中,我們將研究如何將它們推廣到涉及分散式系統中多個節點的事務。
@ -664,7 +664,7 @@ VoltDB還使用儲存過程進行復制但不是將事務的寫入結果從
[^x]: 如果事務需要訪問不在記憶體中的資料,最好的解決方案可能是中止事務,非同步地將資料提取到記憶體中,同時繼續處理其他事務,然後在資料載入完畢時重新啟動事務。這種方法被稱為**反快取anti-caching**,正如前面在“[在記憶體中儲存一切](ch3.md#在記憶體中儲存一切)”中所述。
### 兩階段鎖定2PL
### 兩階段鎖定
大約30年來在資料庫中只有一種廣泛使用的序列化演算法**兩階段鎖定2PLtwo-phase locking** [^xi]
@ -748,7 +748,7 @@ WHERE room_id = 123 AND
如果沒有可以掛載間隙鎖的索引,資料庫可以退化到使用整個表上的共享鎖。這對效能不利,因為它會阻止所有其他事務寫入表格,但這是一個安全的回退位置。
### 可序列化快照隔離SSI
### 可序列化快照隔離
本章描繪了資料庫中併發控制的黯淡畫面。一方面我們實現了效能不好2PL或者伸縮性不好序列執行的可序列化隔離級別。另一方面我們有效能良好的弱隔離級別但容易出現各種競爭條件丟失更新寫入偏差幻讀等。序列化的隔離級別和高效能是從根本上相互矛盾的嗎

View File

@ -86,7 +86,7 @@
>
> 雖然這個系統可以比它的底層部分更可靠但它的可靠性總是有限的。例如糾錯碼可以處理少量的單位元錯誤但是如果你的訊號被幹擾所淹沒那麼透過通道可以得到多少資料是有根本性的限制的【13】。 TCP可以隱藏資料包的丟失重複和重新排序但是它不能神奇地消除網路中的延遲。
>
> 雖然更可靠的高階系統並不完美,但它仍然有用,因為它處理了一些棘手的低階錯誤,所以其餘的錯誤通常更容易推理和處理。我們將在“[資料庫端到端的爭論](ch12.md#資料庫端到端的爭論)”中進一步探討這個問題。
> 雖然更可靠的高階系統並不完美,但它仍然有用,因為它處理了一些棘手的低階錯誤,所以其餘的錯誤通常更容易推理和處理。我們將在“[資料庫的端到端原則](ch12.md#資料庫的端到端原則)”中進一步探討這個問題。

View File

@ -145,9 +145,9 @@
>
> **線性一致性Linearizability**是讀取和寫入暫存器(單個物件)的**新鮮度保證**。它不會將操作組合為事務,因此它也不會阻止寫入偏差等問題(請參閱“[寫入偏差和幻讀](ch7.md#寫入偏斜與幻讀)”),除非採取其他措施(例如[物化衝突](ch7.md#物化衝突))。
>
> 一個數據庫可以提供可序列化和線性一致性,這種組合被稱為嚴格的可序列化或**強的單副本可序列化strong-1SR**【4,13】。基於兩階段鎖定的可序列化實現請參閱“[兩階段鎖定2PL](ch7.md#兩階段鎖定2PL)”一節)或**真的序列執行**(請參閱第“[真的序列執行](ch7.md#真的序列執行)”)通常是線性一致性的。
> 一個數據庫可以提供可序列化和線性一致性,這種組合被稱為嚴格的可序列化或**強的單副本可序列化strong-1SR**【4,13】。基於兩階段鎖定的可序列化實現請參閱“[兩階段鎖定](ch7.md#兩階段鎖定)”一節)或**真的序列執行**(請參閱第“[真的序列執行](ch7.md#真的序列執行)”)通常是線性一致性的。
>
> 但是,可序列化的快照隔離(請參閱“[可序列化快照隔離SSI](ch7.md#可序列化快照隔離SSI)”)不是線性一致性的:按照設計,它從一致的快照中進行讀取,以避免讀者和寫者之間的鎖競爭。一致性快照的要點就在於**它不會包括該快照之後的寫入**,因此從快照讀取不是線性一致性的。
> 但是,可序列化的快照隔離(請參閱“[可序列化快照隔離](ch7.md#可序列化快照隔離)”)不是線性一致性的:按照設計,它從一致的快照中進行讀取,以避免讀者和寫者之間的鎖競爭。一致性快照的要點就在於**它不會包括該快照之後的寫入**,因此從快照讀取不是線性一致性的。
### 依賴線性一致性
@ -323,7 +323,7 @@
* [圖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)中,愛麗絲被允許離班,因為事務認為鮑勃仍在值班,反之亦然。在這種情況下,離班的動作因果依賴於對當前值班情況的觀察。[可序列化快照隔離SSI](ch7.md#可序列化快照隔離SSI)透過跟蹤事務之間的因果依賴來檢測寫偏差。
* 事務之間**寫偏差write skew**的例子(請參閱“[寫入偏斜與幻讀](ch7.md#寫入偏斜與幻讀)”)也說明了因果依賴:在[圖7-8](../img/fig7-8.png)中,愛麗絲被允許離班,因為事務認為鮑勃仍在值班,反之亦然。在這種情況下,離班的動作因果依賴於對當前值班情況的觀察。[可序列化快照隔離](ch7.md#可序列化快照隔離)透過跟蹤事務之間的因果依賴來檢測寫偏差。
* 在愛麗絲和鮑勃看球的例子中([圖9-1](../img/fig9-1.png)),在聽到愛麗絲驚呼比賽結果後,鮑勃從伺服器得到陳舊結果的事實違背了因果關係:愛麗絲的驚呼因果依賴於得分宣告,所以鮑勃應該也能在聽到愛麗斯驚呼後查詢到比分。相同的模式在“[跨通道的時序依賴](#跨通道的時序依賴)”一節中,以“影象大小調整服務”的偽裝再次出現。
因果關係對事件施加了一種**順序**:因在果之前;訊息傳送在訊息收取之前。而且就像現實生活中一樣,一件事會導致另一件事:某個節點讀取了一些資料然後寫入一些結果,另一個節點讀取其寫入的內容,並依次寫入一些其他內容,等等。這些因果依賴的操作鏈定義了系統中的因果順序,即,什麼在什麼之前發生。
@ -378,7 +378,7 @@
用於確定*哪些操作發生在其他操作之前* 的技術,與我們在“[檢測併發寫入](ch5.md#檢測併發寫入)”中所討論的內容類似。那一節討論了無領導者資料儲存中的因果性為了防止丟失更新我們需要檢測到對同一個鍵的併發寫入。因果一致性則更進一步它需要跟蹤整個資料庫中的因果依賴而不僅僅是一個鍵。可以推廣版本向量以解決此類問題【54】。
為了確定因果順序,資料庫需要知道應用讀取了哪個版本的資料。這就是為什麼在 [圖5-13 ](../img/fig5-13.png)中來自先前操作的版本號在寫入時被傳回到資料庫的原因。在SSI 的衝突檢測中會出現類似的想法,如“[可序列化快照隔離SSI](ch7.md#可序列化快照隔離SSI)”中所述:當事務要提交時,資料庫將檢查它所讀取的資料版本是否仍然是最新的。為此,資料庫跟蹤哪些資料被哪些事務所讀取。
為了確定因果順序,資料庫需要知道應用讀取了哪個版本的資料。這就是為什麼在 [圖5-13 ](../img/fig5-13.png)中來自先前操作的版本號在寫入時被傳回到資料庫的原因。在SSI 的衝突檢測中會出現類似的想法,如“[可序列化快照隔離](ch7.md#可序列化快照隔離)”中所述:當事務要提交時,資料庫將檢查它所讀取的資料版本是否仍然是最新的。為此,資料庫跟蹤哪些資料被哪些事務所讀取。
@ -555,7 +555,7 @@
***原子提交***
在支援跨多節點或跨多分割槽事務的資料庫中一個事務可能在某些節點上失敗但在其他節點上成功。如果我們想要維護事務的原子性就ACID而言請參閱“[原子性Atomicity](ch7.md#原子性Atomicity)”),我們必須讓所有節點對事務的結果達成一致:要麼全部中止/回滾(如果出現任何錯誤),要麼它們全部提交(如果沒有出錯)。這個共識的例子被稱為**原子提交atomic commit**問題[^xii]。
在支援跨多節點或跨多分割槽事務的資料庫中一個事務可能在某些節點上失敗但在其他節點上成功。如果我們想要維護事務的原子性就ACID而言請參閱“[原子性](ch7.md#原子性)”),我們必須讓所有節點對事務的結果達成一致:要麼全部中止/回滾(如果出現任何錯誤),要麼它們全部提交(如果沒有出錯)。這個共識的例子被稱為**原子提交atomic commit**問題[^xii]。
[^xii]: 原子提交的形式化與共識稍有不同:原子事務只有在**所有**參與者投票提交的情況下才能提交,如果有任何參與者需要中止,則必須中止。 共識則允許就**任意一個**被參與者提出的候選值達成一致。 然而原子提交和共識可以相互簡化為對方【70,71】。 **非阻塞**原子提交則要比共識更為困難 —— 請參閱“[三階段提交](#三階段提交)”。
@ -574,7 +574,7 @@
### 原子提交與兩階段提交2PC
### 原子提交與兩階段提交
在[第七章](ch7.md)中我們瞭解到,事務原子性的目的是在多次寫操作中途出錯的情況下,提供一種簡單的語義。事務的結果要麼是成功提交,在這種情況下,事務的所有寫入都是持久化的;要麼是中止,在這種情況下,事務的所有寫入都被回滾(即撤消或丟棄)。
@ -612,7 +612,7 @@
> #### 不要把2PC和2PL搞混了
>
> 兩階段提交2PC和兩階段鎖定請參閱“[兩階段鎖定2PL](ch7.md#兩階段鎖定2PL)”)是兩個完全不同的東西。 2PC在分散式資料庫中提供原子提交而2PL提供可序列化的隔離等級。為了避免混淆最好把它們看作完全獨立的概念並忽略名稱中不幸的相似性。
> 兩階段提交2PC和兩階段鎖定請參閱“[兩階段鎖定](ch7.md#兩階段鎖定)”)是兩個完全不同的東西。 2PC在分散式資料庫中提供原子提交而2PL提供可序列化的隔離等級。為了避免混淆最好把它們看作完全獨立的概念並忽略名稱中不幸的相似性。
2PC使用一個通常不會出現在單節點事務中的新元件**協調者coordinator**(也稱為**事務管理器transaction manager**。協調者通常在請求事務的相同應用程序中以庫的形式實現例如嵌入在Java EE容器中但也可以是單獨的程序或服務。這種協調者的例子包括Narayana、JOTM、BTM或MSDTC。
@ -709,7 +709,7 @@
為什麼我們這麼關心存疑事務?系統的其他部分就不能繼續正常工作,無視那些終將被清理的存疑事務嗎?
問題在於**鎖locking**。正如在“[讀已提交](ch7.md#讀已提交)”中所討論的那樣,資料庫事務通常獲取待修改的行上的**行級排他鎖**,以防止髒寫。此外,如果要使用可序列化的隔離等級,則使用兩階段鎖定的資料庫也必須為事務所讀取的行加上共享鎖(請參閱“[兩階段鎖定2PL](ch7.md#兩階段鎖定2PL)”)。
問題在於**鎖locking**。正如在“[讀已提交](ch7.md#讀已提交)”中所討論的那樣,資料庫事務通常獲取待修改的行上的**行級排他鎖**,以防止髒寫。此外,如果要使用可序列化的隔離等級,則使用兩階段鎖定的資料庫也必須為事務所讀取的行加上共享鎖(請參閱“[兩階段鎖定](ch7.md#兩階段鎖定)”)。
在事務提交或中止之前,資料庫不能釋放這些鎖(如[圖9-9](../img/fig9-9.png)中的陰影區域所示。因此在使用兩階段提交時事務必須在整個存疑期間持有這些鎖。如果協調者已經崩潰需要20分鐘才能重啟那麼這些鎖將會被持有20分鐘。如果協調者的日誌由於某種原因徹底丟失這些鎖將被永久持有 —— 或至少在管理員手動解決該情況之前。
@ -731,7 +731,7 @@
* 如果協調者沒有複製,而是隻在單臺機器上執行,那麼它是整個系統的失效單點(因為它的失效會導致其他應用伺服器阻塞在存疑事務持有的鎖上)。令人驚訝的是,許多協調者實現預設情況下並不是高可用的,或者只有基本的複製支援。
* 許多伺服器端應用都是使用無狀態模式開發的受HTTP的青睞所有持久狀態都儲存在資料庫中因此具有應用伺服器可隨意按需新增刪除的優點。但是當協調者成為應用伺服器的一部分時它會改變部署的性質。突然間協調者的日誌成為持久系統狀態的關鍵部分—— 與資料庫本身一樣重要,因為協調者日誌是為了在崩潰後恢復存疑事務所必需的。這樣的應用伺服器不再是無狀態的了。
* 由於XA需要相容各種資料系統因此它必須是所有系統的最小公分母。例如它不能檢測不同系統間的死鎖因為這將需要一個標準協議來讓系統交換每個事務正在等待的鎖的資訊而且它無法與SSI請參閱[可序列化快照隔離SSI](ch7.md#可序列化快照隔離SSI ))協同工作,因為這需要一個跨系統定位衝突的協議。
* 由於XA需要相容各種資料系統因此它必須是所有系統的最小公分母。例如它不能檢測不同系統間的死鎖因為這將需要一個標準協議來讓系統交換每個事務正在等待的鎖的資訊而且它無法與SSI請參閱[可序列化快照隔離](ch7.md#可序列化快照隔離 ))協同工作,因為這需要一個跨系統定位衝突的協議。
* 對於資料庫內部的分散式事務不是XA限制沒有這麼大 —— 例如分散式版本的SSI是可能的。然而仍然存在問題2PC成功提交一個事務需要所有參與者的響應。因此如果系統的**任何**部分損壞,事務也會失敗。因此,分散式事務又有**擴大失效amplifying failures**的趨勢,這又與我們構建容錯系統的目標背道而馳。
這些事實是否意味著我們應該放棄保持幾個系統相互一致的所有希望?不完全是 —— 還有其他的辦法,可以讓我們在沒有異構分散式事務的痛苦的情況下實現同樣的事情。我們將在[第十一章](ch11.md) 和[第十二章](ch12.md) 回到這些話題。但首先,我們應該概括一下關於**共識**的話題。
@ -794,7 +794,7 @@
檢視戳複製Raft和Zab直接實現了全序廣播因為這樣做比重複**一次一值one value a time**的共識更高效。在Paxos的情況下這種最佳化被稱為Multi-Paxos。
#### 單領導者複製共識
#### 單領導者複製共識
在[第五章](ch5.md)中,我們討論了單領導者複製(請參閱“[領導者與追隨者](ch5.md#領導者與追隨者)”),它將所有的寫入操作都交給主庫,並以相同的順序將它們應用到從庫,從而使副本保持在最新狀態。這實際上不就是一個全序廣播嗎?為什麼我們在[第五章](ch5.md)裡一點都沒擔心過共識問題呢?

View File

@ -18,7 +18,7 @@
1.在併發操作的上下文中:描述一個在單個時間點看起來生效的操作,所以另一個併發程序永遠不會遇到處於“半完成”狀態的操作。另見隔離。
2.在事務的上下文中:將一些寫入操作分為一組,這組寫入要麼全部提交成功,要麼遇到錯誤時全部回滾。請參閱“[原子性Atomicity](ch7.md#原子性Atomicity)”和“[原子提交與兩階段提交2PC](ch9.md#原子提交與兩階段提交2PC)”。
2.在事務的上下文中:將一些寫入操作分為一組,這組寫入要麼全部提交成功,要麼遇到錯誤時全部回滾。請參閱“[原子性](ch7.md#原子性)”和“[原子提交與兩階段提交](ch9.md#原子提交與兩階段提交)”。
@ -108,7 +108,7 @@
### 持久durable
以某種方式儲存資料,即使發生各種故障,也不會丟失資料。請參閱“[永續性Durability](ch7.md#永續性Durability)”。
以某種方式儲存資料,即使發生各種故障,也不會丟失資料。請參閱“[永續性](ch7.md#永續性)”。
@ -174,7 +174,7 @@
### 隔離性isolation
在事務上下文中,用於描述併發執行事務的互相干擾程度。序列執行具有最強的隔離性,不過其它程度的隔離也通常被使用。請參閱“[隔離性Isolation](ch7.md#隔離性Isolation)”。
在事務上下文中,用於描述併發執行事務的互相干擾程度。序列執行具有最強的隔離性,不過其它程度的隔離也通常被使用。請參閱“[隔離性](ch7.md#隔離性)”。
@ -204,7 +204,7 @@
### 鎖lock
一種保證只有一個執行緒、節點或事務可以訪問的機制,如果其它執行緒、節點或事務想訪問相同元素,則必須等待鎖被釋放。請參閱“[兩階段鎖定2PL](ch7.md#兩階段鎖定2PL)”和“[領導者和鎖](ch8.md#領導者和鎖)”。
一種保證只有一個執行緒、節點或事務可以訪問的機制,如果其它執行緒、節點或事務想訪問相同元素,則必須等待鎖被釋放。請參閱“[兩階段鎖定](ch7.md#兩階段鎖定)”和“[領導者和鎖](ch8.md#領導者和鎖)”。
@ -362,13 +362,13 @@
### 兩階段提交2PC, two-phase commit
一種確保多個數據庫節點全部提交或全部中止事務的演算法。 請參閱[原子提交與兩階段提交2PC](ch9.md#原子提交與兩階段提交2PC)”。
一種確保多個數據庫節點全部提交或全部中止事務的演算法。 請參閱[原子提交與兩階段提交](ch9.md#原子提交與兩階段提交)”。
### 兩階段鎖定2PL, two-phase locking
一種用於實現可序列化隔離的演算法,該演算法透過事務獲取對其讀取或寫入的所有資料的鎖,直到事務結束。 請參閱“[兩階段鎖定2PL](ch7.md#兩階段鎖定2PL)”。
一種用於實現可序列化隔離的演算法,該演算法透過事務獲取對其讀取或寫入的所有資料的鎖,直到事務結束。 請參閱“[兩階段鎖定](ch7.md#兩階段鎖定)”。