一、对你有什么帮助
-
一个疑问:同一行记录 反复修改该如何存储呢?为什么刚写入数据,却查询不出来
-
本文聚焦:TiKV 数据库的 MVCC(多版本并发控制)机制
通过巧妙的 key 编码和多列族架构实现了完整的 MVCC 支持。
大纲 -
原生LSM-Tree IO读写过程
-
为了支持MVCC ,不修改LSM-Tree底层结构基础上,需要设计那些新的数据结构?
-
基于MVCC机制下IO流程优化?
二、最少依赖知识:快照链特点
大家都都知道
Redis是一个内存数据库,
其首要设计目标是将内存中的复杂数据结构(字符串、哈希、列表等)持久化到磁盘文件
内存特点就是:速度快,容量少 ,
Redis采用 fork()加Copy On Write是一种利用操作系统原生能力、实现相对简单且高效的快照方案
假如文件系统 磁盘 想采用提供方式用于实现磁盘卷多版本管理的空间优化型快照技术
肯定不不会这样设计,
哪怕采用全闪磁盘 特点也是 空间容量大,
-
存储系统采用虚拟化存储技术。
存储池中创建的LUN由元数据卷(Meta Volume)和数据卷(Data Volume)两部分组成 -
快照生成并激活后,存储系统在源LUN所在的存储池中动态划分一部分存储空间,用于保存写前拷贝数据。同一个源LUN对应的所有快照LUN共享同一个COW数据空间。COW数据空间包括COW Meta区域和COW Data区域:
-
快照链(Snapshot Chain):多个快照按照时间顺序形成的链式结构。每个快照记录了一个特定时刻的数据状态,并链接到前一个快照,以支持数据恢复和管理。
三、正式开始 探索MVCC之旅
3.1 原生LSM-Tree IO读写过程
参考 302-TiDB 高级系统管理 内容
旁白:不考虑 MVCC ,rockdb raft transaction部分内容,只关注rocksdb 本身
IO 写入过程
IO读取过程
CF三连问(1):Column Family (CF)基本原理是什么?
- 所有列族共享同一个预写日志(WAL),这确保了跨多个列族的写入操作可以具有原子性(要么全部成功,要么全部失败)。
- 每个列族都拥有**自己独立的MemTable(内存表)、Immutable MemTable和SST文件
CF三连问(2):这样设计有什么意义
-
清晰的数据组织:开发者可以将不同类型、不同业务或不同生命周期的数据存入不同的CF
通过CF,TiKV成功地将元数据、主体数据、临时状态数据(锁)和内部协调数据进行了物理分离和逻辑统一管理,符合现实设计意义
-
高效的独立操作:由于数据物理分离,对某个CF的增、删、改、查以及Compaction(合并)操作,基本不会影响其他CF的性能
CF三连问(3):还是不明白,举例说明
第一次去 首都图书馆占地面积3.8万平方米,百万册图书,
简直是刘姥姥进大观园,找一本太难了,假如你是馆长
不会把所有东西都堆在一个房间里,而是分成了四个功能不同的房间,
每个房间就是一个“列族”。
下面我们来“参观”一下这个图书馆的四个房间:
| 列族 (CF) | 图书馆中的角色 | 具体职责与特点 |
|---|---|---|
write (写) |
图书目录卡 | 这是图书馆的检索核心。它不存整本书,只记录每本书的关键信息:书名(Key)、入库时间(MVCC的开始和提交时间戳)、以及这本书在哪个书架(如果书很薄,甚至会把整本书内容抄在卡片背面)。在TiKV中,它存储真实的写入数据和MVCC信息,如果数据小于255字节,就直接存在这里 |
default (默认) |
藏书库 | 这里是存放实体书的地方。当一本书太厚(数据大于255字节),目录卡(write)上写不下全部内容,就会在这里存一份完整的副本,并在目录卡上备注“藏书库,A区3排2架”。这样,目录卡就能保持轻便,查询更快 |
lock (锁) |
借阅登记处 | 这是一个临时工作台。当有人要修改某本书时,需要在这里登记“此书正在修订中,暂不外借”。这个记录是临时的,一旦修改完成(事务提交),记录就会立刻被擦除。如果这里排了长队,说明系统可能出问题了 |
raft (raft) |
馆长办公室 | 这个房间很小,只存放图书馆的内部管理文件,比如各个区域的分区图、值班表等。普通读者(用户)完全不用关心这里,它只对图书馆管理员(TiKV自身)有用 |
所以,TiKV创建这四个列族,根本目的是对数据进行逻辑上的“分房间管理”:
write:存核心索引和小数据,追求最快的查询速度。default:存大数据本体,是write的“仓库”。lock:存临时的事务锁,生命周期短。raft:存内部元数据,体量极小。
当读者(事务)想找某本书在2024年(read_ts)的最新版本时,
他只需去索引卡片柜(write列族),找到该书在2024年之前最新的出版记录卡片,
然后根据卡片指引,去藏书库找到对应的书籍。
**它通过集中管理所有数据的版本元信息(时间戳)和访问路径,使得系统能够以O(log N)的复杂度,快速定位到任意Key在任意时间点下的正确数据版本,从而高效地实现了快照隔离级别
具体存储如下:
3.2 为了支持MVCC ,不修改LSM-Tree底层结构基础上,需要设计那些新的数据结构?
TiKV的MVCC(多版本并发控制)机制与底层LSM-Tree(RocksDB)的结合,并非简单的叠加
而是通过一系列精妙的设计与改进,实现了高性能、高并发的分布式事务处理。
其核心改进在于将MVCC版本信息内嵌到Key中,并利用LSM-Tree的特性进行高效管理
TiKV使用 RocksDB 作为底层存储引擎没有改变
LSM-tree 结构具有以下特点:
- 分层存储:数据在内存中的 MemTable 和磁盘上的 SST files 之间分层存储
- 有序排列:Key 按字典序排列,版本号较大的排在前面
- 持久化:所有数据最终持久化到磁盘,不是纯内存存储
核心改造机制
1. 最根本的改进 时间戳编码的 Key 设计
Key三连问(1) 时间戳编码的 Key 设计是什么?
- TiKV 通过在用户 key 后追加时间戳来实现版本化存储
- 这种设计利用了 LSM-Tree 的有序性,使得版本号较大的 key 排在前面,便于版本查找。
// 构造带时间戳的 key
let k = key.clone().append_ts(start_ts);
let val = self.snapshot.get(&k)?;
Key三连问(2) 为什么这样设计,而不是直接向ob那样直接修改 lsm table结构,去直接改造了RocksDB本身?
TiKV选择将时间戳编码到Key中,而不是像OceanBase那样直接改造LSM-Tree(RocksDB)的内部结构,是一个深思熟虑的架构权衡。
- 保持兼容性和可维护性
- 上游兼容: 避免维护自定义 RocksDB 分支,减少维护成本
- 版本升级: 可以跟随 RocksDB 官方版本升级,获得性能改进和 bug 修复
- 社区支持: 享受 RocksDB 社区的生态支持和优化
相反:直接改造,懂c++不多呀,尤其是研究生 都搞rust去了。
- 极高的工程复杂度和维护成本:需要深入改动一个像RocksDB这样复杂的存储引擎的每一层,包括WAL、MemTable、SSTable格式、Bloom Filter、索引块、Compaction算法等。任何改动都可能引入难以预料的稳定性问题,且需要团队具备极其深厚的存储内核研发能力。
- 与上游社区脱节:改造后的存储引擎将成为一个分支,难以持续、平滑地合并上游RocksDB社区的优化和修复,需要投入巨大精力进行二次维护和融合。
将时间戳编码到Key中的外部编码”方案,其核心优势可高度概括为以下三点,
-
实现高效的历史数据查询:由于LSM-Tree(及同类存储如HBase)的数据在物理上按键的字典序有序存储,将时间戳作为Key后缀,使得同一个逻辑Key的所有历史版本在磁盘上连续排列。这天然地将“按时间点查询特定版本”或“按时间范围扫描”的复杂逻辑,转化为了在有序序列上的高效Seek或范围扫描操作,极大提升了基于时间戳的快照读和范围查询性能。
-
为分布式事务提供原子性基石:该设计完美契合了类似Google Percolator的分布式事务模型。在此模型中,全局唯一、单调递增的时间戳是协调事务状态(开始、提交、回滚)的核心。将
start_ts和commit_ts编码到不同数据(如write列族)和锁(lock列族)的Key中,使得跨多行、多表的事务状态变更,可以通过对一组带有特定时间戳的Key进行原子操作来实现,这是构建跨节点一致性的直接且清晰的方式。 -
简化多版本数据的生命周期管理:MVCC机制会产生大量过期数据版本。由于所有版本按时间戳有序存储,垃圾回收(GC)机制变得非常简单直接:
3.3 为了支持MVCC ,分布式事务如何存储的
TiKV 采用了 [Google Percolator] 这篇论文中所述的事务模型
Percolator is built based on Google’s BigTable, a distributed storage system that supports single-row transactions. Percolator implements distributed transactions in ACID snapshot-isolation semantics, which is not supported by BigTable. A column c of Percolator is actually divided into the following internal columns of BigTable:
c:lockc:writec:datac:notifyc:ack_O
分布式事物三连问(1) Google Percolato 主要内容是什么
Percolator在BigTable中,并非直接将用户数据存入一个简单的键值对。
为了实现事务,它为每一行用户数据引入了三个特殊的“元数据列”(在TiKV中对应为lock, write, default 列族)
data列:存储事务写入的实际数据值。其Key的格式为{用户row, 用户column, start_ts},Value是用户数据。这创建了一个由start_ts标识的、尚未提交的数据版本write列:存储提交记录,标志着某个数据版本已成功提交。其Key的格式为{用户row, 用户column, commit_ts},Value是对应的start_ts。通过查询write列,可以找到在某个时间点(commit_ts)已提交的最新数据版本指向哪个data记录lock列:存储进行中事务的锁。其Key为{用户row, 用户column},Value包含锁持有者的信息(如primary lock的位置)。用于在事务提交过程中防止其他事务干扰
将start_ts和commit_ts作为Key的一部分,分别编码到data和write列中。
将
start_ts和commit_ts作为Key的一部分,分别编码到data和write列中。
分布式事物三连问(2) 分布式事物如何存储到KVDB中。
你会发现
- Default:update 3 =frank,记录最新修改内容,至于3以前内容不记录
- 记录lock区域:begin 加锁操作
- 记录lock区域:commit 提交 有增加一个D 记录
优化 :小文件存储,小于255字节直接存储到wirte 索引区域
- 数据存储到多个节点怎么加锁,主锁 和非主锁
分布式事物三连问(3) 这个存储方式对IO读写有什么影响?
写 id=1,不影响读
- 写写互斥
4. 基于MVCC机制下IO流程优化
- TiKV的MVCC实现受到Google Percolator论文的启发
- 三列族设计分离了数据、元数据和锁,优化了读写性能
- 时间戳由PD(Placement Driver)全局分配,确保事务顺序
- 支持乐观锁和悲观锁两种事务模式
TiKV 通过三个列族(Column Family)来存储MVCC数据 txn.rs:163-179 :
| 列族 | 用途 | 存储内容 |
|---|---|---|
| CF_DEFAULT | 存储实际数据值 | key + timestamp → value |
| CF_WRITE | 存储事务元数据 | key + commit_ts → Write(start_ts, type) |
| CF_LOCK | 存储活跃事务锁 | key → Lock(start_ts, primary, ttl) |
RocksDB 作为 TiKV 的核心存储引擎,用于存储 Raft 日志以及用户数据。
每个 TiKV 实例中有两个 RocksDB 实例,一个用于存储 Raft 日志(通常被称为 raftdb),
另一个用于存储用户数据以及 MVCC 信息(通常被称为 kvdb)。
kvdb 中有四个 ColumnFamily:raft、lock、default 和 write:
- raft 列:用于存储各个 Region 的元信息。仅占极少量空间,用户可以不必关注。
- lock 列:用于存储悲观事务的悲观锁以及分布式事务的一阶段 Prewrite 锁。当用户的事务提交之后,lock cf 中对应的数据会很快删除掉,因此大部分情况下 lock cf 中的数据也很少(少于 1GB)。如果 lock cf 中的数据大量增加,说明有大量事务等待提交,系统出现了 bug 或者故障。
- write 列:用于存储用户真实的写入数据以及 MVCC 信息(该数据所属事务的开始时间以及提交时间)。当用户写入了一行数据时,如果该行数据长度小于或等于 255 字节,那么会被存储 write 列中,否则该行数据会被存入到 default 列中。由于 TiDB 的非 unique 索引存储的 value 为空,unique 索引存储的 value 为主键索引,因此二级索引只会占用 writecf 的空间。
- default 列:用于存储超过 255 字节长度的数据。
读放大问题如何解决
TiDB 的 MVCC 多版本数据存储实现机制,在 Key 上会标识数据版本。
– TiDB 数据存储结构示例
Key: table_id{row_id}_timestamp
Value: column_data + transaction_info
– 实际存储格式:
– Key: t{123}_r1_v5 (table 123, row 1, version 5)
– Value: {name: “John”, age: 25, start_ts: 5, commit_ts: 10}
TiDB的MVCC机制通过在Key中编码时间戳(版本号)来实现快照隔离和并发控制,
这是一种经典且强大的设计
由于RocksDB(TiKV的底层存储引擎)按Key的字典序存储数据,同一行数据(t{123}_r1)的所有历史版本(v1, v2, v3…)会在物理磁盘上连续排列。[读磁盘就是慢 这个问题本身无法解决]
问题场景举例:
假设表
123中行r1被频繁更新了1000次,那么磁盘上就会存在 t123_r1_v1 到 t123_r1_v1000 这1000个Key。
当执行一个需要读取最新数据的查询(例如 SELECT * FROM table WHERE id=1)时,TiKV的读取逻辑(MvccReader)需要:
- 在
writeCF 中,定位到t123_r1这个前缀。 - 向后扫描(Seek)所有以
t123_r1开头的Key,直到找到小于或等于当前事务快照时间戳(start_ts)的最大版本号对应的记录。 - 如果这个最新版本是一个
Rollback记录或已被删除,则需要继续向前扫描寻找上一个有效版本。
这个过程意味着,即使你只想读取一行数据的最新状态,存储引擎也可能需要实际扫描该行的数十甚至数百个历史版本。对于范围查询(如 SELECT * FROM table WHERE id BETWEEN 1 AND 1000),这个问题会被指数级放大、
TiKV的优化:正是为了应对这一问题,TiKV在v8.5.0引入了 MVCC内存引擎(In-Memory Engine, IME)。
IME的核心思想是将最新的数据版本缓存在内存中。
当进行扫描时,系统优先从内存中查找数据,如果可以命中,
则能完全避免在磁盘上遍历大量历史版本,
从而大幅提升扫描性能。
TiKV MVCC 内存引擎 (In-Memory Engine, IME) 主要用于加速需要扫描大量 MVCC 历史版本的查询
IO写过程
- 写入路径:MVCC的写入在TiKV层面变成了带时间戳的新Key的插入。这完美契合了LSM-Tree将随机写转换为顺序写的核心优势。写操作只需追加写入WAL和MemTable,性能很高。
Phase 1: Prewrite阶段
Prewrite命令处理
Prewrite命令负责第一阶段的数据写入 prewrite.rs:495-558 :
- 创建MVCC事务:使用
start_ts初始化MvccTxnprewrite.rs:539-543 - 冲突检测:检查写冲突和锁冲突
- 写入数据:将数据写入
CF_DEFAULT,锁写入CF_LOCK
Phase 2: Commit阶段
Commit命令处理
Commit命令负责第二阶段的提交 commit.rs:52-97 :
- 验证时间戳:确保
commit_ts > lock_tscommit.rs:54-59 - 提交每个key:遍历所有key执行提交操作
- 生成WriteResult:返回提交结果
总结
TiKV的MVCC机制并非在LSM-Tree之上做简单封装,而是通过 “编码内化、数据分离、异步回收” 三大核心设计,与LSM-Tree深度集成:
- 将版本号编码进Key,利用LSM-Tree有序存储特性实现高效版本检索。
- 利用Column Family分离数据职责,优化访问模式与缓存效率,适配事务状态管理。
- 构建基于时间戳的异步垃圾回收,与LSM-Tree的Compaction机制协同,控制存储成本。
这些改进使得TiKV在继承LSM-Tree高写入吞吐、高压缩效率优点的同时,具备了处理分布式事务、支持快照隔离级别的能力,从而成为TiDB HTAP架构的坚实存储基石。
。其核心改进在于将MVCC版本信息内嵌到Key中,并利用LSM-Tree的特性进行高效管理,从而在保持LSM-Tree高写入吞吐的同时,支持了复杂的快照读和事务隔离
参考资料
- tikv/tikv | DeepWiki
- 博客 - TiKV RocksDB读写原理整理 | TiDB 社区
- TiDB 数据库的存储
- https://github.com/feitian124/tidb-course-302
- 302-TiDB 高级系统管理
- https://learn.pingcap.cn/learner/course/1290019
- Talent Plan 之 TinyKV 学习推荐课程
- https://learn.pingcap.cn/learner/course/390002
- [3]TiKV 源码解析系列文章(十三)MVCC 数据读取
- [4]TiKV 源码解析系列文章(十二)分布式事务
- TiKV 的 MVCC(Multi-Version Concurrency Contro) 博客 - TiKV 的 MVCC(Multi-Version Concurrency Control)机制 | TiDB 社区
- 博客 - TiKV MVCC 内存引擎 In-Memory Engine 实现数据多版本场景性能优化 | TiDB 社区 TiKV MVCC 内存引擎 In-Memory Engine 实现数据多版本场景性能优化
祝:
下定决心:
努力不挣钱没关系,
关键不要赔上百万,千万,
熬夜看手机就是 对眼睛,无法恢复的伤害。
一个亿也无法挽回。
手碰一下手机,耳朵听手机声音,看手机屏幕,还是看消息内容
陷入这样 虚拟世界,
无论现实遇到什么问题,哪怕活不到好工作,好项目,好机会
哪怕什么都不懂,努力 0收入 赔上百万,千万都不重要。
都不超过1个亿,最后还是赚了。
2026 重启手机,重启人生
对你操作系统赋予新意义开启
不要独自一个人看手机,
我们常常陷入这样的场景:
独自一人时,在餐厅、地铁、卧室、沙发或书桌前,
当你躺在那里,趴在哪里,做在哪里时候,身体固定狭小空间,无法互动 ,不自觉地掏出手机。
身体被困在狭小的物理空间里,无法动弹,
只能目光便只能被那方寸屏幕牢牢吸引,
你行为被 多巴胺诱惑,简单舒服即使反馈奖励 ,被平台设计各种陷阱控制
除非拥有极强的意志力,根本不选择痛苦迟到的奖励
与其对抗本能,甚至平台 不如改变环境。
请选择去户外,去操场,视眼开阔 看手机。
请主动为你的手机使用选择更健康的场景
核心行动准则1:为特定场景设立无手机时间
- 进入公司开始工作时
- 下班回到家中时
- 在餐厅用餐或社交时
- 乘坐地铁通勤时
行动建议:
- 在上述场景开始时,立刻将手机放入书包或固定在某个位置(如抽屉)。
- 给自己设定一个专注时限,例如至少接下来的3小时内不主动查看。
- 这能有效打破“无聊就刷手机”的循环,把注意力还给当下的人和事。
核心行动准则2:换个开阔的地方看手机
- 早晨起床后
- 下班之后
- 周末时光
行动建议: - 可以选择去图书馆、咖啡馆、商场中庭或景点休息区,公司园区,马路边
- 在这些具有公共生活感的场所使用手机,
- 周围的环境流动能天然地分散你对屏幕的过度专注,避免陷入无休止的刷屏。
一句话描述:
普通人最简单方式,重启自己操作系统
- 固定21点入睡:1 R90睡眠方案之所以能这样的世界顶尖运动员所青睐,每天晚上的睡眠规律你可以
- 固定6点起床:2 成不了作家 你可以打开笔记本写一行文字,3 做不出产品产品你打开软件写一行代码,4 无法演讲信服的话,你自己说一句话。5 成不运动健身达人 你走到运动走一步
今日记录
第11/30天:晚上21点以后不打开电脑控制不了
远离手机:每天5个小时 明天降低30分钟


















