TiDB 实现了快照隔离级别的分布式事务,支持悲观锁、乐观锁,同时也解决了大事务的难点。
事务是数据库的基础,提供高效的、支持完整 ACID 的分布式事务更是分布式数据库的立足之本。事务是数据库执行的最小单元,允许用户将多个读写操作组合为一个逻辑单元。事务需要满足原子性、一致性、隔离性和持久性,也就是 ACID。
数据库有多种并发控制方法,乐观并发控制(OCC):在事务提交阶段检测冲突 悲观并发控制(PCC):在事务执行阶段检测冲突
乐观事务:
TiDB 基于 Google Percolator 实现了支持完整 ACID、基于快照隔离级别的分布式乐观事务。TiDB 乐观事务需要将事务的所有修改都保存在内存中,直到提交时才会写入 TiKV 并检测冲突。
Percolator 使用多版本并发控制来实现快照隔离级别,与可重复读的区别在于整个事务是在一个一致的快照上执行
TiDB 使用 PD 作为全局授时服务(TSO)来提供单调递增的版本号:
事务开始时获取 start timestamp,也是快照的版本号;事务提交时获取 commit timestamp,同时也是数据的版本号
事务只能读到在事务 start timestamp 之前最新已提交的数据
事务在提交时会根据 timestamp 来检测数据冲突
TiDB 使用两阶段提交来保证分布式事务的原子性,分为 Prewrite 和 Commit 两个阶段:
Prewrite:对事务修改的每个 Key 检测冲突并写入 lock 防止其他事务修改。对于每个事务,TiDB 会从涉及到改动的所有 Key 中选中一个作为当前事务的 Primary Key,事务提交或回滚都需要先修改 Primary Key,以它的提交与否作为整个事务执行结果的标识。
Commit:Prewrite 全部成功后,先同步提交 Primary Key,成功后事务提交成功,其他 Secondary Keys 会异步提交。
乐观事务模型在分布式系统中有着极大的性能优势,但为了让 TiDB 的使用方式更加贴近传统单机数据库,更好的适配用户场景,TiDB v3.0 及之后版本在乐观事务模型的基础上实现了悲观事务模型。
乐观事务模型在冲突严重的场景和重试代价大的场景无法满足用户需求,支持悲观事务可以 弥补这方面的缺陷,拓展 TiDB 的应用场景。
悲观事务在 Percolator 乐观事务基础上实现,在 Prewrite 之前增加了 Acquire Pessimistic Lock 阶段用于避免 Prewrite 时发生冲突:
每个 DML 都会加悲观锁,锁写到 TiKV 里,同样会通过 raft 同步。
悲观事务在加悲观锁时检查各种约束,如 Write Conflict、key 唯一性约束等。
悲观锁不包含数据,只有锁,只用于防止其他事务修改相同的 Key,不会阻塞读,但 Prewrite 后会阻塞读。
提交时同 Percolator,悲观锁的存在保证了 Prewrite 不会发生 Write Conflict,保证了提交一定成功。
TiDB 支持乐观事务和悲观事务,并且允许在同一个集群中混合使用事务模式。由于悲观事务和乐观事务的差异,用户可以根据使用场景灵活的选择适合自己的事务模式:
乐观事务:事务间没有冲突或允许事务因数据冲突而失败;追求极致的性能。
悲观事务:事务间有冲突且对事务提交成功率有要求;因为加锁操作的存在,性能会比乐观事务差。