在 TiDB 的 HTAP(混合事务与分析处理)架构中,系统同时具备行存引擎 TiKV 与列存引擎 TiFlash。优化器(CBO)在接收到 SQL 后,会自动评估代价并决定是将计算下推给 TiKV 还是 TiFlash。

但在实际生产运维中,经常会遇到“为什么这个聚合查询没有走 TiFlash?”、“为什么明明加了 TiFlash 副本,查询反而变慢甚至 OOM?”等问题。理解两者的底层存储与计算模型差异,掌握正确的选路基准与调优方法,是保障 TiDB 集群高效运行的关键。

本文将从底层架构原理、选路决策基准、典型避坑案例及执行计划调优四个方面,全面解析 TiKV 与 TiFlash 的选路逻辑。

一、核心决策速查表

在分析具体 SQL 之前,可以通过以下几个关键维度快速判断查询应该走哪个存储引擎:

判定维度走 TiKV(行存)走 TiFlash(列存 + MPP)
典型业务场景OLTP 事务、精准点查、高频业务 APIOLAP 报表、BI 看板分析、海量数据重度聚合
扫描数据行数$< 10^4$ 行(通过索引快速收敛)$> 10^5 \sim 10^9$ 行(大范围扫描或全表扫描)
访问列占总列数比例$> 50%$ 或 SELECT * 宽表读取$< 20%$(尤其是单列/少列的聚合计算)
索引可用性存在高区分度的二级索引或覆盖索引无可用索引、低选择性过滤或大跨度范围过滤
Join 计算形式小表 Lookup Join / 驱动表索引关联大表 Hash Join / 跨节点 MPP Shuffle Join
并发与延迟目标高 QPS(上万)、超低延迟($< 10\text{ms}$)低 QPS(几十几百)、高吞吐优先(亚秒秒级)

二、底层存储与算力模型差异

TiKV 与 TiFlash 不仅仅是“行存”与“列存”的区别,其背后的数据组织方式和分布式计算能力有着根本的不同:

+-------------------------------------------------------------+
|                      TiDB Server (CBO)                      |
+------------------------------+------------------------------+
                               |
            +------------------+------------------+
            |                                     |
            v                                     v
+-----------------------+             +-----------------------+
|      TiKV Cluster     |             |    TiFlash Cluster    |
+-----------------------+             +-----------------------+
|  • RocksDB LSM-Tree   |             |  • DeltaTree 列存     |
|  • 行式物理存储       |             |  • 按列压缩与独立切块 |
|  • Coprocessor 算子   |             |  • MPP 节点间 Shuffle |
|  • B-Tree 索引二分查找|             |  • SIMD 向量化加速    |
+-----------------------+             +-----------------------+

1. TiKV(行存 + 索引寻道)

  • 存储模型:基于 RocksDB 的 LSM-Tree 行式存储,单行数据的所有字段在物理上紧凑连续存储。
  • 计算机制:利用 Coprocessor 将 FilterTopN 及简单聚合算子下推到单个 Region 执行。
  • 核心优势:通过主键或二级索引做点查或小范围 Seek 时,几乎无离散列组装开销;配合覆盖索引(Covering Index),可实现微秒至毫秒级的极速响应。

2. TiFlash(列存 + MPP 分布式计算)

  • 存储模型:基于针对分析场景自研的 DeltaTree 列式存储引擎,每列数据单独存储并进行高比例压缩。
  • 计算机制
    • 列投影过滤(Column Projection):查询几列就只从磁盘读取几列,极大降低 I/O 吞吐开销。
    • MPP 架构:支持在 TiFlash 存储节点之间直接通过网络进行 Partition/Broadcast 数据 Shuffle 与分布式 Hash Join,无需将海量中间结果汇聚到 TiDB Server 单点,打破了单机内存与 CPU 瓶颈。
    • 向量化与 Rough Set:利用 CPU SIMD 指令批量计算,并借助数据块的 Min-Max 粗糙集索引快速跳过无关数据段。

三、场景判定与特征分析

1. 优先走 TiKV 的典型场景

  1. 主键、唯一键或高区分度索引点查
    • 特征WHERE id = 1001WHERE uid = 456 AND status = 1
    • 原因:B-Tree 索引二分查找极快;TiFlash 没有行级二级索引,点查只能做全表扫描,代价极高。
  2. 高频数据写入、更新与删除
    • 特征:业务事务中的 INSERTUPDATE ... WHERE id = ...
    • 原因:TiFlash 是只读的 Raft Learner 副本,事务写入与行锁由 TiKV 的 Raft Leader 协调处理。
  3. 宽表全字段投影(SELECT *
    • 特征:表包含数十上百列,且业务需要返回该行的所有字段。
    • 原因:列存读取全部列会引发“列拼接(Column Reconstruction)”,产生大量离散 I/O 与内存组装开销,整体性能反而弱于行存。
  4. 覆盖索引查询(Covering Index Scan)
    • 特征:SQL 所需的所有列(包括 Filter 和 Select)均包含在联合索引中。
    • 原因:TiKV 直接在索引元数据上完成扫描与聚合,完全省去了回表(IndexLookUp)的成本。

2. 优先走 TiFlash 的典型场景

  1. 大宽表统计少量列(Agg & Group By)
    • 特征SELECT dept_id, SUM(amount), AVG(duration) FROM sales GROUP BY dept_id
    • 原因:列存只扫描计算涉及的三列数据,I/O 消耗仅为行存全行扫描的百分之一甚至更低。
  2. 缺乏高选择性索引的大范围扫描
    • 特征WHERE create_time BETWEEN '2025-01-01' AND '2025-12-31'(涉及数千万行甚至数亿行)。
    • 原因:此时走 TiKV 索引回表会导致海量随机 I/O,走 TiKV 全表扫则数据量过大;TiFlash 的列存高吞吐扫描与 Min-Max 过滤优势显著。
  3. 海量数据跨表关联(MPP Join)
    • 特征:千万级订单事实表与百万级商户/商品维度表关联。
    • 原因:TiFlash MPP 引擎在存储节点间并行完成 Hash Join 计算,避免了将几千万行中间数据回传至 TiDB Server 造成的网络与内存爆满。
  4. 低 QPS 的重度分析与报表接口
    • 特征:后台运营看板、财务月结统计,对 QPS 要求不高,但要求单次查询在亚秒或数秒内完成。

四、常见排查误区与避坑案例

踩坑 1:看到聚合慢查询就盲目加 Hint 强制走 TiFlash

  • 典型误区:认为只要 SQL 中有 COUNT(*)SUM() 且耗时长,就应该走 TiFlash。
  • 真实案例:某现货订单表根据单一用户 ID 统计近 30 天未完成订单:
    SELECT COUNT(*) FROM orders WHERE uid = 880192 AND ctime > 1700000000 AND status IN (1, 2);
    
    原有索引为 (uid, status, ctime),由于范围条件 ctime 在最后,导致前缀 Seek 无法截断,TiKV 扫描了该 UID 下 100 多万条历史索引,耗时 600ms。 如果强行加 Hint 走 TiFlash,由于 TiFlash 没有 uid 的二级索引,会退化为大范围扫描甚至冷数据远程拉取,耗时反而增加到数秒。
  • 正确解法:单 UID/单租户的聚合点查,本质仍属于 OLTP 范畴,应调优 TiKV 联合索引列顺序(uid, ctime, status),让前缀条件直接收敛扫描行数至百行以内,耗时降至 2ms。

踩坑 2:在 TiFlash 上执行 SELECT * 导致 OOM

  • 典型误区:误以为只要机器资源够,TiFlash 的速度任何时候都比 TiKV 快。
  • 后果:对 60+ 列的大宽表执行 SELECT * 走 TiFlash,TiFlash 需要将每一列分别解压并拼装成行,引发巨大的内存占用和 CPU 序列化消耗,极易导致 TiFlash 节点或 TiDB Server OOM。
  • 规范:面向 TiFlash 的分析查询,必须严格遵守 按需投影(Column Pruning) 原则,绝不允许滥用 SELECT *

踩坑 3:统计信息过期导致优化器选路失真

  • 现象:业务逻辑未变,原本走 TiKV 毫秒级返回的 SQL 突然变慢并转走了 TiFlash 全表扫,或者本来该走 TiFlash 的大分析被误派给 TiKV 导致慢查询。
  • 排查方法
    -- 检查表的统计信息健康度
    SHOW STATS_HEALTHY WHERE Table_name = 'orders';
    
    -- 查看 EXPLAIN 中的预估行数(estRows)与实际扫描行数(actRows)是否偏差过大
    EXPLAIN ANALYZE SELECT ...;
    
  • 解决办法:重新收集统计信息并设置合理的自动收集调度:
    ANALYZE TABLE orders;
    

五、执行计划识别与人工控制

1. 执行计划(EXPLAIN)关键特征

通过 EXPLAIN 查看 SQL 执行计划时,重点观察 Task 列与算子名称:

  • 走 TiKV 的标志
    • Task 列出现 cop[tikv]root
    • 算子包含 Point_GetIndexLookUpIndexRangeScanIndexReader 等。
  • 走 TiFlash 的标志
    • Task 列出现 cop[tiflash]mpp[tiflash]
    • 算子包含 TableFullScan(列存块读取)、ExchangeSender / ExchangeReceiver(MPP 节点间数据分发)。

2. 人工控制与 Hint 语法

当优化器由于统计信息波动或复杂表达式导致选路非最优时,可通过 Hint 人工指定存储引擎:

  • 强制指定走 TiFlash

    SELECT /*+ READ_FROM_STORAGE(TIFLASH[t1, t2]) */
           dept_id, SUM(amount)
    FROM sales t1
    JOIN department t2 ON t1.dept_id = t2.id
    GROUP BY dept_id;
    
  • 强制指定走 TiKV

    SELECT /*+ READ_FROM_STORAGE(TIKV[orders]) */ *
    FROM orders
    WHERE order_sn = 'SN20260824001';
    
  • MPP 模式开关控制

    -- 会话级开启 MPP(默认开启)
    SET @@tidb_allow_mpp = ON;
    
    -- 临时关闭 MPP(降级为单节点 Coprocessor 收集后回传 TiDB 计算)
    SET @@tidb_allow_mpp = OFF;
    

六、选路决策流程图

                +------------------------------+
                |     SQL 调优 / 引擎选路决策   |
                +--------------+---------------+
                               |
                               v
                /------------------------------\
               /    单次预估扫描行数规模?      \
              +--------------------------------+
               |                              |
               | < 10^4 行                    | > 10^5 行
               v                              v
    /----------------------\        /----------------------\
   /  是否有高区分度索引?   \      /   查询涉及列占比?     \
  +--------------------------+    +--------------------------+
   |                        |      |                        |
   | 是                     | 否   | 少量列 / 聚合计算      | 大量列 / SELECT *
   v                        v      v                        v
+------------------+   +-------+ +------------------+   +------------------+
|    走 TiKV       |   | 尝试建| |   走 TiFlash     |   | 评估裁剪投影列   |
| 依靠索引极速返回 |   | 覆盖索| | 利用列存+MPP加速 |   | 或改走行存分页   |
+------------------+   +-------+ +------------------+   +------------------+

掌握了上述核心原理与选路特征后,在面对慢查询报警时,就能从“盲目加 Hint”转变为“从数据规模、索引结构与列投影比例系统性分析”,从而做出最合理的性能调优决策。