万物皆可KV(1)TIDB存储布局分析

1. 总体布局

text 复制代码
SQL 对象或一行数据
│
├─ Schema 元数据
│  ├─ Key:   "m" + 结构前缀 + 对象逻辑名/字段名
│  └─ Value: JSON、十进制文本、原始字符串或空
│
├─ 主记录
│  ├─ Key:   "t" + 物理表/分区 ID + "_r" + 行句柄
│  └─ Value: row v1 或 row v2;只保存非句柄、非虚拟生成列
│
├─ 行存索引
│  ├─ Key:   "t" + 表/分区 ID + "_i" + 索引 ID + 有序列值 [+ 行定位符]
│  └─ Value: 行定位符、分区 ID、恢复数据及兼容标记,或单字节占位
│
└─ 事务与命名空间层
   ├─ Key:   API V1 保持不变;API V2 前置 0x78 + 3 字节 keyspace ID
   └─ Value: keyspace codec 不修改;MVCC 以 Put/Delete 和时间戳管理版本

这里最重要的边界是:

  • Key 决定对象命名空间、排序、点查和范围扫描边界。
  • Value 保存不能或不宜放入排序 Key 的对象内容、非主键列和行定位附加信息。
  • Key/Value 关系 是"一个可排序身份对应一个对象版本或索引入口",而不是把
    完整 SQL 行简单序列化到一个不透明键名下。

2. 由当前生产编码器生成的真实样例

以下字节不是手工拼接。它们由当前生产编码器生成,再由对应生产解码器反向解析。

转义表示便于观察 ASCII 边界,Hex 是完整字节。

2.1 数据库元数据:数据库 42 app

逻辑对象是 ID 为 42 的数据库。它保存在 DBs 哈希的 DB:42 字段。

Key

text 复制代码
转义: "mDBs\x00\x00\x00\x00\x00\xfa\x00\x00\x00\x00\x00\x00\x00hDB:42\x00\x00\x00\xfc"
Hex:  6d4442730000000000fa000000000000006844423a3432000000fc

Value

json 复制代码
{"id":42,"db_name":{"O":"app","L":"app"},"charset":"utf8mb4","collate":"utf8mb4_bin","Deprecated":{},"state":5,"policy_ref_info":null}
text 复制代码
Hex: 7b226964223a34322c2264625f6e616d65223a7b224f223a22617070222c224c223a22617070227d2c2263686172736574223a22757466386d6234222c22636f6c6c617465223a22757466386d62345f62696e222c2244657072656361746564223a7b7d2c227374617465223a352c22706f6c6963795f7265665f696e666f223a6e756c6c7d

Key/Value 关系 :Key 唯一定位数据库元数据字段;134 字节 Value 是完整数据库

定义 JSON。反向解码得到哈希名 DBs、字段 DB:42、数据库 ID 42 和名称 app

2.2 row v2:表 100、句柄 42

示例逻辑行是:

text 复制代码
id=42(聚簇主键,不重复写入 Value)
name="Alice"
score=-7
note=NULL

列 ID 分别为 1、2、3、4;note 的显式 NULL 需要保留。

Key

text 复制代码
转义: "t\x80\x00\x00\x00\x00\x00\x00d_r\x80\x00\x00\x00\x00\x00\x00*"
Hex:  7480000000000000645f72800000000000002a

Value

text 复制代码
转义: "\x80\x00\x02\x00\x01\x00\x02\x03\x04\x05\x00\x06\x00Alice\xf9"
Hex:  80000200010002030405000600416c696365f9

逐段解释 Value:

text 复制代码
80          row v2 版本字节
00          flags:小列 ID、小偏移、无行校验和
02 00       2 个非 NULL 列(小端)
01 00       1 个 NULL 列(小端)
02 03       非 NULL 列 ID:2、3
04          NULL 列 ID:4
05 00 06 00 累计数据结束偏移:5、6
41...65     "Alice"
f9          -7 的最短一字节补码

Key/Value 关系 :Key 中的表 ID 100 和句柄 42 唯一定位主记录;Value 只保存列

2、3、4 的状态。反向解码恢复 name="Alice"score=-7note=NULL,并由

Key 恢复 id=42

同一逻辑 Key 在 API V2、keyspace ID 0x010203 下的物理 Key 是:

text 复制代码
780102037480000000000000645f72800000000000002a

前 4 字节 78 01 02 03 只属于物理 Key 命名空间;Value 保持不变。

2.3 唯一索引:name="alice" 指向句柄 42

索引属于表 100,索引 ID 为 1,没有 NULL,因此是 distinct 唯一入口。

Key

text 复制代码
转义: "t\x80\x00\x00\x00\x00\x00\x00d_i\x80\x00\x00\x00\x00\x00\x00\x01\x01alice\x00\x00\x00\xfc"
Hex:  7480000000000000645f69800000000000000101616c696365000000fc

Value

text 复制代码
Hex: 000000000000002a

Key/Value 关系 :Key 在索引值后不再附加句柄,因此相同 alice 只能对应一个

入口;8 字节大端 Value 保存整数句柄 42。反向解码从 Key 得到表 100、索引 1、

alice,从 Value 得到句柄 42。

2.4 非唯一索引:name="alice" 与句柄 42 共同唯一

索引属于表 100,索引 ID 为 2。

Key

text 复制代码
转义: "t\x80\x00\x00\x00\x00\x00\x00d_i\x80\x00\x00\x00\x00\x00\x00\x02\x01alice\x00\x00\x00\xfc\x03\x80\x00\x00\x00\x00\x00\x00*"
Hex:  7480000000000000645f69800000000000000201616c696365000000fc03800000000000002a

Value

text 复制代码
转义: "0"
Hex:  30

Key/Value 关系 :句柄 42 附在 Key 末尾,使多个同值行能够共存并按句柄排序;

Value 只是兼容占位字节。反向解码能从 Key 单独恢复索引值和句柄。

3. Schema 到 KV

3.1 结构化元数据 Key

所有这里讨论的 Meta Key 都以 ASCII m 开头。其后有两种主要形式。

字符串项:

text 复制代码
Key   = "m" | EncodeBytes(logical-key) | EncodeUint('s')
Value = 调用方原始字节

哈希字段:

text 复制代码
Key   = "m" | EncodeBytes(hash-key) | EncodeUint('h') | EncodeBytes(field)
Value = 调用方原始字节

EncodeBytes 把输入按 8 字节分组,每组补零后增加一个 0xff-padCount 标记;

长度恰好为 8 的倍数时仍增加终止组。因此 Key 可无歧义解码,并保持字典序。

EncodeUint 是 8 字节大端无符号整数;上面的 's''h' 实际占其最低字节。

旧版本曾有哈希元计数 Key。当前写路径不维护它,哈希长度由字段前缀扫描得到;

列表的 L/l 结构编码仍是通用兼容能力,但当前 Schema 主路径没有生产调用者。

3.2 Schema 对象矩阵

对象 Key Value Key/Value 关系与生命周期
数据库 DBs 哈希,字段 DB:<dbID> 完整 DBInfo JSON Key 按数据库 ID 定位;更新重写 Value;删除移除字段
表、普通视图、序列定义 DB:<dbID> 哈希,字段 Table:<tableID> 完整 TableInfo JSON Key 体现所属数据库和对象 ID;更新定义重写整个 Value
列、索引定义、外键、检查/唯一约束、分区、视图 SQL、序列选项 无独立 Key 嵌入 TableInfo JSON 与表定义原子读取;局部修改具有整 Value 重写成本
表/索引/自增/自随机/序列数值 DB:<dbID> 哈希中的 TID:IID:TARID:SID:SequenceCycle: 字段 十进制 ASCII 整数 Key 区分分配器种类和表 ID;Value 支持原子增量语义
放置策略 Policies 哈希,字段 Policy:<id> 魔数 0x00 + PolicyInfo JSON Key 按策略 ID 定位;表/库 JSON 只保存引用
数据脱敏策略 MaskingPolicies 哈希,字段 MaskingPolicy:<id> 魔数 0x00 + JSON Key 按策略 ID 定位;定义集中保存
资源组 ResourceGroups 哈希,字段 RG:<id> 魔数 0x00 + ResourceGroupInfo JSON Key 按资源组 ID 定位
Schema 全局版本 字符串逻辑 Key SchemaVersionKey 十进制 ASCII 版本 每次 Schema 变更推进 Value
Schema diff 字符串逻辑 Key Diff:<version> SchemaDiff JSON Key 中版本可点查;Value 描述该版本变化
全局 ID 与策略 ID 分配器 多个固定字符串逻辑 Key 十进制 ASCII 整数 Key 区分分配器;Value 原子递增
引导与表版本 固定字符串逻辑 Key 十进制 ASCII 或状态字符串 Key 定位单例状态
元数据锁、BDR 角色、Schema cache 大小、摄取调优等 固定字符串逻辑 Key,或专用哈希字段 0/1、原始字符串、十进制文本或 JSON Key 定位集群级配置;Value 按配置类型解释
Request Unit 统计 固定字符串逻辑 Key JSON 单 Key 保存一组统计状态
DDL job 历史 DDLJobHistory 哈希,字段为 8 字节大端原始 job ID Job JSON Key 按 job ID 排序定位;完成后写入历史
分布式执行框架调优 DXFScheduleTune 哈希,字段为 keyspace 字符串 调优 JSON Key 按 keyspace 区分配置
运行中 DDL、reorg、MDL 和部分历史 mysql.tidb_ddl_* 系统表的行/索引 Key 普通系统表 row Value 不使用独立 Meta 编码,服从第 5 节的标准表 KV
统计信息、权限、角色、绑定等 mysql.* 系统表的行/索引 Key 普通系统表 row Value Schema 只是系统表定义,业务对象内容按标准表 KV 保存

TableInfo Value 是 Schema 信息最密集的单元。它包含列顺序、类型、字符集/排序

规则、默认值、生成表达式和依赖、隐藏列、在线变更状态;索引列与前缀、唯一性、

主键/全局/多值/部分/列式属性;外键、检查约束和唯一约束;分区定义;视图或

序列定义;聚簇句柄模式、自增与自随机选项、TiFlash 副本、临时表、缓存表、

placement 引用、统计选项、交换分区、TTL、软删除、亲和性和 region split

策略等。数据库 ID 不重复嵌入这个 Value,而由 Meta Key 的数据库哈希部分给出。

当前生产 Meta/DDL 持久化分派没有为存储过程、存储函数或触发器定义独立目录

Key/Value。解析器中的兼容 AST 和错误码不等于已经存在持久化对象,因此本文

不把它们虚构为 Meta KV;将来若通过系统表实现,其内容也会先服从普通表

Key/Value,除非另行增加专用 Meta 分派。

列没有独立 KV 的直接收益是读取表定义时只做一次点查,并能得到一致快照;代价是

新增列、改变一个默认值或更新一个索引状态都要重写整个 TableInfo Value。删除表

定义时删除对应哈希字段;删除数据库还会清理该数据库的表定义哈希。

逻辑"数据库"是 Schema 命名空间;用户表的物理行 Key 不包含数据库名或数据库

ID,而依靠全局唯一的表/分区 ID 隔离。API V2 的 keyspace 是更外层租户式物理

命名空间,它统一前置到 Meta Key、行 Key 和索引 Key,不嵌入各自 Value。

4. Data 到 KV

4.1 主记录 Key

主记录 Key 的格式是:

text 复制代码
Key = 't'
    | EncodeInt(physical-table-or-partition-id)
    | "_r"
    | handle.Encoded()

EncodeInt 把有符号整数的符号位翻转后用 8 字节大端保存,因此负数、零、正数

按数值顺序排列。非分区表使用表 ID;分区表使用实际分区的物理 ID。

整数聚簇句柄的编码是一个 EncodeInt。复合聚簇句柄把各主键列按 comparable

datum 规则连续编码;内部会把很短的 common handle 补到至少 9 字节,以维持

句柄分类和解析约束。主键列已经存在于 Key,因此不会再写入主记录 Value。

读取一行时以完整 Key 点查;表扫以 t|tableID|_r 为前缀构造起止 Key。Key 的

有序性让同一物理表的记录连续,也让整数或复合聚簇主键范围可以直接映射为 KV

范围。

4.2 主记录 Value:row v1

row v1 仍需读取,且可由升级集群或显式配置继续写入。它是重复的

列 ID datum | 列值 datum

text 复制代码
Value = EncodeValue(colID1) | EncodeValue(value1)
      | EncodeValue(colID2) | EncodeValue(value2)
      | ...

列 ID 和 Value 都有类型 flag。常用 Value 分支是:

  • 整数 Value :有符号使用 varint flag 0x08,无符号使用 uvarint flag
    0x09,后接变长整数。
  • 字符串/字节 Value :compact-bytes flag 0x02,后接变长长度和原始字节。
  • 浮点 Value :float flag 0x05,后接 8 字节可排序浮点编码。
  • 十进制 Value :decimal flag 0x06,后接精度、标度和十进制二进制体。
  • 时间 Value :uint flag 0x04,后接打包时间。
  • 时长 Value :duration flag 0x07,后接有序有符号纳秒数。
  • JSON Value :JSON flag 0x0a,后接 JSON 类型码和二进制 JSON。
  • NULL Value :nil flag 0x00

空逻辑行的 Value 是单个 nil flag 00,而不是零长度 Value;零长度在事务

mutation 中有删除语义,不能拿来表示一条存在的空行。

row v1 的收益是自描述、兼容性强;代价是每一列和值都重复携带 flag,列 ID

也使用通用 datum 编码,空间与解码分支多于 row v2。

4.3 主记录 Value:row v2

新建集群默认采用 row v2,DDL 重组默认也使用 v2。其总体格式为:

text 复制代码
Value =
  0x80
  | flags
  | nonNullCount:u16le
  | nullCount:u16le
  | sorted non-null column IDs
  | sorted null column IDs
  | cumulative end offsets of non-null payloads
  | concatenated non-null payloads
  | optional checksum header and crc32

flags 的 bit 0 表示 large 格式,bit 1 表示带行校验和。小格式列 ID 是 1 字节、

偏移是 2 字节小端;只要有列 ID 大于 255 或数据区超过 65535 字节,就切换为

4 字节小端列 ID 和 4 字节小端偏移。NULL 列只进入 NULL ID 数组,没有数据体。

非 NULL 列 ID、NULL 列 ID 都升序保存,偏移数组给出每段数据的结束位置。因此

解码器可以二分列 ID,只切取需要的列 Value,而不必逐列解析前缀。row v2 外壳

自身没有通用压缩层;字符串、BLOB、JSON、向量等都内联在同一个主记录 Value。

可选行校验和只用于 row v2,并受会话和内部事务条件控制,默认关闭。当前版本的

校验和覆盖已经形成的 row Value 头部/数据以及行句柄,保存 32 位小端 CRC32。

写入时会跳过:

  • 已经编码进 Key 的句柄列;
  • 虚拟生成列;
  • 可以由 Schema 默认值无歧义恢复、且当前等于 NULL 的列。

存储生成列仍写 Value。读取旧行遇到缺失列时,由当前 Schema 补默认值;如果

显式 NULL 与"列缺失后补默认值"语义不同,编码器会把该列列入 NULL ID 数组。

4.4 当前 SQL 类型的字节映射

下表的"行 Value"指主记录 Value 内某一列的数据体;row v1 还会在数据体外加

类型 flag,row v2 依靠 Schema 类型与偏移解释数据体。"索引 Key"指该类型

合法作为普通行存索引列时的 comparable 编码。

SQL 类型 行 Value 索引 Key 说明
BOOLBOOLEAN TINYINT(1) 整数保存 按有符号整数保存 只是语法别名,没有独立类型码或 Value 格式
TINYINTSMALLINTMEDIUMINTINTBIGINT v1 为 varint;v2 按数值范围取 1/2/4/8 字节小端补码 flag 0x03 + 8 字节大端符号位翻转 声明宽度不决定 row v2 固定宽度
上述整数的 UNSIGNED 形式 v1 为 uvarint;v2 取 1/2/4/8 字节小端无符号值 flag 0x04 + 8 字节大端无符号值 最大值仍保持有序
YEAR 与整数相同 与整数相同 没有独立行数据体
FLOATDOUBLE v1/v2 都使用 8 字节浮点有序变换 flag 0x05 + 同一 8 字节变换 正数翻转符号位,负数逐位取反,使字节序等于数值序
DECIMAL(p,s) 精度 1 字节、标度 1 字节、MySQL 二进制定点数 flag 0x06 + 可排序十进制编码 负数数据位取反并规范最高位;保持精确十进制语义
CHARBINARY v1 为 compact bytes;v2 为原始字节 flag 0x01 + 8 字节分组转义串 CHAR 的补空格与排序规则先由类型语义处理
VARCHARVARBINARY v1 为 compact bytes;v2 为原始字节 flag 0x01 + 8 字节分组转义串 新排序规则下,字符类型的 Key 保存 sort key,不一定是原文
TINYTEXTTEXTMEDIUMTEXTLONGTEXT 与字符串相同,内联主记录 Value 合法前缀索引保存截断后的 sort key/字节 不能把完整大文本误解为独立 overflow KV
TINYBLOBBLOBMEDIUMBLOBLONGBLOB 原始字节内联主记录 Value 合法前缀索引保存截断字节 当前行 codec 没有 BLOB chunk Key
DATEDATETIME 打包为无符号时间整数;v2 使用最短 1/2/4/8 字节小端 flag 0x04 + 8 字节大端打包值 Key 保持时间顺序
TIMESTAMP 写前按时区转换到 UTC,再按打包时间保存 按规范化后的打包时间形成 Key 解码展示时再按会话时区转换
TIME 纳秒数;v1 为有序时长编码,v2 为最短 1/2/4/8 字节小端补码 flag 0x07 + 有序有符号整数 可表达负时长
ENUMSET 数值表示;v2 为最短无符号小端 按无符号数形成有序 Key Schema Value 保存元素文本和排序规则
BIT 无符号数;v2 为最短无符号小端 按无符号数形成有序 Key 位宽在 Schema 中
JSON JSON 类型码 + MySQL binary JSON 普通 JSON 列不能直接做普通 B-tree Key 多值索引只编码从 JSON 数组展开出的标量
VECTOR(FLOAT32) dimension:u32le + 连续 float32le 不写普通 TiKV 行存索引 Key 向量索引是列式索引;维度上限 16383,拒绝 NaN/Inf
SQL NULL v1 用 0x00;v2 放入 NULL 列 ID 数组 comparable nil flag 0x00 唯一索引含 NULL 时不视为 distinct,句柄进入 Key

binary JSON Value 的第一字节是类型码。对象和数组含 32 位小端元素数与总长度,

随后是键/值入口和偏移指向的数据区;数字采用小端定宽表示,字符串用变长长度,

并支持字面量、对象、数组和 opaque 数据。JSON 中的 list/map 由这一 Value

内部结构表达,不生成独立行 Key。

语法别名不会增加 Value 格式:

  • MIDDLEINTINT1/2/3/4/8INTEGER 分别归一到相应整数类型;
    SERIALBIGINT UNSIGNED NOT NULL AUTO_INCREMENT UNIQUE 的列定义简写。
  • NUMERICFIXED 归一到 DECIMALFLOAT4 归一到 FLOAT
    FLOAT8DOUBLE PRECISION 归一到 DOUBLEREAL 根据 SQL mode
    归一到 FLOATDOUBLE
  • CHARACTERNCHARNATIONAL CHAR 等归一到 CHAR
    CHARACTER VARYINGNVARCHARNATIONAL VARCHAR 等归一到
    VARCHAR
  • LONGLONG VARCHAR 归一到 MEDIUMTEXTLONG VARBINARY 归一到
    MEDIUMBLOBSQL_TSI_YEAR 归一到 YEAR
  • MariaDB 解析模式下的 UUID 没有原生 TiDB 类型,而是归一为 CHAR(36)
    SIGNEDUNSIGNEDZEROFILL 只改变整数属性,其中 ZEROFILL
    隐含 UNSIGNED

当前语法没有独立的 SQL array、map、struct、tuple、domain、interval 或带时区

时间列格式。多值索引里的"数组"是把 JSON 数组 cast 为元素数组的索引标记,

不是新的主记录 Value 类型。类型系统中的 TypeUnspecifiedTypeNull

TypeNewDateTypeGeometry 是协议、表达式或兼容常量;TypeVarString

也是协议/兼容字符串类型,当前普通 VARCHAR DDL 使用 TypeVarchar。它们

都不要求虚构新的持久化 KV 格式。

4.5 排序规则与可恢复字符串

字符串进入普通索引 Key 时,新排序规则模式保存 collation sort key,以保证

KV 字典序与 SQL 比较顺序一致。这会丢失大小写、重音或尾空格等原文差异。需要

从索引覆盖扫描恢复原值时,索引 Value 可附带一段 row v2 格式的"恢复数据";

二进制字符串或无需恢复的值不写这段数据。

因此:

  • 索引 Key负责比较和范围边界,可能只含规范化 sort key。
  • 索引 Value在需要时补充原始字符串或尾空格信息。
  • Key/Value 关系同时满足"按 SQL 语义排序"和"覆盖索引能恢复展示值"。

5. 索引到 KV

5.1 通用行存索引 Key

text 复制代码
Key = 't'
    | EncodeInt(index-table-or-partition-id)
    | "_i"
    | EncodeInt(index-id)
    | EncodeKey(indexed-value-1, ...)
    | optional row locator

索引列用 comparable datum 编码。常用 flag 是 NULL 0x00、bytes 0x01

signed int 0x03、unsigned int 0x04、float 0x05、decimal 0x06

duration 0x07;编码确保合法索引类型按 SQL 比较语义排列。

distinct = unique && 所有索引列都非 NULL

  • distinct 唯一入口不把行定位符放进 Key,而把它放进 Value,以便相同索引值
    发生 Key 冲突。
  • 非唯一入口和含 NULL 的唯一入口把行定位符附在 Key 末尾,以允许多个相同值。
  • 整数定位符在 Key 中是 flag 0x03EncodeInt(handle);common handle
    直接附其复合有序编码。

5.2 索引 Value

兼容的最短形式是:

  • 唯一、整数句柄、局部索引 Value:8 字节原始大端无符号句柄。
  • 非唯一索引 Value :ASCII 0,即 0x30,真实句柄已在 Key。

扩展格式以尾部长度描述可选段,并可组合:

text 复制代码
common handle: 0x7f | handleLength:u16be | handle bytes
global index:  0x7e | EncodeInt(partitionID)
restored data: row v2 restore segment
compatibility: padding、int handle 或 uncommitted 标记

扩展 Value 通常补到至少 10 字节以与旧格式可靠区分。旧 common-handle v1

Value 仍可读取。全局索引 v1 的某些非 distinct、非聚簇形式还会在 Key 的内部

句柄前编码 0x7e|partitionID,并在 Value 中保留分区 ID;这是兼容格式,不应

误认为所有当前索引都采用相同重复布局。

5.3 索引种类矩阵

索引种类 Key Value Key/Value 关系、收益与代价
整数聚簇主键 主记录 Key 的句柄部分 主记录 Value 不重复主键 一次点查得到行;改变主键等价于迁移主记录 Key
复合聚簇主键 主记录 Key 的 common handle 主记录 Value 不重复主键 天然支持主键前缀/范围;宽主键放大所有二级索引定位符
非聚簇主键 标准唯一二级索引 Key Value 保存行句柄 先查主键索引再查主记录
唯一索引、无 NULL 索引值到 Key 末尾 Value 保存行定位符 Key 冲突强制唯一;点查一步得到句柄
唯一索引、含 NULL 索引值后附行定位符 通常为占位或扩展 Value 遵循多个 NULL 可共存语义
非唯一索引 索引值后附行定位符 0x30 或扩展 Value 相同值按句柄有序;范围结果可直接得到定位符
局部索引 Key 使用物理分区 ID Value 通常不需分区 ID 每分区连续且更短;跨分区查询需扫描多个前缀
全局索引 Key 使用逻辑表 ID Value 保存物理分区 ID 和行定位符 跨分区唯一/点查只访问一个索引空间;Value 更宽,分区变更更复杂
前缀索引 Key 只保存截断值 Value 按需保存恢复数据 控制 Key 尺寸;碰撞增多,可能需回表过滤
表达式索引 表达式结果先成为隐藏生成列,再写标准索引 Key 标准索引 Value 复用普通索引格式;Schema Value 承担表达式与隐藏列定义
部分索引 谓词为真时才写标准索引 Key 标准索引 Value 降低空间和写放大;优化器必须证明谓词适用
多值索引 将一个 cast JSON 数组展开为多个标准索引 Key 每个入口使用标准索引 Value 一个主记录对应 0...N 个索引 KV;同一数组内按 binary JSON 哈希去重,空数组写 0 个,NULL 写 1 个
临时索引 索引 ID 与 0x7fff000000000000 做位组合,Key 仍用索引前缀 Value 是 normal/delete 元素序列及 d/b/m 阶段标记 在线 DDL 暂存操作;完成切换后不是最终查询布局
BTREE 标准行存索引 Key 标准索引 Value 实际依赖有序 KV
HASH,以及兼容元数据中的 RTREE 若进入行存索引实现,仍使用标准有序索引 Key 标准索引 Value HASH 不产生哈希桶;当前 SPATIAL/RTREE DDL 会被拒绝,兼容枚举也没有空间树 KV
hypothetical 无持久 Key 无持久 Value 仅优化器会话模拟,不影响存储
VECTORINVERTEDFULLTEXT 列式索引 普通行 DML 不写 TiKV 索引 Key 普通行 DML 不写 TiKV 索引 Value 定义嵌入 TableInfo;物理索引数据由 TiFlash 持有;HNSW 只存在于 AST,预处理后归一为 VECTOR
普通索引 DESC 当前与升序使用同一行存 Key 同一标准 Value 语法可接受但普通行存编码未反转字节,不能推断为独立降序布局

前缀索引对二进制或 ASCII 值按字节截断,对其他字符串按 Unicode 字符截断,

然后再执行排序规则编码。多值索引最多展开一个数组表达式;每个元素与其余普通

索引列组合。表达式索引之所以没有新 KV 格式,是因为表达式结果在 Schema 中

表现为隐藏生成列。

索引点查使用完整 Key;非唯一查找、前缀条件和范围条件以索引前缀构造起止 Key,

上界通常由前缀的字典序后继得到。索引 Key 把表/分区 ID、索引 ID 和列值依次

放在高到低层级,正好让同一索引及相邻列值物理聚集。

6. 写入、更新、删除与 MVCC

插入一行时:

text 复制代码
1. 由物理表/分区 ID 与句柄构造主记录 Key。
2. 把非句柄列编码为 row v1 或 row v2 Value。
3. 向事务内存缓冲写 Set(record Key, row Value)。
4. 对每个适用的行存索引生成一个或多个 index Key/Value 并 Set。
5. 提交时把非空 Value 变为 Put mutation。

更新一行时,主记录通常在同一 Key 上写入新 Value;索引列、句柄或分区归属变化

时,旧索引 Key/旧主记录 Key 被 Delete,新 Key 被 Put。不变的 Key 在新的

commit timestamp 下形成新版本,而不是原地改写已提交版本。

删除一行时,TiDB 对主记录 Key 和各索引 Key 调用 Delete。事务缓冲中的删除是

空 Value mutation,预写阶段映射成 Op_Del,不是"写一个 row Value 为

NULL"。因此必须区分:

  • SQL NULL Value:行仍存在;row v1 用 nil flag,row v2 用 NULL 列 ID。
  • 空逻辑行 Value :行仍存在;row v1 至少写单字节 00,row v2 有完整头部。
  • Delete mutation:逻辑 Key 在新 MVCC 版本后不可见;不携带用户 Value。

读取在一个快照时间戳上执行。点查先查完整 Key,范围扫描按前缀迭代;MVCC 层

负责选择不晚于快照的最新可见 Put,并让 Delete 截断后续快照的可见性。这里能

确认的是 TiDB/client 的 mutation 与时间戳协议;TiKV server 在 RocksDB 各 CF

中如何编码版本后缀、短 Value、锁和回滚记录,不在当前源码可验证范围内。

API V2 再把每个事务 Key 改为:

text 复制代码
Physical Key = 0x78 | keyspaceID:u24be | TiDB logical Key
Physical Value = TiDB logical Value

这使不同 keyspace 的整个 Meta、记录和索引范围物理隔离,并保持每个 keyspace

内部原有排序。API V1 不加此前缀。

7. 组织原因、收益与代价

设计 主要收益 明确代价
全局表/分区 ID 进入 Key 数据库改名、表改名不搬数据;范围天然隔离 ID 必须稳定且全局管理;从裸 Key 看不到 SQL 名称
表记录与索引采用不同 ASCII 分隔前缀 点查、表扫、索引扫边界简单 二级索引和主记录是多个 KV,写事务有写放大
聚簇句柄进入主记录 Key 主键点查和范围扫直接映射 KV 宽复合主键放大记录 Key 与二级索引
row v2 的列 ID/偏移数组 随机取列快、固定开销低、NULL 无数据体 解码依赖 Schema;大列 ID/大行触发 4 字节目录
Schema 子对象嵌入 TableInfo JSON 一次点查获得自洽表定义,演进方便 小变更重写大 Value;JSON 比专用二进制更占空间
comparable 索引 Key SQL 排序、范围扫描、唯一冲突复用 KV 顺序 排序规则 sort key 可能丢原文,需要 Value 恢复段
全局索引的分区 ID 放 Value 跨分区点查和唯一约束只需一个索引空间 Value 变宽,分区移动和兼容格式更复杂
大文本、JSON、向量内联主记录 Value 没有额外 chunk 查找和孤儿对象管理 单行变宽,事务 entry 大小和网络/写放大成为约束
MVCC 版本位于底层事务层 SQL codec 无需为每个对象自行设计版本链 裸逻辑 Key/Value 不能单独解释历史与可见性

8. KV 设计启示

  1. 先固定 Key 的排序职责,再选择 Value 的紧凑程度。 身份、范围边界和冲突
    检测应放 Key;不参与排序且可能演进的内容放 Value。
  2. 区分逻辑命名空间和物理租户命名空间。 TiDB 用全局对象 ID 稳定逻辑布局,
    再用统一 keyspace 前缀隔离租户,两层不互相污染。
  3. 为唯一和非唯一入口选择不同的行定位位置。 唯一索引让相同值竞争同一
    Key;非唯一索引把定位符放 Key,既消除冲突又稳定排序。
  4. 显式区分 NULL、缺失、空 Value 和删除。 四者混用会破坏默认值回填、
    唯一约束和 MVCC 墓碑语义。
  5. 紧凑行格式仍要保留可演进目录。 列 ID 与偏移数组使新增列、缺失列默认值
    和投影读取可以共存,而无需让每列都携带完整类型标签。
  6. 比较编码与恢复编码可以分离。 Key 保存 sort key,Value 只在需要时保存
    原始文本,能同时满足排序正确性与覆盖索引。
  7. 兼容格式必须可判别。 索引 Value 的 magic、长度与最小尺寸让新旧格式
    可共存;设计自研格式时应预留明确的版本或分支判据。
  8. 只把已验证边界写成字节规范。 客户端 mutation 语义不能替代服务端 CF
    字节证据;依赖未锁定时,应把未知边界列为约束而不是补全想象。
相关推荐
方方洛1 小时前
AI 教程系列-TUI 应用开发教程03-TUI 框架与技术选型
前端·javascript·ui kit
第二个人1 小时前
Python Web开发:从Flask到FastAPI,我经历了什么
前端·python·flask
一路向北North2 小时前
Spring AI(6) :对话机器人-会话历史
java·人工智能·spring
其实防守也摸鱼2 小时前
前端应用的离线暂停更新策略:构建稳定可靠的渐进式部署方案
服务器·前端·数据库·学习·ai·命令行·linux系统
太平洋月光2 小时前
Antv G2中自定义技巧📊
前端·数据可视化
shmily麻瓜小菜鸡2 小时前
前端“伪防盗链”方案
前端·javascript·vue.js·bootstrap·echarts
mayaairi2 小时前
JS数组完全指南(含十大操作详解)
开发语言·前端·javascript
RuoyiOffice2 小时前
超级个体接私活必看:后端+前端+移动端三端一体企业管理系统怎么选(2026)
java·spring boot·vue·uniapp·全栈·企业管理·接私活
IT_陈寒2 小时前
Python的线程池把我CPU跑满了,原来少传了个参数
前端·人工智能·后端