作为后端工程师,事务(Transaction)是整个系统设计中最核心的一环。现代应用架构大多基于 CRUD(Create, Read, Update, Delete),但在设计数据层时,必须思考操作的粒度(granularity)以及需要提供何种安全性保证(safety guarantee)。最典型的例子就是银行转账:当 A 账户扣减 500 元时,B 账户必须增加 500 元。如果在传统关系型数据库(SQL)与 NoSQL 之间做架构选型,首要考量通常就是系统是否必须具备事务能力。如果无需事务,NoSQL 该如何组织;如果需要事务,能否将事务的粒度切得更细,或者部分链路改用非事务方案替代。这种权衡极为关键,因为一旦事务保护缺失,在中间状态出现数据损坏(data corruption)或数据丢失(data loss),例如 A 和 B 账户总和不再是 1000 元,即使机器重启也无法恢复。
```mermaid
flowchart LR
accTitle: 交互式长事务与现代微事务对比
accDescr: 展示了早期交互式事务与现代拆分为细粒度小事务的演变模式。
subgraph Early ["早期传统交互式事务"]
direction TB
E1["开启订票事务"] --> E2["多步交互选座"]
E2 --> E3["最终统一 Commit"]
end
subgraph Modern ["现代互联网微事务"]
direction TB
M1["加购独立事务"]
M2["偏好修改独立事务"]
M3["支付扣款独立事务"]
end
```
早期的数据库事务大多是长时间的交互式事务(interactive transaction),比如从用户开始选机票就启动事务,直到最后全部完成才提交(commit);而现代互联网公司则将其拆分为大量细粒度的微小事务(例如用户添加某件商品算一个独立事务,设置一项偏好算另一个)。这一领域的专业名词极其混乱:
- **概念多义性**:例如快照隔离(Snapshot Isolation)到底属于并发控制级别(Concurrency Control Level)还是隔离级别(Isolation Level)。
- **最混淆的单词 Consistency**:一致性(Consistency)在不同语境下含义天差地别,一致性哈希(Consistent Hashing)指的是节点映射平衡性,CAP 定理中的 C 指的是线性一致性(Linearizability),而 ACID 里的 C 又指的是应用层的业务约束。
- **商业厂商的造词包装**:数据库历来是利润极其丰厚的基础设施,像 Oracle 这样依靠技术老本几十年赚取高利润的公司,为了销售产品发明了海量新术语,导致工程师在交流时名同实异。
在整个支付生命周期(lifecycle)中,任何一个环节都可能发生单点故障(single point of failure):
- 扣减用户信用卡可能失败(例如余额不足或风控拦截);
- 转入咖啡馆账户可能网络超时或系统异常;
- 往往需要引入两阶段检查或预冻结(hold/lock)机制,在每一步校验通过后再往下执行。
```mermaid
flowchart TD
accTitle: 支付扣款的核心链路与异常分支
accDescr: 展示了用户到商户转账过程中的关键检查点与失败分支。
subgraph Flow ["支付生命周期与校验"]
A["发起消费 500 元"] --> B{"预扣/冻结账户余额"}
B -- "余额不足/风控失败" --> F1["终止交易并报错"]
B -- "冻结成功" --> C{"入账商户账户"}
C -- "入账失败/超时" --> R1["解冻/回滚补偿"]
C -- "入账成功" --> D["扣款正式结算 (Commit)"]
end
```
在进入更复杂的隔离级别前,典型的非规范化(Denormalization)数据场景最能揭示底层数据并发访问的问题。以收件箱(Email Inbox)为例,系统通常需要展示两个关键要素:未读邮件计数(Unread Counter)以及邮件列表详情。若采用严格范式化设计,每次打开邮箱都必须执行类似 `SELECT COUNT(*) FROM emails WHERE user_id =? AND is_read = false` 的全表扫描;当单个用户的邮件量达到数万封时,频繁扫描行记录的性能开销是不可接受的。
```mermaid
flowchart TD
accTitle: Dirty Read Email Inconsistency Flow
accDescr: Sequence showing how an uncommitted email insert and delayed counter update cause inconsistent reads between two timestamps.
```mermaid
flowchart TD
accTitle: Read Skew Under Read Committed
accDescr: Demonstrates how a reader observing accounts at different stages of a transfer transaction sees missing money.
MVCC 的灵魂在于解耦读写冲突:**“读不阻塞写,写也不阻塞读”(Readers never block writers, and writers never block readers)**。每个事务启动时都会获取一个单调递增的事务快照版本号,读操作根据版本号仅可见在该事务开始前已经提交的历史版本。与性能损耗极大的两阶段加锁(2PL)相比,MVCC 在保证只读事务看到全局一致快照的同时,彻底消除了读操作排队加锁的巨大开销。
第四种方式则是基于条件更新的比较并设置(Compare-and-Set / CAS)。在执行更新的 SQL 语句中附加条件约束,例如将更新写成原子条件语句:当且仅当变量当前值等于旧值时(如 `UPDATE table SET var = 43 WHERE id = 1 AND var = 42`),才允许更新成功;若在此期间其他并发事务已将该值修改,条件不满足则更新失败。Scott 强调,使用 CAS 必须提防快照隔离(Snapshot Isolation)带来的隐患:因为快照隔离在定义上就意味着事务读取的可能不是最新版本的实时数据,若基于陈旧快照做条件判定,依然存在数据丢失的可能。不过在实际工程中,CAS 依然是一种“相对 low-risk、比较无脑”且直接的选择。通过把一段时间范围内的并发竞争压缩为一个原子判定的时间点,系统便能决出胜负者(Winner and Loser),失败方只需简单重试即可。最后一种思路是采用 CRDT(无冲突复制数据模型),正如 Uber 曾分享过的无序哈希表实现案例:写入阶段极度宽松,无条件接受所有并发写,但代价是把复杂度转移到了读端,客户端在读取时必须执行额外的合并逻辑以厘清因果先后。
A["初始状态: var = 42"] --> B["事务 T1 读取: 42"]
A --> C["事务 T2 读取: 42"]
B --> D["T1 尝试更新: SET var=43 WHERE var=42"]
C --> E["T2 尝试更新: SET var=44 WHERE var=42"]
D -->|"先到达 (Winner)"| F["更新成功: var 变为 43"]
F --> G["T2 条件不满足 (Loser)"]
E -.-> G
G --> H["T2 Abort 并触发 Retry"]
```
[01:13:00](https://www.youtube.com/watch?v=XCqLFER4Xww&t=4380s) Q&A 互动:Java 原子类 CAS 与数据库并发控制机制
讲解告一段落后,Scott 提到事务与后续的分布式共识(Consensus)机制非常烧脑, consensus 显然无法在一周之内讲完,因此后续两周会分阶段逐步拆解。在自由答疑环节,一位学员提问关于原子操作的底层机制:Java 语言并发包中的 `AtomicInteger` 同样依赖 Compare-and-Swap(CAS),这与数据库所谈论的 CAS 和锁机制有何异同?底层是否也是加锁逻辑?
Scott 澄清道,Java 的 `AtomicInteger` 属于多线程并发编程(Multi-threaded Coding)的范畴,其底层依托于 CPU 硬件级别的原子指令(如 x86 的 `CMPXCHG`),避免了传统互斥锁的上下文切换开销;而数据库的并发控制面对的是持久化存储与事务上下文。不过,两者在设计哲学与临界区保护的思想上是高度相通的:操作系统或多线程中在执行临界区代码时加锁、结束后释放锁的流程,与数据库行级锁及原子条件判定的核心目标完全一致,本质上都是为了消除并发竞争造成的数据不一致。
Scott 解答指出,在快照隔离级别下,其他事务能够读取到该修改的前提当且仅当该事务已经成功提交(Committed)。如果事务在中途失败中止,整笔事务就像“从来没有发生过一样”,所有未提交的修改都会被回滚并丢弃,外部读取绝不可能读到任何半成品的中间状态。Scott 由此总结了理解事务隔离机制的核心认知模型:并发控制的关键在于分清“时间点”与“时间段”。在任意一个持续的时间段内,可能重叠着无限多个并发时间段与时间点,由此便滋生了各种并发冲突与异常读写;而串行化(Serializable)等强隔离方案的精髓,就是通过锁或原子判定,把一段充满不确定性的“时间段”坍缩为一个不可分割的“时间点”(Atomic),一旦所有操作都规整为互不重叠的时间点,系统便能对其进行全局排序,从而彻底消除竞态条件。
Scott 分析指出,XA 是一个非常古老的规范,其本质就像定义了一套跨异构系统的 REST API 协议标准,其底层同样采用两阶段提交(Two-Phase Commit / 2PC)来协调分布式事务。在理论模型中,只要参与的各个系统——无论是关系型数据库、消息中间件还是邮件服务——均严格实现 XA 规范,协调器便能统治这组异构系统(Heterogeneous Systems),确保全局事务的一致性。Java 平台也曾推出过对应的标准 API 支持。然而 Scott 直言,XA 属于“理论上很丰满,但实际上现在工业界基本没人愿意在生产中使用”的技术,不仅性能开销极其高昂,运维极度复杂,且容易出现长时间锁悬挂与单点故障问题。现代分布式系统设计通常会放弃这种重量级的全局强一致事务,转而采用本地事务配合消息队列、At-Least-Once 重试以及下游基于业务唯一键做幂等去重的方式来落地。讨论在此告一段落,Scott 感谢大家的提问并结束了本场分享。
## Notable quotes
> [10:00](https://www.youtube.com/watch?v=XCqLFER4Xww&t=600s) 在 Spanner 这篇 paper 里面,作者说因为 2PC(Two-Phase Commit)可能太贵了,所以可能就不支持它,但是其实 transaction 这个部分是整个 database 的 abstraction 带来中最重要的一部分。 — Scott Shi
> [25:00](https://www.youtube.com/watch?v=XCqLFER4Xww&t=1500s) 从公元前一直活到现在的长生不老事务 — Scott Shi
> [40:00](https://www.youtube.com/watch?v=XCqLFER4Xww&t=2400s) Bob 把车开走了,付款账单却寄给了 Alice — Scott Shi
> [45:00](https://www.youtube.com/watch?v=XCqLFER4Xww&t=2700s) 读不阻塞写,写也不阻塞读 — Scott Shi
> [55:00](https://www.youtube.com/watch?v=XCqLFER4Xww&t=3300s) 先别考虑分布式数据库,光是单个单机数据库的并发处理就足够让人头疼了。 — Scott Shi
> [01:00:00](https://www.youtube.com/watch?v=XCqLFER4Xww&t=3600s) 这就有点像我把代码 Pin 在了 GitHub 仓库的某一个具体的 Branch Version(版本 Commit Hash)上 — Scott Shi
> [01:10:00](https://www.youtube.com/watch?v=XCqLFER4Xww&t=4200s) 相对 low-risk、比较无脑 — Scott Shi
> [01:14:30](https://www.youtube.com/watch?v=XCqLFER4Xww&t=4470s) 从来没有发生过一样 — Scott Shi
> [01:20:30](https://www.youtube.com/watch?v=XCqLFER4Xww&t=4830s) 理论上很丰满,但实际上现在工业界基本没人愿意在生产中使用 — Scott Shi
## Mentioned
Scott Shi, YouTube, Oracle, Cloudflare, Google Spanner, Raft, Paxos, Apache Airflow, FoundationDB, Apple, 美图秀秀, 美币, Salesforce, Uber, Uber DockStore, Uber Schemaless, Redis, Apache Cassandra, Google Bigtable, DynamoDB, Voldemort DB, MySQL, InnoDB, GitHub, MongoDB, Apache Kafka, Java, XA
## Video description
In this video, Scott will walk through the transaction, the slide could be found here:
https://docs.google.com/presentation/d/1bo4SVBEVWgWPmagMuA2jsIpEco9hfGZ3sNZk25g4RKg/edit?usp=sharing
[10:00] Scott Shi: 在 Spanner 这篇 paper 里面,作者说因为 2PC(Two-Phase Commit)可能太贵了,所以可能就不支持它,但是其实 transaction 这个部分是整个 database 的 abstraction 带来中最重要的一部分。
[25:00] Scott Shi: 从公元前一直活到现在的长生不老事务
[40:00] Scott Shi: Bob 把车开走了,付款账单却寄给了 Alice
[45:00] Scott Shi: 读不阻塞写,写也不阻塞读
[55:00] Scott Shi: 先别考虑分布式数据库,光是单个单机数据库的并发处理就足够让人头疼了。
[60:00] Scott Shi: 这就有点像我把代码 Pin 在了 GitHub 仓库的某一个具体的 Branch Version(版本 Commit Hash)上
[70:00] Scott Shi: 相对 low-risk、比较无脑
[74:30] Scott Shi: 从来没有发生过一样
[80:30] Scott Shi: 理论上很丰满,但实际上现在工业界基本没人愿意在生产中使用
Original transcript
[0:00] 好我们开始录音然后Transaction的话这块的话也是也是就是做一个backend程序员的话其实我觉得是最核心的一部分吧然后很多时候设计的时候你看要想到这个怎么样能保证这个Data的crackness就是safety 是吧就是你设计的时候你这个data肯定是经过说白了就是现在大部分设计都是一个crew的嘛就是create read update和delete但是这个create read update 和delete的话你的granularity 是什么然后你设计的时候有哪一些有哪一些这个Sifted Guarantee要保障是吧,怎么样这个Data,你比如说你转账的时候,A账户减少了500块,那B账户应该增加500块,这个Sifted Guarantee要在这里是吧,然后是比较核心的一部分。然后Transaction同时呢也是,我觉得也是传统的Database里面讨论最多的一部分。嗯很多时候,SQL和NoSQL选择的时候,第一选择可能就是需不需要Transaction,是吧?如果不需要Transaction的话,那NoSQL应该怎么样来?SQL应该怎么样来?如果需要Transaction的话,那我能不能把Granularity,Transsection设计的小一点我们看是不是有一些地方可以用非Transaction的东西代替然后这个是比较核心的一部分就是SQL和NoSQL的选择中比较核心的一部分然后这个的话,这部分也是选database中和这个后端实践中最比较花时间的一部分因为这个如果你这个transaction没有达成的话,比如说你A账户转B账户转500块,然后你出现了一个中间态,这两个加起来不等于1000的话,这个中间态是recover不了的,机器重启recover不了,因为这个data是cropted, data loss,你是recover不了的。所以这个transaction也是比较重要的。特别早的时候,database transaction就是一个interactive transaction,比如说从订一个机票开始,就开始一个transaction,然后最后全部搞完了以后是commit,是吧?然后就把你这个transaction定下来,但是现在的互联网公司的transaction,我讲就是小的transaction,就brick down下来,是吧?比如说你这块加了一个什么东西,然后你就是一个transaction,然后你这个,比如说你这个选了一个preference,就是一个transaction,所以和之前的transaction的概念不是完全一样的。一方面是Wording比较confusing你比如说一直在提什么是snapshot isolation然后它到底是个concurrency control level还是一个还是一个这个这个就是这个isolation level 是吧这个word是非常confusing的特别是这个consistency, consistent 是最为confusing的一个word因为这个consistent 是在很多地方不停的被提到的比如说 consistent hashing 的 consistency 然后 CEP 的 consistency然后这个AC的这个Consistency还有什么 consistency,反正说的这个 consistency,其实意义都不一样,就是意义都不一样。就是并且这个 transaction 这块的话,我觉得因为它这个 database 是 database 最核心的一部分。也是DataBase中最核心的部分,所以它这个DataBase本身是利润率很高的一个地方,像Oracle这么挣钱就是吃的老本是吧,十几年前的老本,几十年前老本。所以它这个Oracle为了这个Cells,它其实是发明了很多新的概念的。所以非常confusing,Oracle发明了很多新的概念,非常conforming然后它这个就是,所以说你需要去鉴别出来,它到底这个概念是什么意思?所以你靠 word 来说的话,很多时候你说一个名词,别人说的名词,他理解的意义是不一样的。所以很多时候你还是要理解这个 transaction 这个里面就是你在讨论这个问题的本质是什么,就是这个 transaction 这里面本质是什么。所以这个也是非常核心的一部分。好,然后就是这样,然后大家这块有什么想额外问的问题的话也可以说一下,但是总体来说这张是非常非常重要的一张,然后我给大家直观的建议是大部分面试官对transaction理解其实都是比较弱的,就是他其实面试官的理解就是比较弱的,就是我说实话,我见的面试官很多时候对transaction理解本身是比较弱的,包括面试的人就是比较弱的。所以你这块的话,你给它抛名词是没用的,你可以抛名词,但是抛名词很多时候没用的,所以最好举个例子是吧。就像比如说如果做二的话其实就是完全是一个transaction的面试其他的部分都是之前讲过很多次的,但是这个transaction这部分其实是比较幸福的然后,所以说,就是举例子嘛,就是举反例,你就要找一个timing的例子,在什么,在什么timing的情况下,你这块有risk condition,然后你这个这个database,整个你的safety就被broke了。就是你的Safety Broker了
[5:00] 对不对就是你转账的时候就不是500转500了就变成了这个人增加了500那个人减少了1000这种所以是其实是找反例的一个过程,transaction其实是找反例的一个过程,然后是这个方面,然后其次的话,谢谢大家我现在想到的就是这么些了,我刚才想到一些其他的,然后突然忘掉了,现在想到的就这么些了。所以这个transaction是非常重要的一招。比如说,我有一个问题,我说如果要设计一个payment system,比如说,我们需要设计到一个billage transaction,这样的话我们应该去怎么样来做一个design呢?乐度感我不是很了解,如果可以的话那稍微讲。对,Payment系统,其实你面试的时候,它最常见的问题,Payment系统就是说,你比如说你是,我举个例子吧,我还真没有做过Payment Marking Interview,其实我可以考虑做一下,就是我可以去考虑再深入研究一下但是payment的话最简单的一个问题就是比如说你现在从A公司,比如说你是开发咖啡馆,我去咖啡馆消费了,我要从我的信用卡里花着500块,然后去咖啡馆里面增加500块。然后咖啡馆的balance增加500块,然后我的balance减少500块,是吧?那我做这个payment的时候,我做payment的时候,从我的账户里滑走500块的操作可能会失败,然后另一方面的话,把500块把500块转到咖啡馆的这个可能会失败是吧,可能多种原因,比如说鱼饿不足啊,所以说对所以需要有些事情,我只能先锁一下,然后怎样的,然后就分析不承认做每一步的check一下是不是可以做下一步这样子?对的,我也不是很了解这一部分,但是我想大家可以讨论一下,大概这样的话应该怎么样给实现吧?对,我们可以下周和下下周讨论一下吧,因为payment也是非常高频的一个面试题,我其实进Uber的时候面的也是payment。但是我忘了当时怎么达到了因为当时理解可能也不一样其实payment是非常高的平等面试题我觉得payment最重要的一点的话是你需要理解就是在payment的任何一个flow中任何一个life cycle中任何一步都可能fail掉所以这个处理是比较复杂的但是这个value是所有不管是什么不管是Fayment系统或什么系统都是有可能Fayment系统,但是Payment的比较重要一点就是Rocquefield的话就因为这个是真实的那个钱比较重要的一个交易可能所以需要需要有一些特殊处理我理解是就不知道到底现实生活人是怎么样的处理法?我们可以讨论一下细节,并且我知道我们其实群里面有很多专门做payment,我记得那个嗯我记得review了几个ERD是在那个里面的,就是design review是这个ERD,他们那个payment的其实更复杂一点,还有就是concurrency,不是那个currency的问题,还有货币的问题,还有并且好多其实payment的,做payment的其实是欠Uber欠的,有个RAR的问题,那你怎么样collect RAR的话是并且还有是cash payment 的问题,其实这个payment 的做起来是比较复杂的。对,有各种情况。好,我们确实可以把做一个mock intro 的面试题,payment 其实有很多很有趣的地方,payment 也属于相当于是比较 call transaction 和这个系统,就是这个distribute system,任何一个 single point failure,就是 single point failure 和这个 fault tolerance 这个也是比较就是考的比较多的地方,怎么样处理 transactions,怎么样处理各种各样的 faults好,我们继续好了所以Transaction呢,我们今天呢是讲的是集中Transaction,然后这边的话,所以大家今天的课会真的比较有气啊,今天讨论的东西会真的比较有气啊,你会看到各种情况下,在这个Data Base不同的isolation level里面guarantee,你会看到不同的结果是吧?所以这个里面这个例子呢,希望大家就是仔细想一想,然后把它记下来,名词记下来最好,但是例子记下来的话,其实对里面是很有帮助的,包括对你个人的理解是很有帮助的。这里面比如说一上来的话,最简单的可能是有一个multi object的一个,就是autonomic,这个就是transaction的概念是吧?就是第九章讨论,然后就是下周讨论,然后这个read committed的话,那你是dirty rights和dirty reads有什么结果是吧,有什么可能造成的结果,那就是也是level最低的一种transaction是吧,就是你这个isolation level是guarantee最低的,然后往上的话就是snapshot isolation,snapshot isolation的话那为什么需要有信息呢,
[10:00] 能保证什么?然后再往上的话,这是Serializable。Serializable 的话,其实也是有几种Serializable 的。然后它这块的话,有一个Isolation Level,叫Serializable。然后有几种Concurrency Control的一个实践,比如Two-Face Locking, SSI,或者是Actual Serialized Execution,这都是有几种实践的。然后总体来说,它们又分成悲观的这个锁,其实也是非常有讨论的地方,所以今天就是比较开脑洞的一天,就是大家想一下各种出现可能的情况。这个说并且所有Transaction面试呢也是,我个人是比较喜欢面Transaction的这个面试的,就是有的面试官很喜欢面这个面试,因为就是可以看一下这个人对这个系统理解和这个系统组件的理解,然后各种counter case怎么处理。然后像在周二的面试其实就是找反例这个过程其实就很有趣的一部分就是怎么样找出一个反例来,然后说这个系统不work早反例有时候比设计一个系统有时候会更难一点在Spanner这篇paper里面,就是他这个Spanner作者说,因为这个Two-Face Committed可能太贵了,所以可能就不支持他,但是其实这个transaction这个部分是整个database的extraction带来中最重要的一部分最重要的一部分,我可以认为MySQL里面给我带了最好的一个abstraction,假设它是一个abstraction,最重要的abstraction就是transaction。如果说让我重新发明一个transaction,我还不如不用这个database,是吧?所以说提供不提供transaction,不应该是说提供不提供transaction,而是说我提供给你transaction,那你用不用是你的问题,是吧?而不是说这个database,其实没有重新的,我尝试去我尝试去自己实践一个transaction,这个是非常非常困难的。这个基本上来说都feel mis-volved 了,因为这个transaction已经经过近50年的发展,它已经非常完备了,自己实践的transaction很有可能会出现各种各样的问题。好,所以Database中,其实很有可能就是各种各样的问题都会出现,比如说你的Database software hardware failure,是吧,然后application可以crash,然后Network可以partition并且一个非常简单的Network partition就可以导致你的系统整个就down掉我们见过这个就是上个月的时候不是见过一个简单的人为的Network出问题了它就是那个Cloud Flare然后它整个它自己有个private network然后它把backbone的private network设置出问题的,不算是partition,相当于是人类的network出问题了然后把所有的traffic direct到一个data center,然后整个cloud flyer的整个network就down掉了这是上个月刚出现的事,7月份刚出现的事但是,包括我们下周就讨论的这个Consensus Algorithm,不管是Draft,不管是Paxos,还是Rift,Rift,Rift,它其实都有一个非常simple的一个Network的一个case,就是你A点和B点之间,它Network时段时连,然后你的Rift,你的那个RAFT就可能花很久才能converse到一个consensus了,就是A点和B点,就是你一个node和另一个node之间,时段时连,时段时连,时段时连,然后你这个系统就不响应了,就不available了。所以这个Network这块其实有很多counter case,很有趣的case可以研究,然后也是比较复杂的地方。好,然后从系统的角度来讲的话,就是你可能是这个就是这些transaction这些讨论的问题是你可能在每一个时间点上,你都有多个client去访问然后我想说这块系统来说对系统来说时间是相对的就是像那个数学里面例子一样就是你任何两个时间点之间有无限大的时间点你任何两个transaction之间有无限多的transaction,你可以这么想,假设你是真的一个非常crowded的系统的话,你任何两个时间点之间有无限的。就像你任何两个有理数之间有无限多个无理数,是这样的感觉。所以这个时间是相对的概念。所以这个时间是相对的概念的话,那你这么多这个computer,就是contender来去content的这个transaction,那么你怎么样决定谁是winner,谁是loser呢?所以这个的话,你就必须要考虑一下怎么样处理这个concurrency control。你怎么样处理这个concurrency control了,我们之后今天也会稍后谈一下这个concurrency control。控制的问题然后其次的话你可能会读到一个partial updated data 是吧?比如说你在写database的时候,我不知道大家想过这个问题没有比如说你database是一个document database你是个Json然后把这个Json呢写到这个database里面那我读这个Json的时候
[15:00] 如果这个databaseupdate一半怎么办,这个datum写了一半怎么办,因为你毕竟写的时候,你可以把时间无限放大,是吧,假设这个写需要5毫秒,那这个5毫秒的话,你可以认为是有5000个什么纳秒,是吧,你这个时间无限放大的话,你可以想到就是如果在读的时候读到一半怎么办,是吧,那怎么样partial updated data也是有可能的。我之前的话,我之前在Southforce的时候专门做这个专门做这个就是有点做了一个有点像Airflow 的东西,但是它其实中间有大量的这个transaction 和这个我当时写了一个optimistic locking,写了一个optimistic locking,然后大量的risk condition。就是有的risk condition,我们系统推出了一年半以后,然后我写的那个系统推出一年半以后才发现然后发现这个系统以后我们就推敲这个 risk condition 是怎么来的这部分是最难的, debug 是怎么来的然后这个复杂度就很高,所以这个Risk Condition 是比较难的就是你很多时候你系统推出了好几年后才发现然后那个Risk Condition 可能都没法解决那没法解决怎么办就希望它不发生很多时候就Risk Condition 你想解决这个Risk Condition 如果千万分之一一分之一发生的事情啊 你想解决它那你造成的结果可能就是你整个系统要重写,然后你就假装它没看见,希望它没发生,很多时候有时这样,但是你要搞清楚为什么会有 risk condition,以后真的要重构的时候,真的要存 architecture的时候,你是不是能解决这个 risk condition好,Transaction的话,我个人觉得是Database提供的最好的一个非常neat的一个 abstraction就是你实际上是把很多的group-reads和reads变成一个logical unit了所以你认为你这个就是执行了一大堆读和写,然后变成一个operation你要不就是 get all,要不就 get nothing,就是 about or roll back.get out 的话就是你 committed,如果没有get nothing的话就是about或rollback,然后如果是rollback的话,你的application可以safely retry所以这是Transaction,你可以认为这是Transaction的一个 abstraction。所以在2000年的时候,就是这个NoSQL新起的时候,他们整个NoSQL database是普遍的放弃了Transaction,有一些特例,比如说FoundationDB,Apple II的FoundationDB,其实它是提供Transaction的,它是Serialized Snapshot Isolation,有一些特例,但是大部分NoSQL,基本上是放弃Transaction的,因为Transaction是Performance最主要的影响。Transaction就变成了NoSQL运动的牺牲者。它不提供Transaction,不提供Strong的Transaction,提供一个Wake Side的Transaction。然后这样的话就变就是用那个效率换取它这个safety 是吧就是efficiency和safety之间的考虑他们选择了efficiency没有选择safety这个是可以理解的,毕竟也是从2000年左右互联网用户还比较增加多的,互联网的需求还比较增多的,所以他们就是选择了Efficiency,没有选择这个Safety所以说就是现在有一种popular观点是transaction呢,是任何一个大的large-skilled system应该去abort transaction为了更好的performance和high availability,然后如果你是需要的话,比如说你是essential requirement是需要transaction的话,那你应该用这个需要transaction的这个,比如说你是valuable data,比如说payment这种,那你应该用一个有transaction的系统。这个说法还是相对比较片面的,有多种原因,一个是用transaction的系统的时候,不代表说你用transaction系统,但是transaction,就能guarantee你能做payment了这个不是啊它中间有很多很细的东西然后这是一点然后其次的话,这个当时有很多paper,就是2013年2014年的时候很多这个,Blockchain 里面做Transaction,就出现了各种各样很奇葩的事情,然后各种Blockchain,系统设计没设计好丢钱是吧,就是Double Spending,这种特别之前不是花了,就是在Ethereal之前有很多这种奇怪的他们设计的东西,然后就没有在Ethereal这个平台上进行coding,没有这个Smart Contract平台上进行coding,出现了各种黑外的事情,所以我记得有一个非常有名的就是美图秀秀出了一个美币,然后这个美币呢,本来就是被人拉过来投资的炒作的然后他推出了以后从最高点低了跌到最高点的1%以后呢就在1%的基础上跌了1/10000因为它有个double spending的问题所以我可以不停的copy它里面的currency所以我记得每币什么最高的时候100块跌到1块钱觉得肯定会涨了然后结果跌到之前的1/10000导致这个
[20:00] 他想已经不能数了是吧,因为你可以copy b 的话就一分不值嘛它就有个bug就是这样子的好Double Spanding没有解决好。好,在讲到Transaction的时候,最popular的一个概念就是ACID,但是 ACID 是一个 out of date concept, ACID 是一个不是很好的 concept这个也是我在 school 里面学到一个最好的concept,也估计是你面试的时候可能会被念到的concept,因为这个是教科书上的东西,1983年就有了教科书上的东西,然后这个是不是很好的concept。那对应的ACID是个basic,表示basic available,然后soft state,eventual consistence,基本上您可以拿到任何一个database甚至您可以拿比如说卡卡也可以说是这个是个贝斯是吧卡夫卡这种不是database的,你可以说它是个base好,AC的值是什么呢?A的话是autonomicAtomicity就是原子性你要不get all, you'll get nothing如果这个transaction决定about的话,它应该能被safely retry所以更像是aboutability,就是意思是你可以about这个transaction如果你的一个assumption就是你在如果在Commit的时候遇见任何你的Assumption变了,或者什么不满足的地方,你可以About这个Transaction然后Consistency的话就是什么意思呢?Consistence就是说,当你去写你的Data的时候,你会有一个这个 expectation,比如说你是做transaction 是吧?你希望A和B账户加起来是固定的钱数,那你会有个 expectation,如果这个 expectation 被broke 的话,如果这个Expectation被broke的话,你应该就是相当于是Consistency被broke,那Consistency被broke的话,你应该去,就是应该,就是这个系统呢应该是能组织你的。但是这个Consistency呢,其实是一个user或者business oriented thing,所以它其实不是database,从database的角度来讲,你A账户多50块钱和B账户少50块钱,Database没有觉得有任何问题是吧?你设计的时候说这两个人必须一起执行,Database的角度来讲,这是两个操作嘛。那他觉得不bondor在一起没有什么问题。所以这个Consistency更多的是一种Client的需求,就是Client Requirements,其实不是对于Database的一个需求,它只不过是Business的需求,所以我们必须很多时候你这个做技术,最重要的是Business,Business Orientated,所以你必须做Business的时候,但其实不是database的事情Database不能阻止你做一些crazy的事情。然后isolation的话就是我们说过在任何一个时间段上,你可能有无数的东西在写进的database,你有无限多的东西,transaction在写的database。那么我们怎么样保证这个database不出现问题,是吧?你不会两个transaction相互践踏,然后出现各种各样奇怪的结果,我们很快会看到各种各样奇怪的结果。那么就像刚开了一个咖啡店刚开了一个那个耐克鞋,耐克鞋突然限量版鞋开店了,然后几万个人往一个耐克店里面抢,那怎么样保证最后的结果就是虽然有几万个人同事在抢,最后结果感觉像他们一个一个排斥对境来似的感觉,就是要做的事情。就是Serializability 的事情。最后一个 Durability 的话是,如果你这个 data committed,应该有一个 checking point,在这个 checking point 之前,我是不 guarantee 的 data 会 committed。在这个 checking point 之后,你的 data 是一定会被 committed 下来的。如果我们记得之前的讲东西吧,这个 checking point 在这个 SQL 里面,就是 write ahead log,是 Red Hat Log所以在 Red Hat Log 之前你认为你没有写到Ride-A-Hide-Log,这个Ride-A-Hide-Log没有被flush了disk的时候,你可以认为这个还是可以被abot it,可以被扔掉的。但Ride-A-Hide-Log被flush了disk以后呢,你可以认为这个东西已经彻底的存下来了,就是不应该丢失,就为整个硬盘坏掉了,是吧,或者硬盘这一部分坏掉了,是吧。不应该被丢失然后这个有的系统database是copy on write的,所以如果是copy on write的系统的话,可能就是你这个有个pointer,是吧?这个B-Trait里面有个pointer,你把这个旧的version指向新的version,就是一个checking point,就是一个时间点。在这个时间点之前,你都是没有committed,在这个时间点之后,你都是committed。所以这个分清在哪个时间点是committed,是很重要的一个时间。所以这个面试的时候也是很重要的一个概念。或者user是吧你什么时候跟人提出我拿到你的request了我guarantee 会 serve 你的 request 但是我可能不会当下收 request 可能需要过段时间的 serve request 是很重要的一点好
[25:00] 其次的话,Transaction里面有三个object和多个object的概念啊如果是单个object的话,其实这个transaction和lineability就很像了。这个transaction,我觉得这个单个object的transaction,和这个单个就是和Linearability就很像很像很像嗯我想单个objects/transsection基本和单个objects/linearability很像大家不知道有没有能想到一个反例了,我在这里没想到,但是Transaction和Linearability,就是Distribute System的Linearability,我们之后会在Consensus,就是下周讲到Linearability的一个概念主要的不同就是Transaction一般是有多个多个object的,就是transaction有多个object的。哦,还有一点不一样的是,transaction,Linearability是要求你读的已经是实时的,但是你单个object的这个transaction有可能会读到这个就是旧的still的data,因为你可能是snap chart isolation所以还是有点不一样的但是除了这点以外应该都是一致的谢谢好,我看这块的话,对,Transaction的话,如果是DogObject的话,写的时候就写,不知道大家有没有写过Post,那个procedure, 是吧,procedure call,那叫什么来着,Stop procedure,不知道有没有写过Stop procedure,之前在Southforce,我之前在Southforce的时候,还有很多人在写Stop procedure,我看到Uber印度组有很多人喜欢写Stop procedure,写的时候就经常是现在有个begin transaction,然后什么select for update,然后committed,然后这种比较不透明,这种code其实写起来就是跟现在的互联网设计是很不一致的,为什么呢,因为它这个会有各种各样的问题,首先它这个code,不是version的,就是你其实,depends on you check in, or not check in to GitHub.如果不是Github的话,你都没办法version然后其次的话 你这个写的是SQL code的 没法debug 你没法写log 是吧 没法log 没法debug然后其次的话你也没法 login,你不能说下一个debug pointer,你没办法 login,这个login也是有问题的,这个SQL里面怎么login,这个是很有问题的,很难login的。所以这个version control,login,和debug,都在stop procedure里面都有问题,然后stop procedure,如果你写过stop procedure,你就知道stop procedure,既然上business logic用最重要的一部分。所以说话现在基本就被大家遗弃了就是你现在基本上很少自己写 begin transaction commit 了或者是写的话也是就是这个 ORM 经常会给你 wrapper 一层让你写的比较简单点,single object的这个transaction。如果你要写这个的话,你要非常小心的写。Self-host 写这个stop procedure是有很多很多问题的。我记得最常见的一个问题就是,我们当时debug这个distribute system的时候,就说有一个transaction,它run了,48 小时,他们当时做Performance Evaluation,有一个Transaction,乱了48 小时,48 小时就是Forever了,48 小时在计算机中就是在那Forever了感觉从公元前零年,活到现在了,活到四千多年以后了从埃及开始建金字塔的时候,5000多年一直存在的一个transaction并且因为它是个two-faced locking,就是说他lock的object没有人能读,就是两天之内他lock的object是没有人能读的就这么悲惨,就是你lock了一个东西,然后从公元前3000年到现在没有人能赌,就变成了一个机密的东西,这个就是非常悲惨的一个过程,所以这个stop procedure和transaction写不好也会很惨的结果好然后这块的话,Atomic和Isolation对SingleObject也是相关的,是吧?比如说你在写一个20KB的一个 data,20KB的一个JSON data当你写了database的时候,假设你写了10KB的时候,那么你database里是存了10KB的刚写的data和之前的data呢,还是怎么处理,所以这个是很重要的。大部分Database,基本上所有Database,不是说不包含所有Database,基本上所有Database都保证单个object的,atomic,和这个isolation,就是你只能看到之前的结果和之后的结果,你没有half-baked的结果,但是file system里面是可以看到写了一半的结果的。然后这块有个问题说是, maybe I feel around the charge procedure different from transaction.你可能在做到任何通用的通用程中好像也可以的,这么说好像是可以的,但一般来说,Stop Procedure是减少了你的interactive 的过程,就是你不是读到A,然后处理一下,分约A的结果是满足条件,然后再写个B,不是这样子的,所以Stop Procedure是,你像现在,
[30:00] 其实还会写Stop Procedure你在写Redis的时候Redis的那个Lua languageScript language其实是相当于是写一个Stop Procedure这也跟Redis的设计有关我们之后会看到这个Redis的设计为什么要求他写这个Stop Procedure我这块只是举个例子就是举一个时间上的例子这是我过去工作中真实见到一个例子就是你有一个Transaction有一个Transaction在Run了48小时以上还在Run的然后不知道他就是他在等一个什么东西不知道他等什么东西但是他lock了一大堆东西然后lock了一大堆东西没有人能读也没有人能写这是真实中存在的一个例子最后的话很多这个操作系统很多datastore是about这个abundant这个multi-object transaction因为如果你这个multi-object transaction是cross charts或者cross partition的话你的performance会很糟糕像这个Cassandra还有其他的一些database它是可以允许你定义这个partition key的因为你能定义partition key所以你能决定你的data就是属于一个transaction data能把它放到一个shard里面那你这样的话你这个可以做这个在Visicnshard可以做一些transaction那这样的话,这个performance会比较好,像Uber的这个Skinivision是没有这个partition但最新的这个storage is DockStore我不知道会不会发一个engineering block,但是这个 DockStore是支持这个这个就是在一个shard的引内是支持transaction,并且能让你自己来定义shard然后Google的这个,我记得Google的bigtable还是还是另一个 service 也是支持你自己定义shard的我忘了具体是哪一个 serviceGoogle有一个 service我对Google的这个 text tag不是很熟但Google有一个 service是允许你定义partition key的就是你定义partition key的话你就可以决定哪个 data在哪一个shard里面然后你可以决定把你想做 transaction data全部放到一个shard里面其实有很多好处在relational database里面就是我看这块有没有什么想跳过的地方然后然后对这个ForeignKate这块其实大部分你看到大部分互联网公司你会尝试去减少这个ForeignKate的使用就是你尝试在Application Logic然后Maintain这个ForeignKate因为这个ForeignKate其实非常非常Dangerous然后非常这个Tidious然后你这个CascadeDelete包括你有这有这个 cascade delete比如说你这个比如说你之前这个你A指向B是吧那B delete的话A应该也对应的被delete如果但是你这块如果没有handle的话就downgling pointer就是A指向B结果B被delete了你读A的时候就变成downgling pointer了这个是非常糟糕的地方所以在设计的时候就是ForeignKey看起来是一个很不错的feature但是在在这个互联网公司和distribute system设计的时候你要非常小心这个ForeignKey并且ForeignK这块有更麻烦的地方就是你这个ForeignK可能在不同的share里面所以当你这个ForeignK在不同的share里面的话这个crossshare的这个consistency就很糟糕了就变得非常非常糟糕所以这个ForeignK一般来说是尽量避免使用的然后就是这样子了然后看基本上这个multi-object这块就cover了好我们开始看一些有趣的例子我们开始看一些有趣的例子基本上后面就全部讲例子了前面讲了一些这个实践后面就全部讲例子了第一个例子是这样子的就是我们在写这个email的时候打开Google email的时候你实际上是看到两个东西一个是你有没有onread的email如果有onread的email它可能highlight一下然后其次的话它是显示哪一个是onread的email这个email也没有onread的这是两个东西但是假设你想说假设你是normalized data实践的话你就必须用这样一个SQL来写就是select count from email是属于你的email,但是没有被read过这个是要从你的database从这个table里面scan出所有的table这个road belong to your the road这个performance是很糟糕很糟糕的是吧你比如说我随便看我email这个folder可能有上万个email我每当打开email的时候它要读上万个road这个performance是很糟糕的所以为了使这个performance快一点的话所以经常会把这个data denomalize一下就是读成两个road你专门有一个column是说你有没有onread的email是吧就是有column是有没有onread的email然后其次的话每个email有个on-read和read的这个booling就是在这个field上有个on-read的booling然后我们这块有一个很有趣的例子是这样子的因为你现在是两个field一个field是这个email和他read的情况另一个field是有没有on-read的email我们这块是有一个transactionuser是新写了一个emailuser1刚给user2写了一个email
[35:00] 他insert的时候先把email insert进去了所以在这种情况下的话,在这儿的时候,在这个第一个时间点和第二个时间点之间,这个email是被insert进去。但是这个on-read update 稍后发生了那Unraid是从0变成了1,就是在这个很后面的时间点从0变成1如果我在他发邮件的这一瞬间这个时间虽然是一瞬间但是可以无限大就是时间是相对的这个时间可以无限大我在他发邮件的一瞬间我读这个我自己的email的话那么我会看到我现在没有onread的email,但我读到第一个email是onread的这个data是inconsistent的 对吧就是一在跟我发email的时候然后他发email的email和update的email的email这两个步骤是有一个空档的所以导致我结果是我读到inconsistent data 我没有看到email写的是0但是我读到第一封email就是Enray的email这个是一个简单的 brokenBroken好,这个例子带来没有什么问题。有没有什么绝对想像? 这里的话就是应该先先把那个unread先set,然后再做吗?不想理解是不是要fix个问题的话?一样的,一样的,我们说时间节点是无限长的,你比如说这个unread先写过来,unread先写过来,当你读的时候说,我没有任何email,就是我有一个unread的email,在读第二的时候,我说的email都read过了就这两个换一下这两个换一下,这两个换一下就好了。那应该怎么样解决这样的问题?我们稍后会考虑一下怎么样解决这个问题,但是您可以想象这两个换一下以后是一样的结果,就是只不过换下的结果就是第一个说你有一个on-read的email但是你看说的email都是read过了因为你那个on-read的email还没到你这个box里面谢谢大家是好,这个问题是什么呢?就是大家想一下这个问题的本质是什么?我可能给个10秒钟时间,15~15秒,我看大家有没有能想到这个问题的本质是什么?好我看到有人说是DirtyRate了,Karatoids能解释一下什么DirtyRate吗?谢谢大家好你说我吗?对啊。好的,Dirty Rate 的话就是你可以看到还没有 commit 的 transaction。好的,所以这块的本质是不是就是你读到了一些,就是同时一堆人在写一个东西的时候,你作为一个user,你读到别人还没有 committed transaction,就是你读到这个没有 committed transaction,所以它是这个问题的本质就是 dirty read是吧 如果你读的时候别人没有committed transaction 就是这一段这个email insert,如果读的时候,你这个email insert,因为它没有committed,读不到,那这个读的是consistent的就相当于对于user1来说这个transaction就变成了一个时间点在这个时间点之前读的假设这个时间点在这儿啊,我们把这个时间点画这儿你任何读在这个时间点之前读的,你都能读,就是读到这个没有任何on ready的一码,在这时间点之后读的,你可以认为是就是读到了NRAD email所以这个问题的本质是Dirty Rate你在这个Rate里面你读到了别人没有Committed data不是很明显但是如果你还没有Committed它怎么会到已经到Dirted Base里面来了你是怎么可能读到这个星期了?Depend on database是怎么实践的,Depend on database是怎么实践的。有database是,比如说我举个copy on write这种事,你实际上是就是你是把 b-share copy了一份是吧,把 b-share copy了一份,然后用 pointer 来指向的但你如果是一个如果有database是直接在上面覆盖的话你比如说DynamoDB是直接放在覆盖的话,那你是有可能读到的Denamore是有可能做到的,你如果在这个Denamore上写的话,你是有可能读到这个结果的。我只保证这个email对于这个email来说我只能保证要么就是一个email没有要么就是一个email全部加进去但我不能保证是email和另一个相互关联的写是同时加进去的在Dynamo里面,是吧?那我在用Dynamo的话,我有可能读到这个结果,就是DirtyRate的。所以这个问题只存在于比如说unlow software database 如果是socware database 一般都会有一个transaction保护创始,对吧?不会出现 dirty read 的情况吗?对,现在其实 database 很多,database 真的很多,有很多很奇怪的 database,我记得和翔静讨论一个就是 voltdb 的时候Voltmore DB,Voltmore DB 的时候,它就是,它对于这个,
[40:00] Ibis的isolation是比Denomal还要糟糕的,就是什么呢?Animal 还有 read/repair 和这个就是嗯Red Repair和这个Enchophe,就说白了是,它会不停的scan它的区间,然后找一些Inconsistent data,然后去Puretically show full time in consistent data但这个VaultModeDB呢是你只有read repair,就是你这个data只有长读的data相对来说可靠性高一点你这个data如果从来没有读过,读得很频繁的话,你这个data很有可能会丢失所以不同的 database 它的 implementation 跟它的 use case是差别非常非常大的,所以你要非常小心,你到底用哪一种database然后提供什么样的guarantee好,我们去下一张好了,就是。对好 所以这块的话就是最basic的isolation,isolation level的计划讨论的就是 committed data,就是 no dirty reads,这种情况下,还有一个是 no dirty writes,就是 notated writes 是什么意思呢?就是你只有 overwrite data,被 committed data,你不能 overwrite,没有 committed data我们看就是,比如说,有一个例子,在这个书里面有个例子,就是两个人同时买一辆车然后可能的结果如果你有dirty rights的话,可能结果就是车归了一个人,然后另一个人付账单就是比如说我和我和比如说我和AJ一起买车 是吧?然后做了一个payment然后结果侧归了我了,这个侧的ownership 是我的,但是invoice是够agy的,这是有可能的。如果你有做tree writes,还是有这种可能性的。这个就不细地谈了,就是如果感兴趣的话可以看一下那个例子,也是很有趣的一个例子这是第一个Transaction Level,就是Rate Committed Level然后像我们之前谈过denomalDB是不提供这个transaction guarantee的,就是这个isolation leveldenomalDB是不提供的所以就是,但是一般来说这个SQL的话,这个是最低的level,就是你在写SQL的时候,你可以configure这个SQL的isolation level,就是最低的一个level。基本上是最低的一个level了,就是没有比这个更低的了。好,然后我们再看这个我们再看第二个例子第二个例子我看一下这个例子是这个就是Invoice Dirty Rights的例子,比如说Alice和Bob一起买车,然后呢 你现在把这个Bair 车的ownership给了Alice了然后把invoice给Alice 是吧这个时间是相对的所以你这个中间可以有无限大的时间点在你这个时间点的时候你又把这个车的bear给从Bob了然后invoice给了Bob 就是账单给了Bob这种情况下当这个事情结束的时候,你看一下最终的结果是什么呢?这个owner,Bob是Overwrite Alice,对于从owner来说,Bob是owner但是从这个invoice副葬单的情况来讲Alice是overwrite Bob所以这个车归了Bob但是这个葬单归了Alice是吧所以这就是这个 dirty write 就是你说白了是你的write overwrite的一个 uncommitted write 是dirty write的一个结果好我们再看一下简单实践30Rx基本上是被所有的Mansickel database实践的,基本上是被它实践的有比 dirty rights, dirty,就是 read committed,有比 read committed更低的一个level,但是我们基本上就不讨论了,就是rate committed的更低level,比如说这个denomal db 属于rate committed的,这个isolation level更低的level,然后rate committed呢说白了是no dirty rates和no dirty rate,然后我们之前有一个例子是dirty rate的例子,一个例子是dirty rate的例子所以说从high level的角度来讲的话,基本上能guarantee什么呢?就是对于任何一个object来说,如果一个 transaction 两个 transaction 想写到同一个 object, 那它必须等到一个 transaction committed 了以后, 然后才能去拿到锁, 然后再写这个另一个 update所以这个呢实际上是基本上是用lock来实现的,是用row level lock来实现的所以当你写data的时候,如果你没有committed的时候,很多时候比如说,我记得我上研究生的时候,本科的时候上了一个distribute system,就是实践一个committed过程。当你写的时候,比如说你要做一个update的时候,你实际上是把这个写到那个肉的同名文件,然后加一些random number,加你的transaction,我们当时要求加一个transaction number,就比如说我要update row1,那我把 row1这个呢写到一个file里面,然后加一个transaction number。那当我这样committed的时候,我其实把这个row1加我的transaction numberreplace 掉肉依,这样的话相当于是个 committed 的操作那这样的话
[45:00] 那其实是能guarantee就是你在任何情况下你没有corrupted data的,当时是建了个最简单的distribute system的本科的时候。所以是这样子的。所以好对,然后如果你是读的话,假设你在Committed的时候,如果你在读的话那你像我们举刚才的例子就是有一个肉1,然后有个肉1+transaction,然后肉1+transaction因为没有committed,所以你在读的时候就只读肉1的data然后如果这个肉依加transaction committed的话就overwrite这个肉依是吧,你就读了新的data是吧所以你读的时候呢,你要么读old committed data,要么读新的transaction data,所以你不希望在读的时候有blocking,可能会做一些优化然后这是一个 dirty read 和 dirty read 的一个 isolation level, 很多时候觉得看到这个以后,好像这个已经足够了是吧,那会有什么potentially 会有什么地方可能会 go run 呢?那我们再看第二个例子,叫 snapshot isolation这也是相当于在这个讨论期里面讨论最多的一个例子了,就是snapshot isolation这个例子也是比较简单的一个例子,也是比较有趣的一个例子,就是你Account1往Account2里面转100块钱,是吧,Account1和Account2里面转100块钱,那么我在做这个Transfer的时候,我是先从Account1里面加了100块钱,然后从Account2里面减了100块钱,然后again这个时间是无限大的,是吧,所以在另一个用户看的时候,他可能看到Account1的时候,他这个Account1的读是在我这个Transsection之前,Account2的读的时候,是在我这个 transaction 之后然后导致他看到两个账户加起来之后900块钱但我这边呢是看起来是1000块钱就是我 transaction 之前是500块钱我 transaction 之后是就是600 400 是吧所以我这个从我的角度来看我的资算户是Consistent的但是从他的角度来看凭空的少了100块钱这100块钱不知道去哪了这个叫这个reschool 是吧这个也叫做这个就是non-repeatable write如果这个Alice在这个时间点之后再进行一次读的话它会看到一个变成600 一个变成400了那这个造成的最主要的原因是什么呢?是它的这个毒,有一部分毒是在transaction之前的,有一部分毒是在transaction之后的就是你读的不同的路是横跨了一个transaction,一个或多个transaction,因为时间是相对的,你是横跨了一个或多个transaction的。所以就造成了结果就是你读之前,你读第一个object的时候是第一个transaction,你读第二个object的时候是第二个transaction,读第三个object的时候中间又插了几个transaction所以这个是显然是不能接受的,虽然你再读一下的话,Data will consistence,但是这个显然是不能接受的。特别是做analytic的话,比如说你要看每个区的销售量,是吧,如果你是没有snapshot isolation的话,没有snapshot isolation的话,你看的这个销售量可能今天看的时候已经down了10%了,如果大宗商品的话,汽车的销售的话,今天看的时候down了10%了,结果明天一看变成增加了50%。这个很糟糕的是吧,所以这个,所以这个,这个,这个,这个,一般analytic database会要求有一些这个,有时候会要求Snap Shot Accelerator 但是很多时候确实不处理它是有所谓的好,这是Snapshot Isolation,我先停10秒钟,然后看大家有什么问题想问一下嗯谢谢大家好,所以我再看一下,这个snapshot isolation 主要的原因是什么呢?你读的时候,你就是读的时候你有读了不同的 object 多个 object其中一部分object在transaction发生之前,然后另一部分object的transaction发生之后,导致你读的data是inconsistent的。是 snapshot isolation那么我们怎么样去做这个实践来保证 Snapshot Isolation?我们这块一直在讨论,群里面讨论的很火的就是Multi-Version Concurrency Control.然后这个Multi-Version Concurrency Control很重要的一个概念是什么呢?就是你Reader和Writer之间是不相互block,你Reader和Writer之间是相互block,这个是和这个Performance是和这个Two-Face Locking有本质的区别的,我们之后会讨论Two-Face Locking,然后看它这个是怎么样Reader和Writer
[50:00] writer 之间的相互 block比如说在这种情况下在刚才这个例子情况下我进行锐的这个 transaction 我可能是 transaction 12 是吧?就是当我Alice 进行锐的这个 transaction 的时候它是 transaction 12然后她进行转账的这个 transaction 是 transaction 13然后对于每一个transaction来说对于每个data肉来说它不是一个简单的肉了它是一个 sequence of data, a sequence of event所以当我读这个的时候,我会看到我现在读Account1Account1上一个Transaction是Transaction1,并且已经Committed,所以我读的Account1是500块当我读Account 2的时候我读 Account 2 的时候我会看到 Account 2 呢上一个 transaction 我能读到的 transaction它是什么呢?它是Created by Transaction created的它是Transaction created的,它会被deleted,它deleted的话是有点像Single Point List,Single Link List的这种,它是被Transaction 13给deleted掉了,但是Transaction 13在我发生了之后,那我应该ignore它。因为我是Transaction12,Transaction13比我大,我应该ignote它,所以我应该读Transaction13之前的那个transaction的data,那就是我读应该transaction5的data,所以我读出来也是500好好 我们再想一下 我们从这个writer的角度来想一下当我进行对这个 account 1和 account 2 进行 update 的时候当我对Account1和Account2进行update的时候,我现在要从Account1把500变成600 所以我建一个新的block 是吧 就变成这个Create 13,Delete by,相当于这个是最新的状态,Delete by 空的话是最新的状态那我从account 1里面增加100块钱我从Account2里面减少100块钱当我这两个准备好的时候,我同时表示这个 committed,是吧?就是这个 13,这个 transaction 13 被 committed 应该真的是变成 600 和 400 了。那之后的 transaction 应该只成 600 和 400 了。然后,但是之前的 transaction在之前的Transaction,应该还是500,还是500,它应该ignign.就是一个闹新的这个13的committed所以之前的transaction相当于是把它变成一个一个一个原子的点是吧在这个原子点之后我都是600 400在这个原子点之前都是500和500那这个原子点这个single point在哪呢是在这个transaction13就是当这个single point是什么呢是在这个transaction13 committed的这个时间然后你其他transaction没有开始下传现在也没有开始 对吧所以这个的话其实你要考虑是个visibility rule,就是说当你这个Reader的Transaction Started的时候,那么你有没有Committed?所以对于这个来说,因为这个13这个commit的点是在这儿,所以你这个transaction在这个13这个commit点之前,你是读不到13的,在这个commit点之后是读到13。这是一点是吧,然后这相当于是定义了一个 single point,然后在这之前之后是读到不同的 data其次的话,你其实每个Data的每个Role这个Mexico是以肉为Granularity的,每个肉来说谢谢大家对于MySQL的每个Role来说,你都是有个link list的,当你最后面的这个,就是最后面的Data,比如说这个Transaction5,已经close了Transaction 5已经closed了,并且所有能看到Transaction,这个block都已经closed的话,那你这个block是可以被可以被这个回收的就是说,怎么说呢?就是你在13之前的transaction全部close的话,就是在13committed之前开始的transaction,全部close的话,那这个block和这个block就可以被回收了因为没有人能看到就对他了所以它可能会有一个backend process来不停的回收这个data所以假设你这个是三 Committed 之前的Transaction之前开始的交易全部结束的话,这两个block就可以被回收了,因为已经没有data,没有transaction能读到这两个data了。所以这是Snapshot Isolation。我们就是我从这个写的角度和读的角度分开讨论了一下这块但是我们high level的想一下就是不需要想那么细的想一下就是high level想一下因为这个detail的level其实过段时间都会忘的high level的想一下它这个设计理念是什么呢?就是说,它可以认为你这个transaction都是连成一块的,就是你最后的时候,比如说,比如说我们举个例子我去给你了把三笙拿出来谢谢大家请不吝点赞 订阅 转发 打赏支持明镜与点点栏目
[55:00] sumline调出来好然后我们这块比如说举个例子你在这里面写的时候你可能有开始的时候有transaction1是吧然后有transaction3然后有transaction5然后有transaction2这是committed时间段这是committed时间段就是transaction2这是committed时间段那么你在这里面读的时候写的时候是这么committed如果你读的时候的话你不会一部分data读在transaction发生之前一部分data读在transaction之后你一定会有个single point就是一个single point就在这个point然后你进行了读的操作就是read1是吧那你会读到transaction1的data或者是另一个single point比如在这个point的时候你又进行操作那你可能会读到transaction1和3的data就读不到5和2的data所以这是它的highlight我想法它实践的话是通过这么实践的其实想法也比较简单就是我只读我之前在我之前的transaction我之后的transaction全部忽略就是如果是这样的话你是没有转账100块你会读到一个500一个400的情况好对于这个snapshot isolation,大家有什么疑问吗?我有个问题它这里是不是它每个block里面除了有它的有它的id create by, delete by,是不是还有一个Fill是表示这个transaction有没有commit呢还有表示一个transaction没有commit我觉得这个可能还是和实践有关因为如果是serialized的如果看后面的话有一个serialized snapshot isolation的话它会详细的讲哪一个transaction应该被abot然后哪一个transaction应该能commit这块的话如果只是这个也看不同的时间了如果让这个做一个最简单的实践的话那么就是谁先写过来的话谁就committed是吧谁先写过来谁就committed所以他这个的话可能会有lost update的情况这个我觉得是跟时间有关对如果他没有表示这个transaction有没有commit那我在这个id为13的transaction就是下面的这个他这两个operation中间去读那我就读到DirtyWay了这个Commit是有一个单独的地方是记载它Commit的这个Log的所以就像我们刚才看到这个一样就是你有一个单独的地方是记载CommitLog的这是一个单独的地方是记载CommitLog的就是说这块是单独的地方这也和时间相关的是单独的地方记载Commit Log你先读这个先读Commit Log然后再读这个就没有问题了I see它不会写到这个上面的因为你写到这个上面的话你就有问题了你到底写到这个Block上还是写到这个Block上那这两个Block可能值不一样你这话说Committed这话没说Committed怎么办好的那所以 somehowSignature Isolation一定会比ReCommitted要强势了对,一定比read committed要强它实际上在read committed上面时间一定会比read committed强的好的,谢谢好的我有一个问题就是我怎么能知道谁先committed,谁后committed那如果是比如说我database它是一个这个commit就是它不是一个分布式的数据库的话那它很容易知道谁先commit谁后commit但如果它是一个分布式的数据库我怎么能知道谁先commit谁后commit不考虑分布式数据库你单个数据库就够你头疼了其实今天讲的说的课单个数据库就够你头疼了但是这个谁先commit谁后commit还是一个非常复杂的问题在这的话你可能会有种情况是比如说Transaction 13,因为它特别大,所以它先写到这里面,后面有个Transaction 14,又覆盖了Transaction 13那它Commit的时间特别晚,就是Transaction 14先Commit了,然后Transaction 13也没有Commit有可能这种情况,这种情况下的话,这个Database应该还是覆盖的,就是你这块Commit的时候,应该是一个覆盖的操作就是你相当于是这个值呢我举个例子这也看实践了不同database的实践可能会不一样但如果是没有serialized这个snapshot transaction的guarantee的话如果没有serializedsnapshot transaction guarantee的话这种情况下的话可能会出现什么情况呢就是这个14呢因为14先committed所以14在下面1313在上面就是13是最后
[60:00] 写在最后的,14是在它下面的,有可能的,这也跟实践相关就是说,我们说的时候,我们现在谈论的其实我觉得群里面有个讨论特别好我不知道这个Tax的Tax同学是,他是专门研究这个PhD,但是他这个理论非常深厚就是说,其实是有两个东西,一个是Isolation Level,一个是Concurrency ControlIsolation Level是什么意思呢?是一个理论上,我觉得我个人理解更像是一个理论上的概念就是说我现在这个isolation level就讲的是什么意思呢就是我读的时候我不会一半读在transaction一半读在transaction我只会读到一个具体的时间点我只会读到具体的一个时间点所以这个是有snapshot isolationsnapshot isolation是一个isolation level是个理论的概念但我实践的时候像这个是一种实践方式是一种concurrency control的实践方式它只要满足这个isolation level就guarantee Snapshot Isolation那它具体如果它满足这条件以后那它具体怎么实践那到底是覆盖呢还是这个就是 Single Point Fillure不是 Single Point就是那个Poiter 指的时候还是什么 Lock 住 指呢还是怎么样指呢那它实践的问题但是它只要能guarantee这个 Isolation level能guarantee这个high level的东西那它就满足snapshot isolation所以到时候这个committed的时候到13先committed了14后committed那它们之间怎么覆盖呢那是实践的一个问题了不同database可能实践的不一样OK那也就是说我这个东西它能保证我重复读都能读到同一个数是吧你重复读对,如果你没有覆盖的话,你从不读是读同一个书但是如果他读到不同的书呢,他就不是这个不是这个第三个级别了他就是rate committed这个是repeatable rate这个就是您现在讲的repeatable rate那如果是repeatable rate的话,那他一定能保证就是我比如说我这个东西我这个transaction我一个transaction我一直读的话我一直会读到同样一个数字对吧你不干净你读到同一个数字如果你在读第一遍的时候13没有committed读第二遍的时候13committed的话你读的是不同的数字哦那它跟这个recommitted有什么区别呢这个地方我不太理解它和read committed有什么区别read committed这个里面假使就是我们那次回述一下这个里面是read committed这个是read committed为什么是read committed你没有读任何un committed data嗯你只不过是读的时候这个transaction没有发生读读读一的时候transaction没有发生读二的时候transaction发生了但你没有读到任何committeduncommitted data所以这个是read committed嗯但是它不guarantee snapshot isolationOK这个这个东西它就比如我两次就是recommitted它可以比如读两次我发现,就是您刚才说的那个,就是我读两次,比如说,我就两个,我读到两个不同的结果,因为中间有一些committed,是吧?对,对,对,是这个意思。所以你在这个里面其实是read committed,但是你读的data是非常糟糕的,就是你是read committed,但是你读到的data是一个在transaction之前,一个在transaction之后,导致你data inconsistent,但是它是read committed。OK,那如果假如说我是snapshot,就是保证了它repeatable read,那也就是说我两次读之前它不可能有commit,是吧?比如说我一个transaction,两次读之间是不可能有commit的。对,你其实是不是表示你这个读之间,你这个读可能很久的,像我们不是说就是SellSource有的读能持续好几个小时吗这个读可能持续很久但是我读的时候它后面不管有多少个transaction我相当于就只在这了就有点像我拼到这个GitHub这个branch这个version了我之后的时候我都只向这个version之后的version跟我没关了就这个意思OKOK那也就是说我比如说我就像您刚才说这个timestep这个东西就是我就是在这个timestep下我只能读它timestep之前的数据我不能读到之后的数据你只能读到之前的transaction不能读到之后的transaction我只能读到之前的transaction不能读到之后的transaction这样就保证我一个transaction里边多次读它的读到数据是一样的对的对的OK,谢谢好的好,那step
[65:00] file transaction Snapshot isolation, there's no other problem. Thank you. Okay, I'll talk a little bit about this, then I'll just stop. I don't want to talk too much. I think once you've done this, if you've never been able to do this, you'll understand it. I'll just talk about this two-step. I'll talk about this two-step. I'll talk about this two-step. I'll talk about this five-step. I'll talk about this five-step. Then we'll talk about this five-step. Then we'll talk about this five-step. Then we'll talk about this five-step. How? This is a bit of snapshot isolation, which is more than a more important place. This is a bit more simple. This is Read, Modify, Write. It's a very common exercise. For example, you have a counter. This counter is YouTube View. YouTube View is the number of people who are watching. For example, YouTube has 42 people watching. Then you click on the search, you can read 42 and write 43. Now you can see the YouTube page. Then the user is reading 42 and then reading 43. In the situation, you have more people who watch. But you read only once. Incremental is increased. Snapshot isolation is not possible to solve the snapshot. 因为这里面 Every transaction between each time It's the same thing. It's a real-time data. You read the user 2, when user 1 is not committed, so he read the user 1 is not committed. User 1 read the user 2 is not committed, so user 1 is not committed. So it will be a lost update. This was the first time I had seen before. I was a project that was similar to the case, but it was a counter. When I was updated, I thought I had the number of updates as the final one. I found that number was very bad. I used the dynamoDB, but the mongodb. MongoDB. And the loss rate is very high. The data is very slow. I remember the QPS rate was very low. 40-50% of the size, not 40% of the size. I'm just going to click on the test. I'm just going to click on the test. I'm just going to click on the test. I'm just going to click on the test. I'm just going to click on the test. 10-20 QPS, lost 40% of the loss update. In this case, it's not a unique thing. For example, if you just mentioned MongoDB, you can use JSON-Doc. If you just update a number, it's very fast. But if you use JSON-Doc, it's about 100k. Like Mongo, I remember it was 16 megabytes. If you update 16 megabytes, you need a minute. 1 second time to update 16 megabytes That's the time for forever. I think the time for forever is a year. It's almost like a year. It's almost like a year. It's almost like a year. If you have a year update, you will have lost update. It's not just a few things that you have to be able to do. It's like that person has 1000, and then you have to do 50. And then 1000, the process is not done. And on the other hand, you may see the VK update is the same. When the two of us updated VK, the people who wrote the last one Last write, and then just to cover the previous one. This is a lot of solution for the launch update. The most famous solution is Autonomic. Autonomic right solution, just from database of guarantee. From database's perspective guarantee, this is the case. Atomic operation, like this, set update, counter, v-don, v-e, and for update, like this, it's not the middle of the middle of the time. It's not the time period, it's the time period. That's not a problem, it's not a period of time Atomic is just a time frame, that's no problem. You won't have lost updates. You can also write a "sort" box. Explicitly lock in. This is what I think many Southforce engineers have written. You should be able to write this thing. Because this thing is a problem. You should be able to write this thing. This thing is not a problem. You should be able to write this thing. This thing is... You can write this thing and ensure it has lost updates. It can work.
[70:00] 这个Database transaction 写成这个语句呢嗯你肯定可以walk,但你要非常非常小心,否则的话你可能有整个lock database,lock 48 小时,就可能是你写出来的,就比较糟糕了。好,然后呢,有的系统是支持自动的检测这个Lost updated的。有的系统是支持的他这个的话,他就比如说,他这个info是一个read,modify,write,然后再compare一下你写之前是不是一个dirtywrite,是吧?有的系统是支持的,然后谢谢大家这是第三种解决方式,就是有的系统是自动支持的,如果它是Loss Update的话,它会abot然后第四种解决方式呢就是这个 comparison 就是在你写之前 你就是你在写之前你就是写这个update的操作呢我们可以想象写个简单的sigle query 是吧就是update这个row当且紧张,就是update var equal to 42的时候update,如果它不等于42,那这个var这个条件就不满足了是吧?所以这个compare就不满足了,那你这种情况下的话是,你这个compare的set也是可以work的。但compare的set要非常小心,是snapshot isolation还是会丢失data,因为你snapshot,就刚才我们看到过,by定义就是你读的data可能不是最新的data,如果你用snapshot isolation,那这concurrency control的话,你读的data可能不是最新的data,如果你读的不是最新的data的话,当你做compared set的时候,你读的这个data已经不是最新的data,所以你这个set还有可能data loss。所以这ComparedSide是不可能在snapshot的isolation但是总体来说这ComparedSide是比较简单的,比较无脑的一种选择就是当你真的要自己实践的话相对来说比较low risk,比较无脑的一个选择,就是其他的不满足的话就是updateID=43, whereID=42,是吧?当时的警告ID=42的时候,你把它变成43那当你同时有几个transaction进行commit的时候,你相当于是把这个时间段变成了一个时间点那就是就有winner和loser,loser的话可以retry最后的话就是这个CRDT Data Model了,这个CRDT呢我之前讲过,我这块就不讲了,不讲太多了,如果感兴趣的话可以看一下之前这个我记得是在应该是上周了,应该是上周,Replication的时候,讲过这个Uber这块怎么样事件的这个怎么样实践了一个CRDT的一个,怎么样实践了一个on order的hashmap这个database啊,它也是一个CRDT的一个model,就说白了是,我接受你所有的写,我接受你所有的write,你在写的时候非常容易,但你在读的时候,你要做一些额外的,client需要做一些额外的复杂的操作,然后分清,就是谁先谁后,就是写的时候会很容易,但你读的时候会有额外复杂的操作。好,我就讲到这吧,后面的这个也是挺烧脑的,其实。还有七个slides,可能这次的话就是分开点,因为Consensus肯定是一周讲不完的,所以有可能下周和下下周讲完记彰和Consensus,带有什么问题的话,我可以帮忙答一下,或讨论一下。您好,我想问您一个问题,就是刚才您说的这个atomic operation这个问题,就是我看它比如java它里面,它里面有个叫atomic integer,它用那个compare and swap这种方法。你说加班有个什么?Atomic Integer这种class那个不一样,Atomic Integer是Multi-Sped CodingOK它那个实现跟这个实现有什么区别吗?谢谢大家它的底层是用那种锁的都是假设吗?就是,如果...可以可以可以...都是加索嘛,就是都是加索嘛,说白了就是加索逻辑也比较简单,就是在你这个,我不知道大家有没有学过那个operating system里面不是加索那个逻辑,在你要执行critical fields的时候在前面加一个锁,然后在之后unlock 掉是吧?都是枷锁吗? OK,谢谢。我忘了那个具体是怎么讲的,但那个是Multi-Swag的这个,这个是DataBase的但是实践都是加索来实践的,这个其实不完全一样,但是也有些相似的。OK,谢谢.这个时候问一下那个SypeShop的isolation的问题如果减100那个操作如果失败的话是不是就可能要就发那个rollback 然后这个snapshot就被删除了对 我就是想一下
[75:00] 这个snapshot isolation 当你读到这个减100和+100操作当时紧当13committed 如果13committed这个操作从来没有发生过的话 你读到任何操作都是没有13的但是如果第一个加100成功了,第二个减100失败的话,是不是第一个snapshot也会被删除?对的,就会被信出了就才会留办理工的出发就相当于这个没有committed,没有committed的话你就读不到它了就 about it了,对。所以我觉得这个transaction 这块,包括这个isolation control 和这个transaction 这块,你就是一定要想清楚什么是一个时间点。什么是一个时间段 任何一个时间段之间可能有无限多个时间点 有无限多个时间段跟它重合但是如果它是个时间点的变成atomic的话那它就不会有冲突了所以这个Serialize很多时候就把所有的操作变成一个atomic,一个atomic是吧,变成一个时间点,一个时间点,一个时间点。那这样的话就可以排序了然后可以就像,就可以排序了谢谢大家嗨,Scott,我在这里可以问我之前看到的Item Potence的大概我可以啊对就是我想想找你解释一下Idempotence,以及那个Exactly one, at least one 的关系。哦,就是那个Cafka那个是吧?我记得Cafka那个是吧?嗯,那个呢,我也对,嗯,- A message,对,对。 - 对,这个的话,我把那篇文章调出来。其实这个,我的理解不一定比你,就是如果你读过这篇文章的话,我觉得你也可能跟我在同一个level,这个我也是读了一下这个文章,然后理解了一下。我觉得你读过这篇文章的话可能跟我理解是同一个level的这里面有一些K-words要非常小心,因为它是个Sales文章,这个Engineer我见过他本人,我参加conference见过他本人。它是个SELS文章所以有一些SELS文章你要非常小心的读起来其实它就exactly once呢总体来说是非常复杂的就是你很难去guarantee,所以这个卡夫卡很多时候是,就是你这个at least wants 和at most wants 是吧?然后这里面读起来的话,我读了一下,我记得有几个地方很有趣啊,就是你在producer的时候你就可以set这个itempotent的adempotent的在producer的时候setadempotent你producersetadempotent什么意思呢?就是说首先adempotent就是你重复的写你是实际上最后从server端从接收端只有一次写的操作就是多次duplicate的写是没有set effect多次duplicate是没有satisfied所以这种情况下的话它其实是producer写到broker然后consumer从broker上读你在producer那块set item potent的话实际上就是说在这个broker里面你在producer不管写多少次broker是接收的一次谢谢大家OK 那这个是Exactly Once 还是Addis Once? 啊对这个是... 这两个是两个不同的概念。 对这两个不同的概念。然后这个这个Adempotent呢?就是你相当于是这样子就是Exactly wants一定要要求这个producer是adempotentproducer 的血一定是 idem potent,是必须要求充分不必要条件,还是必要不充分条件,必要不充分条件,必要不充分条件,就是你producer 一定是 idem potent,然后除了这个以外呢它的consumer也需要做这个consumer也要做一些额外的这个操作我忘了他 consumer 要做一些额外的什么操作了就像producer和Adam Potent 是以后的true嘛然后这块我觉得有一些地方读的很有趣的是它其实也提供Craft的CraftCraft Topic Transaction但是可以很明显的因为我Transaction Coordinator所以很明显的是在用这个two-phase login来做这个two-phase commit在做这个这个cross topic transaction就是 cross topic 这个 transaction 概念然后它这块有个read committed和read uncommitted,就是刚才我们讲的是吧,read committed,read uncommitted,就是isolation level,就是刚刚讲的第一个level,是吧?我觉得这里面最重要的一点是什么呢?就是consumer这个实践,我记得好像是Consumer这个实践我记得他好像没有写很细,他Consumer这块是用什么样的setting来guarantee exact ones,但是好像,嗯,我这块有个 another takeaway point 是什么呢嗯就是这个它这个Exactly Wants是在卡夫卡系统内的Exactly Wants,它不包含卡夫卡,比如说你要写卡夫卡,变成个Notification System,你这个你其实是这个 notification system就是你consumer读到这个卡卡message以后
[80:00] notification. This is not guaranteed exact amounts, because it is already in Kafka. It's the exact one, it's just in Kafka system. It will produce a message And then consumer sends this message, and produces this message. This is guaranteed exact ones, cross system is not guaranteed exact ones Oh, sounds good. That you mentioned before the notification system. at most once. It's probably ignored failure. At least once, is it used to be an idempotent? At least once, actually, we can think that it can use idempotent, but it can also not use idempotent. You can flash, you can do it 100 times and 1 times. So she at least once more. Oh, right, right, right. It's not related. But you can try to get it. You can try to get it. You can try to get it. Yeah, you can try to get it. But the exact ones are the producers are important. Processing, this part is called Exactly Once. Exactly once, that's what you do. just use a cross system the transition Cross-system transaction with this is not related, because exactly wants is one topic and one topic. It is a system, a node or topic is quite complicated. It is not to think so complicated. Cross-topic exactly wants is two-face locking. Sorry, two-phase commit. I was just saying two-phase commit. - Yes, I know it's in this one. But I said it's the notification system in the notification, so you hope, can be exactly one. That's what they need to distribute the system. I remember there was a protocol, very old. I forgot what it was. It was XAR or what? XA protocol. It's a protocol. It's like REST API. It's a It's as if you've done XA, is the number of the transactions. 理论上的一个东西实际上的话我觉得好像现在基本没有人在用它 I remember Java has a API endpoint. It actually is with two-phase commit It's a similar thing. Two-phase commit, many times it's a "tong-chic" uh,统治了一个,就是Henergineous的一个,一个,一个System, 它这个的话是,你这个你可以,一个是SQL,一个是什么,什么email server也可以,什么只要你实践它的protocol就能guarantee这个,这件事 Exactly one, is it right? Guarantee transaction, but this Now we don't have any use, this is a lot of questions. I see, I see, I see, thank you. Okay, that's it. Thank you. Thank you. Thank you. Thank you.