编程 TiDB HTAP 架构深度拆解:从 Raft 共识到列存 MPP,手写一个混合负载查询引擎

2026-08-02 20:14:17 +0800 CST views 11

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 语句执行时:

  1. TiDB Server 将写请求发送到 TiKV
  2. TiKV 通过 Raft 将数据复制到多个 TiKV 副本
  3. TiFlash 作为 Raft Learner,异步从 TiKV 同步数据
  4. 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 结构

写入流程:

  1. 写入 WAL(Write-Ahead Log)——持久化保证
  2. 写入 MemTable(内存中的跳表)——快速写入
  3. MemTable 满了后 Flush 到 SST 文件(L0)
  4. 后台 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'

  • 行存:需要扫描整张表(全表扫描),读取每一行的所有列
  • 列存:只读取 amountstatus 两列,跳过其他列,压缩比更高

在实际测试中,列存对于分析查询的性能通常是行存的 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: ...                                  │
└─────────────────────────────────────────────────┘

关键设计决策

  1. Delta 编码:对于递增的整数列(如自增 ID、时间戳),Delta 编码只存储差值,压缩比极高
  2. 字典编码:对于低基数列(如 status: paid/pending/refunded),使用字典编码将字符串映射为整数
  3. Run-Length 编码:对于有大量连续重复值的列,存储"值 × 重复次数"
  4. 向量化执行:数据按 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~50ms16x
SUM(amount) GROUP BY~1200ms~80ms15x
复杂分析查询~3000ms~200ms15x
点查 (WHERE id=)~2ms~10ms0.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 方案对比

维度TiDBCockroachDBSingleStoreApache Doris
架构行存+列存分离行存为主行存+列存混合列存为主
事务分布式 ACID分布式 ACID有限事务无原生事务
OLTP 性能★★★★☆★★★★☆★★★☆☆★★☆☆☆
OLAP 性能★★★★☆★★★☆☆★★★★★★★★★★
HTAP 成熟度★★★★★★★★☆☆★★★★☆★★☆☆☆
开源协议Apache 2.0BSL 1.1商业Apache 2.0
运维复杂度中等中等

十、总结与展望

TiDB 的 HTAP 架构代表了数据库发展的一个重要方向:打破 OLTP 和 OLAP 的壁垒,用一套系统服务所有数据需求

核心设计哲学总结

  1. 计算存储分离:TiDB Server 无状态,可以水平扩展;TiKV/TiFlash 存储有状态,通过 Raft 保证高可用
  2. 行存列存并存:TiKV 行存服务 OLTP,TiFlash 列存服务 OLAP,通过 Raft Learner 异步同步
  3. 智能路由:查询优化器自动选择最优存储引擎,开发者无需关心底层实现
  4. 渐进式 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,正在这条路上走得最远。

推荐文章

如何在 Linux 系统上安装字体
2025-02-27 09:23:03 +0800 CST
PHP 代码功能与使用说明
2024-11-18 23:08:44 +0800 CST
JavaScript 异步编程入门
2024-11-19 07:07:43 +0800 CST
你可能不知道的 18 个前端技巧
2025-06-12 13:15:26 +0800 CST
关于 `nohup` 和 `&` 的使用说明
2024-11-19 08:49:44 +0800 CST
程序员茄子在线接单