TiDB从0到1系列
- TiDB-从0到1-体系结构
- TiDB-从0到1-分布式存储
- TiDB-从0到1-分布式事务
- TiDB-从0到1-MVCC
一、事务定义
这属于老生常谈了,无论不管是传统事务还是分布式事务都离不开ACID
- A:原子性
- C:一致性
- I:隔离性
- D:持久性
二、隔离级别
同样都是老一套东西,主要应对脏读、幻读、不可重复读等问题
- 读未提交
- 读已提交
- 可重复读
- 可串行化
在TiDB中,实现了快照隔离 (Snapshot Isolation, SI) 级别的一致性:
事务只能看到早于它开始时刻之前提交的其他事务。
如图,T2是无法访问到T1事务的内容,T3是可以访问T1、T2事务中的内容。
而快照隔离基本是基于Percolator事务模型原理实现的
Percolator 模型
Percolator模型是Google在2010年提出的一种分布式事务处理模型,其设计目标是在大规模分布式系统中实现高效的事务处理。Percolator是一种乐观的事务模型,写写冲突被延迟到事务提交时才会进行检测。可见性与冲突检测依赖于事务的开始时间戳以及提交时间戳。
Percolator模型是一个典型的通过两阶段提交(2PC)进行分布式事务协调的实现。事务的实现主要依靠三个参与者:Percolator客户端、BigTable TabletServer、Timestamp Oracle(TSO)。按照2PC的要求分为两个步骤:PreWrite和Commit。
Percolator模型中,事务执行的过程可以概括为以下几个步骤:
- Percolator客户端作为事务协调者,负责发起事务请求。 BigTable TabletServer接收并处理事务请求。
- Timestamp Oracle(TSO)提供时间戳服务,确保事务的顺序性和一致性。
- 在PreWrite阶段,TSO分配一个时间戳给事务,BigTable TabletServer将事务的写请求记录在本地。
- 在Commit阶段,TSO根据事务的时间戳和BigTable TabletServer的记录,决定是否提交事务。
- Percolator模型的一个创新点在于,它依靠对时间戳的使用和BigTable的单行事务,实现了跨行事务。由于某一行数据可以位于不同的BigTable的TabletServer之上,所以该事务也是跨节点的。
这里也再次印证TiDB中为什么事务在开始前和提交时会分别获取一次TSO。
三、TiDB-分布式事务(单行)
1、Begin(开启一个事务)
此时会从PD获取一个TSO,假设为100
2、SQL
update test set name=‘Frank’ where id=3;
3、Commit(提交事务)
此时再次从PD获取一个TSO,假设为110
4、进入2PC阶段
- prewrite阶段
将数据:3(id)_100(事务开始的TSO),Frank(数据内容)记录在默认链表中
将锁信息:3(id),W(写锁),pk(主锁),3(id),100(事务开始的TSO),Frank(数据内容)记录在锁链表中 - commit阶段
将锁信息:3(id),D(已释放的锁),pk(主锁),3(id),100(事务开始的TSO),Frank(数据内容)记录在锁链表中
将数据:3(id)_110(事务提交的TSO),100(事务开始的TSO)记录在更新链表中
这里有两个细节
1、TiDB中的DML都是以追加的方式实现。
做一个对比
传统关系型数据库:
begin;insert into test(id,name) values(1,'zmz');commit;
begin;update test set name='MySQL' where id=1;commit;
begin:update test set name='TiDB' where id=1;commit;
此时底层其实只有一条数据,就是id:1\name:TiDB
TiDB数据库:
begin;insert into test(id,name) values(1,'zmz');commit;
begin;update test set name='MySQL' where id=1;commit;
begin:update test set name='TiDB' where id=1;commit;
此时底层其实是有三条数据的
id:1\name:TiDB
id:1\name:MySQL
id:1\name:zmz
那么当我们在TiDB查询test表时会查到3条数据吗?当然不会,而是根据提交事务的TSO读取到最新的数据。
那么长期看来是不是会有非常多的冗余无效数据呢?当然也不会,GC模块会定时清理掉历史版本数据。
2、上面提到主锁机制
TiDB的分布式事务中,假设一条事务涉及多行数据,那么将采用主锁机制:只对事务的第一行数据上锁,后面的数据跟随第一行的主锁。
四、TiDB-分布式事务(多行)
1、Begin(开启一个事务)
此时会从PD获取一个TSO,假设为100
2、SQL
update test set name=‘Jack’ where id=1;
update test set name=‘Candy’ where id=2;
3、Commit(提交事务)
此时再次从PD获取一个TSO,假设为110
4、进入2PC阶段
-
prewrite阶段
这里与单行数据事务最大的不同是,第二行数据的加锁信息:
2(id),W(写锁),@1(跟随主锁1),2(id),100(事务开始的TSO),Candy(数据内容) -
commit阶段
主锁释放后,跟随锁一起释放。
再次强调,这里的释放不是删除,而是插入新的锁清理记录。
彩蛋
TiDB可以动态调整事务的隔离级别和锁的实现方式
- 根据transaction_isoiation设置隔离级别
- 根据tidb_txn_mode设置乐观锁还是悲观锁