在分布式数据库系统中,每个节点虽然明确知道自己在事务操作过程中的结果是成功还是失败,但却无法获取其他节点的结果。为了保持事务处理的ACID特性,需引入一个协调者来统一调度所有节点(参与者)的执行逻辑。协调者负责调度参与者的行为,并最终决定参与者是否要把事务真正进行提交。这种通过协调者和参与者共同配合来保证事务原子性的机制叫两阶段提交(2PC),目前大多数分布式数据库普遍采用两阶段提交的方案,比如TiDB、OceanBase等,本篇主要介绍TiDB中的分布式事务能力。
一. 什么是两阶段提交协议(2PC)?
简单描述一下两阶段提交,其实就是把一个事务的commit阶段划分成两个步骤来完成:请求阶段(prepare)和提交阶段(commit)。
请求阶段(prepare):
(1)协调者通知每个参与者准备提交。
(2)参与者在本地执行事务。如果各个参与者本地执行成功,告知协调者本地已经执行成功;如果有参与者执行失败,也会告知协调者本地执行故障。
提交阶段(commit):
(1)协调者根据所有参与者返回的信息,决定是否提交事务。如果全部参与者都反馈成功,协调者通知所有参与者提交事务;如果存在个别参与者反馈失败,协调者通知所有参与者回滚事务。
(2)如果协调者收到全部参与者成功commit信息,完成本次提交。
关于常规的2PC提交流程,可以使用下图中经典的客户存款例子来理解,图中经理相当于协调者的角色,柜员1~柜员3相当于三个参与者角色。
![]() |
2PC在工程实践中已有很多案例,但2PC仍然存在一些潜在的问题,比如阻塞问题、单点问题及数据不一致风险,关于2PC的问题不是本文重点因此不作详细说明。
二. 事务在TiKV中如何存储?
首先了解一下TiDB中分布式事务的一个简单流程(以乐观锁机制为例):当客户端开启一个事务时,首先由TiDB server向PD组件获取一个TSO(start_ts),接下来将需要修改的数据读取到内存中进行修改,当发起commit命令后,便进入了两阶段提交的逻辑。两阶段中的第一阶段为prewrite(预写),它把修改后的数据和锁信息写入到TiKV节点中;第二阶段为commit(提交),它会从PD组件重新获取一个TSO(commit_ts),并结合这个commit_ts写一个最终提交信息,完成事务提交操作。
![]() |
关于prewrite和commit,更具体一点的逻辑如下所述:
(1)Prewrite:TiKV节点使用3个列簇(Column Family,简称CF)来保存事务中的相关信息。第1个是Default CF,用于存储修改后的数据,它会将事务开始时间戳start_ts附加到业务id上(如图中的3_100)。第2个是Lock CF,用于存储锁信息,需要注意的是,TiDB事务逻辑中只会给事务中的第一行修改记录加一把主锁,其它修改记录都依附于这把主锁,图中W代表写锁。第3个是Write
CF,用来存储提交信息,由于此时还未到第二阶段,所以上图中内容为空。
(2)Commit:首先从PD获取提交时间戳commit_ts,然后在Write CF中写入提交信息,此时会把commit_ts附加到业务id(如图中的3_110),事务的start_ts也会记录到提交信息中。写完提交信息后,需要将锁进行清理,这是通过在Lock CF中插入一条新的清理信息实现,图中的D代表删除这个锁信息。
以上动作完成后,事务彻底提交完成,如果此时客户端想查询被修改的这条数据,逻辑是首先从TiKV节点的Write CF中查找最近一次修改的数据,比如3_110,这样便能得到3_110这个数据所在事务的开始时间是100,通过3_100去Default
CF中查找到需要的数据。
Write CF并不仅仅保存提交信息,TiDB规定当用户写入的行数据长度小于255字节时数据会被存储在Write CF,否则被存入到Default CF中。
三. TiDB如何解决分布式事务?
前面提到TiDB在事务中如果修改多条数据,只会在第一条记录上增加一把主锁,其他记录上的锁都是一个用于指向主锁指针。如下图所示,TiKV Node 2中Lock CF中的@1代表它是一个指针,指向Node 1中的锁。当有节点异常宕机后,会根据锁的指针来决定是否需要重做丢失的部分来保证最终的事务一致性。
![]() |
四. TiDB中的MVCC多版本并发控制?
传统的数据库一般通过先保存一份修改前的数据副本来实现MVCC技术,这样同时发生的读操作和写操作都是基于不同的数据副本发生,从而避免读和写的并发冲突问题。TiKV底层存储采用LSM Tree的存储结构,无论是插入、更新还是删除都是以一条新记录的形式写入到LSM Tree的MemeTable中并在之后进行压缩合并,有关LSM Tree存储内容可参考另一篇文章 浅谈TiKV中的数据持久化存储 (qq.com) 了解详情。基于这种实现机制,TiDB中很容易保存数据的多个版本,天然便于实现MVCC。







