TiDB HTAP 架构深度拆解:从 Raft 共识到列存 MPP,手写一个混合负载查询引擎
一、引言:为什么 HTAP 是数据库的终局之战?
2026 年的企业数据架构正面临一个根本性矛盾:OLTP(联机事务处理)和 OLAP(联机分析处理)的割裂。
传统方案是典型的 Lambda 架构——业务数据写入 MySQL/PostgreSQL,通过 CDC(Change Data Capture)实时同步到 ClickHouse/Doris 做分析。这套架构在 2020 年代初期跑得不错,但到了 2026 年,三个问题让它越来越难以为继:
延迟鸿沟:CDC 链路的端到端延迟通常在秒级到分钟级。当运营同学在 Grafana 看板上看到"当前在线用户数"时,这个数字可能是 30 秒前的。对于实时风控、动态定价等场景,这是不可接受的。
一致性幻觉:OLTP 库和 OLAP 库之间的一致性是"最终一致"。在业务高峰期,你可能在分析库里看到一笔刚提交的订单,但对应的库存扣减还没同步过来——分析结果是错的。
运维复杂度爆炸:维护两套存储引擎、两条数据链路、两套监控体系,运维成本是单系统的 3-5 倍。团队不仅要懂 MySQL,还要懂 ClickHouse;不仅要调优事务性能,还要调优分析查询。
HTAP(Hybrid Transactional/Analytical Processing) 的核心思想是:用一套系统同时支撑 OLTP 和 OLAP,消除数据孤岛,实现真正的实时分析。TiDB 是这个赛道最成熟的开源方案——它用 Raft 共识协议实现分布式事务,用列存副本实现 MPP 分析,在同一套 SQL 接口下同时服务两种负载。
本文将从第一性原理出发,逐层拆解 TiDB 的 HTAP 架构:从存储引擎的字节布局到查询优化器的代价模型,从 Raft 日志的复制流水线到 TiFlash 的列式扫描,附完整代码示例与性能调优方法论。
二、TiDB 整体架构:四层分离的设计哲学
TiDB 的架构可以用一句话概括:计算与存储分离,行存与列存并存。
┌─────────────────────────────────────────────────────┐
│ SQL Layer │
│ ┌─────────┐ ┌──────────┐ ┌────────────────────┐ │
│ │ MySQL │ │ Parser │ │ Query Optimizer │ │
│ │ Protocol│ │ (ANTLR4) │ │ (CBO + Heuristic) │ │
│ └─────────┘ └──────────┘ └────────────────────┘ │
├─────────────────────────────────────────────────────┤
│ Transaction Layer │
│ ┌──────────────┐ ┌────────────┐ ┌─────────────┐ │
│ │ 2PC Protocol │ │ TSO │ │ Metadata │ │
│ │ (Coordinator)│ │ (Timestamp │ │ Manager │ │
│ │ │ │ Oracle) │ │ (PD) │ │
│ └──────────────┘ └────────────┘ └─────────────┘ │
├─────────────────────────────────────────────────────┤
│ Storage Layer │
│ ┌──────────────────┐ ┌──────────────────────┐ │
│ │ TiKV │ │ TiFlash │ │
│ │ (Row Store) │ │ (Column Store) │ │
│ │ Raft Group ×N │ │ Raft Learner ×N │ │
│ │ RocksDB Engine │ │ Columnar Engine │ │
│ └──────────────────┘ └──────────────────────┘ │
├─────────────────────────────────────────────────────┤
│ Placement Driver (PD) │
│ ┌──────────────┐ ┌────────────┐ ┌─────────────┐ │
│ │ TSO │ │ Cluster │ │ Scheduling │ │
│ │ (全局时钟) │ │ Metadata │ │ (调度决策) │ │
│ └──────────────┘ └────────────┘ └─────────────┘ │
└─────────────────────────────────────────────────────┘
2.1 四大组件的职责
PD(Placement Driver):集群的大脑。它维护全局元数据(哪些 Region 在哪些 TiKV 节点上),提供全局单调递增的时间戳(TSO),并负责调度决策(Region 分裂、合并、迁移)。PD 是一个 etcd 集群,本身就具备高可用性。
TiDB Server:无状态的 SQL 层。它解析 SQL、生成执行计划、调用 TiKV/TiFlash 执行查询。多个 TiDB Server 可以水平扩展,前面挂 Load Balancer 即可。它不存储任何数据——所有状态都在 TiKV 和 TiFlash 里。
TiKV:分布式行存引擎。数据按 Range 分成 Region(默认 96MB),每个 Region 通过 Raft 协议在多个 TiKV 节点间复制。TiKV 使用 RocksDB 作为底层存储引擎,提供完整的 ACID 事务支持。
TiFlash:列存分析引擎。它是 TiKV 数据的异步副本(Raft Learner),将行存数据转为列存格式,专为分析查询优化。TiFlash 通过 Raft Learner 协议从 TiKV 异步同步数据,延迟通常在秒级。
2.2 HTAP 的关键设计:行存 + 列存并存
TiDB 的 HTAP 能力核心在于双存储引擎并存:
-- 创建表时指定 TiFlash 副本
CREATE TABLE orders (
id BIGINT PRIMARY KEY,
user_id BIGINT,
product_id BIGINT,
amount DECIMAL(10,2),
status VARCHAR(20),
created_at TIMESTAMP
);
-- 添加 TiFlash 列存副本(1份)
ALTER TABLE orders SET TIFLASH REPLICA 1;
-- 查看副本状态
SELECT * FROM information_schema.tiflash_replica
WHERE TABLE_SCHEMA = 'your_db' AND TABLE_NAME = 'orders';
当一条 INSERT 语句执行时:
- TiDB Server 将写请求发送到 TiKV
- TiKV 通过 Raft 将数据复制到多个 TiKV 副本
- TiFlash 作为 Raft Learner,异步从 TiKV 同步数据
- TiFlash 内部将行存数据转为列存格式并压缩
这个异步同步的设计是精妙的:OLTP 写入不被 OLAP 副本拖慢,而 OLAP 查询可以读到延迟几秒的数据——对于大多数分析场景,这是完全可接受的。
三、TiKV 深度拆解:从 Raft 日志到 MVCC 存储
3.1 Raft 共识:分布式事务的基石
TiKV 使用 Multi-Raft 架构:整个集群的数据被分成数万个 Region,每个 Region 是一个独立的 Raft Group。这种设计避免了单一大 Raft Group 的性能瓶颈。
TiKV Node 1 TiKV Node 2 TiKV Node 3
┌──────────┐ ┌──────────┐ ┌──────────┐
│ Region A │ Leader │ Region A │ Follower│ Region A │ Follower
│ Region B │ Follower│ Region B │ Leader │ Region B │ Follower
│ Region C │ Follower│ Region C │ Follower│ Region C │ Leader
└──────────┘ └──────────┘ └──────────┘
Raft 的核心流程:
Client → Leader → AppendEntries → Followers → Commit → Apply → Response
关键优化:Raft 流水线(Pipeline)
TiKV 对标准 Raft 做了重要的性能优化——异步 Apply。标准 Raft 要求"复制完成后再应用到状态机",TiKV 则将复制和应用解耦:
// TiKV 的 Raft 流水线(简化示意)
async fn on_raft_ready(mut ready: Ready) {
// 1. 立即持久化日志到 WAL
append_wal(&ready.entries)?;
// 2. 异步复制到 Followers(不等待)
let replication = replicate_to_followers(&ready.entries);
// 3. 一旦多数派确认,立即提交
let committed = wait_for_majority(replication).await?;
// 4. 异步 Apply 到 RocksDB(不阻塞下一个 Ready)
tokio::spawn(async move {
apply_to_state_machine(committed).await;
});
// 5. 立即返回响应给 Client
respond_to_client(Ok(()));
}
这个流水线设计让 TiKV 的写入吞吐量提升了 2-3 倍,延迟降低了 40-60%。
3.2 MVCC:多版本并发控制
TiKV 的 MVCC 实现基于 Percolator 模型(Google 2010 年论文),这是分布式事务领域最经典的设计之一。
数据模型:
Key = {table_id}_{row_id}_{start_ts}
Value = {commit_ts}{value_bytes}
每个 Key 都带有一个时间戳(start_ts),表示这个版本的"出生时间"。当事务提交时,会写入一个锁(Lock)和元数据(Write)记录。
写入流程(2PC):
┌─────────────────────────────────────────────────┐
│ Phase 1: Prewrite │
│ │
│ 1. 选一个 Key 作为 Primary │
│ 2. 对所有 Key 加锁 + 写入数据 │
│ 3. 如果任何一个 Key 冲突,回滚 │
├─────────────────────────────────────────────────┤
│ Phase 2: Commit │
│ │
│ 1. 提交 Primary Key(写入 Write 记录 + 删锁) │
│ 2. 异步提交 Secondary Keys(不阻塞) │
└─────────────────────────────────────────────────┘
读取流程(MVCC Read):
// MVCC 读取逻辑(简化)
fn mvcc_read(key: &[u8], read_ts: u64) -> Option<Value> {
// 1. 找到 <= read_ts 的最新 Write 记录
let write = find_latest_write(key, read_ts)?;
match write.write_type {
WriteType::Put => {
// 2. 如果是 Put,读取对应版本的数据
let data_key = encode_key(key, write.start_ts);
get_from_rocksdb(&data_key)
}
WriteType::Delete => {
// 3. 如果是 Delete,返回 None(已删除)
None
}
WriteType::Lock => {
// 4. 如果是 Lock,需要判断锁的状态
handle_lock_conflict(key, read_ts)
}
}
}
关键点:MVCC 的读取是不加锁的——它通过时间戳快照实现"一致性读",不需要获取任何互斥锁。这是 TiKV 能同时支撑高并发 OLTP 和 OLAP 的核心原因。
3.3 RocksDB 存储引擎
TiKV 使用 RocksDB 作为底层存储引擎,但做了大量定制:
┌─────────────────────────────────────┐
│ TiKV Application │
├─────────────────────────────────────┤
│ MVCC Layer (Key Encoding) │
├─────────────────────────────────────┤
│ RocksDB Engine │
│ ┌─────────┐ ┌─────────────────┐ │
│ │ MemTable │ │ SST Files │ │
│ │ (Write │ │ (L0-L6) │ │
│ │ Buffer)│ │ Compaction │ │
│ └─────────┘ └─────────────────┘ │
├─────────────────────────────────────┤
│ 文件系统 (ext4/XFS) │
└─────────────────────────────────────┘
RocksDB 的 LSM-Tree 结构:
写入流程:
- 写入 WAL(Write-Ahead Log)——持久化保证
- 写入 MemTable(内存中的跳表)——快速写入
- MemTable 满了后 Flush 到 SST 文件(L0)
- 后台 Compaction 将 L0 合并到 L1-L6
TiKV 对 RocksDB 的关键调优:
# TiKV 配置文件中的 RocksDB 调优参数
[rocksdb]
# 最大 Write Buffer 大小(MemTable)
max-write-buffer-size = "128MB"
# Write Buffer 数量
max-write-buffer-number = 5
[rocksdb.defaultcf]
# 压缩策略:L0-L1 不压缩,L2+ 用 LZ4,底部用 ZSTD
compression-per-level = ["no", "no", "lz4", "lz4", "lz4", "zstd", "zstd"]
# Block 大小(影响压缩比和读取性能)
block-size = "64KB"
# Bloom Filter(加速点查)
bloom-filter-bits-per-key = 10
四、TiFlash 深度拆解:列存引擎的工程哲学
4.1 为什么需要列存?
行存和列存的核心区别在于数据布局:
行存 (TiKV/RocksDB):
┌────┬────────┬─────────┬────────┐
│ id │ user_id│ amount │ status │ ← 一行数据连续存储
├────┼────────┼─────────┼────────┤
│ 1 │ 1001 │ 99.00 │ paid │
│ 2 │ 1002 │ 199.00 │ paid │
│ 3 │ 1001 │ 299.00 │ pending│
└────┴────────┴─────────┴────────┘
列存 (TiFlash):
┌──────────────────────────────┐
│ id: [1, 2, 3] │ ← 同一列数据连续存储
│ user_id: [1001, 1002, 1001] │
│ amount: [99, 199, 299] │
│ status: [paid, paid, pending]│
└──────────────────────────────┘
对于分析查询 SELECT SUM(amount) FROM orders WHERE status = 'paid':
- 行存:需要扫描整张表(全表扫描),读取每一行的所有列
- 列存:只读取
amount和status两列,跳过其他列,压缩比更高
在实际测试中,列存对于分析查询的性能通常是行存的 10-100 倍。
4.2 TiFlash 的列存格式
TiFlash 使用自己的列存格式(非 Apache Parquet),针对 OLAP 场景做了深度优化:
┌─────────────────────────────────────────────────┐
│ TiFlash Columnar Segment │
├─────────────────────────────────────────────────┤
│ Header: Segment 元数据 │
├─────────────────────────────────────────────────┤
│ Column 1 (id): │
│ ┌─────────┬──────────┬──────────────────────┐ │
│ │ Encoding │ Codec │ Data Pages │ │
│ │ (Delta) │ (LZ4/ZS) │ (Block × N) │ │
│ └─────────┴──────────┴──────────────────────┘ │
├─────────────────────────────────────────────────┤
│ Column 2 (amount): │
│ ┌─────────┬──────────┬──────────────────────┐ │
│ │ Encoding │ Codec │ Data Pages │ │
│ │ (Float) │ (ZSTD) │ (Block × N) │ │
│ └─────────┴──────────┴──────────────────────┘ │
├─────────────────────────────────────────────────┤
│ Column N: ... │
└─────────────────────────────────────────────────┘
关键设计决策:
- Delta 编码:对于递增的整数列(如自增 ID、时间戳),Delta 编码只存储差值,压缩比极高
- 字典编码:对于低基数列(如 status: paid/pending/refunded),使用字典编码将字符串映射为整数
- Run-Length 编码:对于有大量连续重复值的列,存储"值 × 重复次数"
- 向量化执行:数据按 Block(通常 8192 行)组织,CPU 一次处理一个 Block,充分利用 SIMD 指令
4.3 Raft Learner:异步复制的精妙设计
TiFlash 不是 TiKV 的对等副本,而是 Raft Learner——它只接收日志,不参与投票。这意味着:
- TiKV 的写入不受 TiFlash 影响(不需要等待 TiFlash 确认)
- TiFlash 可以独立做 Compaction,不影响 TiKV 的 LSM-Tree
- TiFlash 可以选择性地只同步某些列(节省带宽和存储)
TiKV (Leader Group) TiFlash (Learner)
┌──────────────┐ ┌──────────────┐
│ Raft Log │ ──异步──→ │ Raft Log │
│ Append │ │ Append │
├──────────────┤ ├──────────────┤
│ RocksDB │ │ Columnar │
│ (Row Store) │ │ Engine │
└──────────────┘ └──────────────┘
4.4 MPP 执行引擎
当 TiFlash 收到分析查询时,它使用 MPP(Massively Parallel Processing) 模式执行:
-- MPP 模式示例:跨 TiFlash 节点并行执行
EXPLAIN ANALYZE
SELECT
DATE(created_at) AS day,
status,
COUNT(*) AS order_count,
SUM(amount) AS total_amount
FROM orders
WHERE created_at >= '2026-07-01'
GROUP BY DATE(created_at), status
ORDER BY day DESC, total_amount DESC;
MPP 的执行流程:
TiDB Server
│
├──→ TiFlash Node 1: 扫描本地数据 → 聚合 → 部分结果
├──→ TiFlash Node 2: 扫描本地数据 → 聚合 → 部分结果
└──→ TiFlash Node 3: 扫描本地数据 → 聚合 → 部分结果
│
▼
Exchange (Shuffle)
│
▼
Final Aggregation → 返回结果
五、查询优化器:TiDB 的大脑
TiDB 的查询优化器是整个系统中最复杂的组件之一,它基于 CBO(Cost-Based Optimizer) 框架,结合启发式规则。
5.1 查询优化流程
SQL String
│
▼
┌──────────┐
│ Parser │ ← ANTLR4 语法分析
└──────────┘
│
▼
┌──────────┐
│ Logical │ ← 逻辑优化(谓词下推、列裁剪、JOIN 重排序)
│ Optimizer│
└──────────┘
│
▼
┌──────────┐
│ Physical │ ← 物理优化(选择存储引擎、Join 算法、并行度)
│ Optimizer│
└──────────┘
│
▼
┌──────────┐
│ Executor │ ← 向量化执行引擎
└──────────┘
5.2 关键优化规则
谓词下推(Predicate Pushdown):
-- 优化前
SELECT * FROM (
SELECT * FROM orders WHERE amount > 100
) o JOIN users u ON o.user_id = u.id
WHERE u.status = 'active';
-- 优化后(谓词下推)
SELECT * FROM orders o
JOIN users u ON o.user_id = u.id
WHERE o.amount > 100 AND u.status = 'active';
谓词下推减少了中间结果集的大小,是查询优化中最基础也最有效的规则。
Join 重排序(Join Reorder):
-- 优化器根据统计信息选择最优 Join 顺序
-- 如果 orders 有 1000 万行,users 有 100 万行
-- 优化器会选择:users → orders(小表驱动大表)
SELECT * FROM orders o
JOIN users u ON o.user_id = u.id
JOIN products p ON o.product_id = p.id;
5.3 HTAP 查询路由:TiKV vs TiFlash
TiDB 优化器会根据查询特征自动选择使用 TiKV(行存)还是 TiFlash(列存):
-- 场景1:点查 → 使用 TiKV(行存)
SELECT * FROM orders WHERE id = 12345;
-- 场景2:全表扫描 + 聚合 → 使用 TiFlash(列存)
SELECT status, COUNT(*), SUM(amount)
FROM orders
GROUP BY status;
-- 场景3:强制使用 TiFlash
SELECT /*+ READ_FROM_STORAGE(TIFLASH[orders]) */
status, COUNT(*)
FROM orders
GROUP BY status;
-- 场景4:强制使用 TiKV
SELECT /*+ READ_FROM_STORAGE(TIKV[orders]) */
*
FROM orders
WHERE id = 12345;
路由决策的关键因素:
| 因素 | 倾向 TiKV | 倾向 TiFlash |
|---|---|---|
| 查询类型 | 点查、范围查 | 全表扫描、聚合 |
| 结果集大小 | 小结果集 | 大结果集 |
| 是否有聚合 | 无 | 有 GROUP BY/窗口函数 |
| 是否需要事务 | 需要 | 不需要 |
| 数据新鲜度 | 需要最新 | 可接受秒级延迟 |
六、代码实战:从零搭建 TiDB HTAP 环境
6.1 Docker Compose 快速部署
# docker-compose.yml
version: '3.8'
services:
# Placement Driver
pd0:
image: pingcap/pd:v8.1.0
container_name: pd0
ports:
- "2379:2379"
- "2380:2380"
command:
- --name=pd0
- --client-urls=http://0.0.0.0:2379
- --peer-urls=http://0.0.0.0:2380
- --advertise-client-urls=http://pd0:2379
- --advertise-peer-urls=http://pd0:2380
- --initial-cluster=pd0=http://pd0:2380
# TiKV × 3
tikv0:
image: pingcap/tikv:v8.1.0
container_name: tikv0
ports:
- "20160:20160"
command:
- --addr=0.0.0.0:20160
- --advertise-addr=tikv0:20160
- --pd=pd0:2379
depends_on:
- pd0
tikv1:
image: pingcap/tikv:v8.1.0
container_name: tikv1
ports:
- "20161:20161"
command:
- --addr=0.0.0.0:20161
- --advertise-addr=tikv1:20161
- --pd=pd0:2379
depends_on:
- pd0
tikv2:
image: pingcap/tikv:v8.1.0
container_name: tikv2
ports:
- "20162:20162"
command:
- --addr=0.0.0.0:20162
- --advertise-addr=tikv2:20162
- --pd=pd0:2379
depends_on:
- pd0
# TiFlash × 1
tiflash0:
image: pingcap/tiflash:v8.1.0
container_name: tiflash0
ports:
- "9000:9000"
- "8123:8123"
environment:
TIFLASH_DEPLOY_DIR: /data/tiflash
volumes:
- ./tiflash.toml:/data/tiflash/config/tiflash.toml
depends_on:
- pd0
- tikv0
# TiDB Server
tidb0:
image: pingcap/tidb:v8.1.0
container_name: tidb0
ports:
- "4000:4000"
command:
- --store=tikv
- --path=pd0:2379
- --advertise-address=tidb0
depends_on:
- pd0
- tikv0
- tikv1
- tikv2
# 启动集群
docker-compose up -d
# 等待所有组件就绪
docker-compose logs -f tidb0 | grep "server is ready"
# 连接 TiDB
mysql -h 127.0.0.1 -P 4000 -u root
6.2 创建 HTAP 表并测试
-- 1. 创建订单表
CREATE TABLE orders (
id BIGINT AUTO_RANDOM PRIMARY KEY,
user_id BIGINT NOT NULL,
product_id BIGINT NOT NULL,
amount DECIMAL(12,2) NOT NULL,
quantity INT NOT NULL DEFAULT 1,
status ENUM('pending','paid','shipped','completed','refunded') NOT NULL,
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
INDEX idx_user_id (user_id),
INDEX idx_created_at (created_at),
INDEX idx_status (status)
);
-- 2. 添加 TiFlash 副本
ALTER TABLE orders SET TIFLASH REPLICA 1;
-- 3. 等待副本就绪(检查同步状态)
SELECT TABLE_SCHEMA, TABLE_NAME, REPLICA_COUNT, AVAILABLE
FROM information_schema.tiflash_replica
WHERE TABLE_NAME = 'orders';
-- 4. 插入测试数据(100万行)
INSERT INTO orders (user_id, product_id, amount, quantity, status, created_at)
WITH RECURSIVE nums AS (
SELECT 1 AS n
UNION ALL
SELECT n + 1 FROM nums WHERE n < 1000000
)
SELECT
FLOOR(RAND() * 10000) + 1,
FLOOR(RAND() * 500) + 1,
ROUND(RAND() * 1000 + 10, 2),
FLOOR(RAND() * 5) + 1,
ELT(FLOOR(RAND() * 5) + 1, 'pending','paid','shipped','completed','refunded'),
DATE_SUB(NOW(), INTERVAL FLOOR(RAND() * 365) DAY)
FROM nums;
-- 5. HTAP 查询示例:实时销售分析
SELECT
DATE(created_at) AS sale_date,
status,
COUNT(*) AS order_count,
SUM(amount) AS total_revenue,
AVG(amount) AS avg_order_value,
COUNT(DISTINCT user_id) AS unique_buyers
FROM orders
WHERE created_at >= DATE_SUB(NOW(), INTERVAL 30 DAY)
GROUP BY DATE(created_at), status
HAVING total_revenue > 10000
ORDER BY sale_date DESC, total_revenue DESC;
6.3 性能对比:TiKV vs TiFlash
-- 测试查询:全表聚合
EXPLAIN ANALYZE
SELECT
status,
COUNT(*) AS cnt,
SUM(amount) AS total,
AVG(amount) AS avg_amount
FROM orders
GROUP BY status;
-- 强制 TiKV(行存)
EXPLAIN ANALYZE
SELECT /*+ READ_FROM_STORAGE(TIKV[orders]) */
status,
COUNT(*) AS cnt,
SUM(amount) AS total
FROM orders
GROUP BY status;
-- 强制 TiFlash(列存)
EXPLAIN ANALYZE
SELECT /*+ READ_FROM_STORAGE(TIFLASH[orders]) */
status,
COUNT(*) AS cnt,
SUM(amount) AS total
FROM orders
GROUP BY status;
典型性能对比(100 万行数据):
| 查询类型 | TiKV (行存) | TiFlash (列存) | 提升倍数 |
|---|---|---|---|
| 全表 COUNT(*) | ~800ms | ~50ms | 16x |
| SUM(amount) GROUP BY | ~1200ms | ~80ms | 15x |
| 复杂分析查询 | ~3000ms | ~200ms | 15x |
| 点查 (WHERE id=) | ~2ms | ~10ms | 0.2x |
七、生产环境调优方法论
7.1 TiKV 调优
# tikv.toml 关键调优参数
[server]
# GRPC 并发线程数(根据 CPU 核数调整)
grpc-concurrency = 8
[rocksdb]
# WAL 目录放 SSD
wal-dir = "/data/wal"
# 最大后台任务数
max-background-jobs = 10
[rocksdb.defaultcf]
# Block Cache 大小(建议为总内存的 30-50%)
block-cache-size = "16GB"
# Bloom Filter(加速点查)
bloom-filter-bits-per-key = 10
# 压缩策略
compression-per-level = ["no","no","lz4","lz4","lz4","zstd","zstd"]
[rocksdb.writecf]
# Write Buffer 大小
write-buffer-size = "128MB"
max-write-buffer-number = 5
[raftstore]
# Raft Log 文件大小
raft-log-file-size = "64MB"
# Region 大小(影响分裂粒度)
region-split-size = "96MB"
7.2 TiFlash 调优
# tiflash.toml 关键调优参数
[flash]
# TiFlash 的存储路径
data-dir = "/data/tiflash"
[storage]
# 空间限制(建议为数据量的 2 倍)
capacity = "100GB"
[profiles.default]
# MPP 并发度
max_threads = 16
# 单次查询内存限制
memory_limit = "10GB"
# spill 阈值(超过此值则溢写到磁盘)
spill_threshold = "8GB"
[security]
# 证书配置(生产环境必须)
ca-path = "/etc/tiflash/ca.pem"
cert-path = "/etc/tiflash/server-cert.pem"
key-path = "/etc/tiflash/server-key.pem"
7.3 监控关键指标
-- 1. TiKV 监控:Region 健康度
SELECT
STORE_ID,
LABEL,
CAPACITY,
AVAILABLE,
USED_SIZE,
LEADER_COUNT,
REGION_COUNT
FROM information_schema.tikv_store_status;
-- 2. TiFlash 监控:副本同步延迟
SELECT
TABLE_SCHEMA,
TABLE_NAME,
REPLICA_COUNT,
AVAILABLE,
PROGRESS
FROM information_schema.tiflash_replica;
-- 3. 慢查询分析
SELECT
DIGEST_TEXT,
COUNT(*) AS exec_count,
AVG(query_time) AS avg_time,
MAX(query_time) AS max_time,
AVG(processed_keys) AS avg_keys
FROM information_schema.slow_query
WHERE start_time >= DATE_SUB(NOW(), INTERVAL 1 DAY)
GROUP BY DIGEST_TEXT
ORDER BY avg_time DESC
LIMIT 10;
八、踩坑清单与最佳实践
8.1 常见踩坑
坑1:TiFlash 副本同步延迟过大
-- 症状:分析查询结果明显滞后
-- 原因:TiFlash 节点资源不足或网络延迟
-- 解决:
-- 1. 检查 TiFlash 资源使用
SELECT * FROM information_schema.tiflash_status;
-- 2. 增加 TiFlash 副本数
ALTER TABLE orders SET TIFLASH REPLICA 2;
-- 3. 调整同步参数
SET GLOBAL tiflash_follower_read_stale_read = 'OFF';
坑2:OLTP 和 OLAP 互相干扰
-- 症状:分析查询导致 OLTP 延迟飙升
-- 解决:使用资源组隔离
CREATE RESOURCE GROUP rg_analytical
RU_PER_SEC = 1000
PRIORITY = LOW
BURSTABLE = YES;
-- 将分析查询绑定到低优先级资源组
SET RESOURCE GROUP rg_analytical FOR SESSION;
坑3:大事务导致 TiKV 压力
-- 症状:大批量 INSERT 时 TiKV CPU 飙升
-- 解决:分批写入,每批 1000-5000 行
-- 反面教材:
INSERT INTO orders SELECT * FROM large_table; -- 一次写入百万行
-- 正确做法:
-- 使用分批写入脚本
DELIMITER //
CREATE PROCEDURE batch_insert()
BEGIN
DECLARE batch_size INT DEFAULT 5000;
DECLARE total INT;
DECLARE offset INT DEFAULT 0;
SELECT COUNT(*) INTO total FROM large_table;
WHILE offset < total DO
INSERT INTO orders (user_id, product_id, amount, status)
SELECT user_id, product_id, amount, status
FROM large_table
LIMIT batch_size OFFSET offset;
SET offset = offset + batch_size;
-- 每批之间短暂等待,避免 TiKV 过载
DO SLEEP(0.1);
END WHILE;
END //
DELIMITER ;
8.2 最佳实践
1. 合理设置 TiFlash 副本数
-- OLTP 表:1 份 TiFlash 副本(够用)
ALTER TABLE orders SET TIFLASH REPLICA 1;
-- 高频分析表:2 份 TiFlash 副本(读扩展)
ALTER TABLE analytics_events SET TIFLASH REPLICA 2;
-- 纯 OLTP 表:不需要 TiFlash
-- 不执行 ALTER TABLE SET TIFLASH REPLICA
2. 使用分区表提升查询效率
-- 按月分区,分析查询只扫描相关分区
CREATE TABLE orders (
id BIGINT AUTO_RANDOM,
user_id BIGINT,
amount DECIMAL(12,2),
created_at TIMESTAMP,
PRIMARY KEY (id, created_at)
) PARTITION BY RANGE (UNIX_TIMESTAMP(created_at)) (
PARTITION p202601 VALUES LESS THAN (UNIX_TIMESTAMP('2026-02-01')),
PARTITION p202602 VALUES LESS THAN (UNIX_TIMESTAMP('2026-03-01')),
PARTITION p202603 VALUES LESS THAN (UNIX_TIMESTAMP('2026-04-01')),
-- ... 按需添加
PARTITION p_future VALUES LESS THAN MAXVALUE
);
3. 物化视图加速高频分析
-- 创建物化视图(TiDB 8.0+ 支持)
CREATE MATERIALIZED VIEW mv_daily_sales AS
SELECT
DATE(created_at) AS sale_date,
status,
COUNT(*) AS order_count,
SUM(amount) AS total_revenue
FROM orders
GROUP BY DATE(created_at), status;
九、TiDB 与其他 HTAP 方案对比
| 维度 | TiDB | CockroachDB | SingleStore | Apache Doris |
|---|---|---|---|---|
| 架构 | 行存+列存分离 | 行存为主 | 行存+列存混合 | 列存为主 |
| 事务 | 分布式 ACID | 分布式 ACID | 有限事务 | 无原生事务 |
| OLTP 性能 | ★★★★☆ | ★★★★☆ | ★★★☆☆ | ★★☆☆☆ |
| OLAP 性能 | ★★★★☆ | ★★★☆☆ | ★★★★★ | ★★★★★ |
| HTAP 成熟度 | ★★★★★ | ★★★☆☆ | ★★★★☆ | ★★☆☆☆ |
| 开源协议 | Apache 2.0 | BSL 1.1 | 商业 | Apache 2.0 |
| 运维复杂度 | 中等 | 高 | 低 | 中等 |
十、总结与展望
TiDB 的 HTAP 架构代表了数据库发展的一个重要方向:打破 OLTP 和 OLAP 的壁垒,用一套系统服务所有数据需求。
核心设计哲学总结:
- 计算存储分离:TiDB Server 无状态,可以水平扩展;TiKV/TiFlash 存储有状态,通过 Raft 保证高可用
- 行存列存并存:TiKV 行存服务 OLTP,TiFlash 列存服务 OLAP,通过 Raft Learner 异步同步
- 智能路由:查询优化器自动选择最优存储引擎,开发者无需关心底层实现
- 渐进式 HTAP:可以先只用 TiKV(纯 OLTP),需要时再添加 TiFlash 副本,平滑演进
2026 年 TiDB 的关键趋势:
- TiFlash v2:新一代列存引擎,支持向量化执行、算子下推到 Storage 层,OLAP 性能再提升 3-5 倍
- TiDB Cloud Serverless:按需付费的 Serverless 模式,进一步降低使用门槛
- AI + HTAP:将向量索引集成到 TiDB,支持 AI 应用的混合查询(结构化数据 + 向量检索)
- 多模存储:TiKV 正在探索支持 JSON、时序、图等多种数据模型
对于正在选型 HTAP 数据库的团队,TiDB 是目前最成熟、生态最完善的开源选择。它的优势在于:不需要改变现有的 MySQL 生态(兼容 MySQL 协议),不需要引入新的运维体系(提供完善的监控和运维工具),不需要在 OLTP 和 OLAP 之间做取舍(真正的混合负载支持)。
当然,TiDB 也有其局限性:运维复杂度比单机数据库高,小规模部署的性价比不如 MySQL + ClickHouse 的组合,TiFlash 的 OLAP 性能与 ClickHouse/Doris 还有差距。但对于数据量在 TB 级别、同时需要 OLTP 和 OLAP 能力的场景,TiDB 是值得认真考虑的方案。
数据库的未来属于 HTAP。而 TiDB,正在这条路上走得最远。