TiDB数据库之HTAP 混合负载分析 — TiFlash

一、什么是 HTAP

1.1 OLTP vs OLAP

传统上,数据库分为两大类:

维度 OLTP(联机事务处理) OLAP(联机分析处理)
典型场景 订单、支付、用户管理 报表、BI、数据挖掘
查询特征 简单查询、高频、低延迟 复杂查询、低频、高吞吐
数据访问 随机读写 顺序读取
存储格式 行存 列存
典型引擎 MySQL InnoDB ClickHouse、Greenplum
行存 vs 列存的区别:

行存:
┌────┬──────┬─────┬────────────┐
│ id │ name │ age │ department │
├────┼──────┼─────┼────────────┤
│ 1  │ Alice│ 28  │ Engineering│  ← 一行连续存储
│ 2  │ Bob  │ 35  │ Marketing  │
│ 3  │ Carol│ 22  │ Engineering│
└────┴──────┴─────┴────────────┘

SELECT AVG(age) FROM employees;
→ 需要读取所有行的全部列(浪费!我们只需要 age)

列存:
id:      [1, 2, 3]
name:    ["Alice", "Bob", "Carol"]
age:     [28, 35, 22]        ← 只读这一列就够了!
department: ["Engineering", "Marketing", "Engineering"]

SELECT AVG(age) FROM employees;
→ 只读取 age 列,大幅减少 I/O

1.2 HTAP 的含义

HTAP = Hybrid Transactional/Analytical Processing

核心目标:同一套系统,同时支持 OLTP 和 OLAP,无需额外的 ETL 流程将数据同步到另一个分析系统。

传统方案 (OLTP + OLAP 分离):

  ┌────────┐     ETL      ┌────────┐
  │ MySQL  │ ──────────>  │ ClickH │
  │ (OLTP) │   (延迟/复杂) │ (OLAP) │
  └────────┘              └────────┘
     │                        │
  事务查询                 分析查询

TiDB HTAP 方案:

  ┌──────────────────────────────────┐
  │            TiDB                   │
  │                                   │
  │  ┌───────┐      实时同步    ┌───────┐ │
  │  │ TiKV  │ ──────────────> │TiFlash│ │
  │  │(行存)  │                │(列存)  │ │
  │  └───────┘                 └───────┘ │
  │     │                          │     │
  │  事务查询                   分析查询   │
  └──────────────────────────────────┘

二、TiFlash 原理

2.1 TiFlash 是什么

TiFlash 是 TiDB 的列存引擎组件,它以 TiKV 的 Region 为单位复制数据,并以列式格式存储:

                     Raft Learner
  ┌──────────────────────────────────┐
  │  TiFlash                         │
  │                                   │
  │  ┌─────────┐  ┌─────────┐        │
  │  │Region A  │  │Region B  │  ...  │
  │  │(列存格式) │  │(列存格式) │       │
  │  └─────────┘  └─────────┘        │
  │                                   │
  │  作为 Raft Learner 从 TiKV 复制数据 │
  │  不参与投票,只同步数据             │
  └──────────────────────────────────┘

TiFlash 的关键设计:

  • 作为 Raft Group 的 Learner 角色,从 TiKV 的 Leader 复制数据
  • 数据以列式格式存储,适合分析查询
  • 与 TiKV 数据保持实时同步
  • 对应用完全透明,不需要额外代码

2.2 数据如何同步到 TiFlash

写入流程:

客户端 ──INSERT/UPDATE/DELETE──> TiDB Server
                                     │
                                     v
                              写入 TiKV (行存)
                                     │
                                     │ Raft 复制
                                     v
                              TiFlash (Learner)
                                     │
                                     v
                              转为列存格式存储

整个过程对应用透明,不需要额外操作。

2.3 TiFlash 如何被使用

SQL 执行流程:

SELECT COUNT(*), department FROM employees GROUP BY department;

1. TiDB Server 收到 SQL
2. 优化器判断 TiFlash 有该表的副本
3. 对于分析类查询,自动选择 TiFlash 执行
4. 结果返回给客户端

应用完全无感知,同一个 SQL,TiDB 自动选择最优执行路径。

也可以手动指定:

-- 强制使用 TiFlash 副本
SELECT /*+ READ_FROM_STORAGE(tiflash[employees]) */ *
FROM employees;

-- 强制使用 TiKV 副本
SELECT /*+ READ_FROM_STORAGE(tikv[employees]) */ *
FROM employees;

三、部署 TiFlash

3.1 拓扑配置

topology.yaml 中添加 TiFlash 节点:

tiflash_servers:
  - host: 10.0.1.40
    tcp_port: 9000
    http_port: 8123
    flash_service_port: 3930
    flash_proxy_port: 20170
    flash_proxy_status_port: 20292
    data_dir: /tidb-data/tiflash-9000
    log_dir: /tidb-deploy/tiflash-9000/log
# 部署时添加 TiFlash
tiup cluster deploy tidb-cluster v8.5.0 topology.yaml

# 或者在已有集群中扩容 TiFlash
tiup cluster scale-out tidb-cluster tiflash-topology.yaml

3.2 开启表的 TiFlash 副本

默认情况下,新建的表不会自动复制到 TiFlash。需要手动指定:

注意:以下命令需要 TiFlash 节点已部署。使用 Playground 启动时需指定 --tiflash 1 参数。

-- 为特定表创建 TiFlash 副本(1 个副本)
ALTER TABLE employees SET TIFLASH REPLICA 1;

-- 等待同步完成
-- 可以通过以下 SQL 检查同步进度:
SELECT * FROM information_schema.tiflash_replica;
-- 输出:
-- +--------------+---------------+----------+---------------+-------------------+
-- | TABLE_SCHEMA | TABLE_NAME    | TABLE_ID | REPLICA_COUNT | LOCATION_LABELS   |
-- +--------------+---------------+----------+---------------+-------------------+
-- | demo         | employees     | 108      | 1             |                   |
-- +--------------+---------------+----------+---------------+-------------------+

-- 为整个数据库的所有表创建副本
ALTER DATABASE demo SET TIFLASH REPLICA 1;

同步进度查看:

-- 查看同步进度(AVAILABLE = 1 表示同步完成)
SELECT
    TABLE_SCHEMA,
    TABLE_NAME,
    TABLE_ID,
    REPLICA_COUNT
FROM information_schema.tiflash_replica
WHERE AVAILABLE = 0;
-- AVAILABLE = 1 表示同步完成

注意:在较新的 TiDB 版本中,information_schema.tiflash_replica 表使用 AVAILABLE 字段(0 或 1)表示同步状态,取代了早期版本的 PROGRESS 字段。


四、HTAP 查询实践

4.1 创建实验数据

CREATE DATABASE htap_demo;
USE htap_demo;

-- 订单表(100 万行级别)
CREATE TABLE orders (
    id BIGINT PRIMARY KEY AUTO_RANDOM,
    user_id BIGINT NOT NULL,
    product_id INT NOT NULL,
    category VARCHAR(32),
    amount DECIMAL(10, 2),
    quantity INT,
    order_date DATE,
    status VARCHAR(16),
    INDEX idx_user (user_id),
    INDEX idx_date (order_date),
    INDEX idx_category (category)
);

-- 为 TiFlash 创建副本
ALTER TABLE orders SET TIFLASH REPLICA 1;

-- 等待同步完成后,插入测试数据
-- 注意:TiDB 不支持存储过程,使用应用层脚本批量插入
-- 以下是 Python 示例:
-- import mysql.connector
-- conn = mysql.connector.connect(host='127.0.0.1', port=4000, user='root', database='htap_demo')
-- cursor = conn.cursor()
-- categories = ['Electronics', 'Clothing', 'Books', 'Home', 'Sports']
-- statuses = ['pending', 'shipped', 'delivered', 'cancelled']
-- for i in range(1000000):
--     cursor.execute(
--         "INSERT INTO orders (user_id, product_id, category, amount, quantity, order_date, status) "
--         "VALUES (%s, %s, %s, %s, %s, %s, %s)",
--         (random.randint(1, 10000), random.randint(1, 500), random.choice(categories),
--          round(random.uniform(10, 1000), 2), random.randint(1, 10),
--          f"2023-01-01 + INTERVAL {random.randint(0, 730)} DAY", random.choice(statuses))
--     )
--     if (i + 1) % 10000 == 0:
--         conn.commit()
-- conn.commit()
-- cursor.close()
-- conn.close()

-- 或者使用简单的 SQL 批量插入方式(分批插入)
INSERT INTO orders (user_id, product_id, category, amount, quantity, order_date, status)
SELECT
    FLOOR(1 + RAND() * 10000),
    FLOOR(1 + RAND() * 500),
    ELT(FLOOR(1 + RAND() * 5), 'Electronics', 'Clothing', 'Books', 'Home', 'Sports'),
    ROUND(10 + RAND() * 990, 2),
    FLOOR(1 + RAND() * 10),
    DATE_ADD('2023-01-01', INTERVAL FLOOR(RAND() * 730) DAY),
    ELT(FLOOR(1 + RAND() * 4), 'pending', 'shipped', 'delivered', 'cancelled')
FROM orders a, orders b LIMIT 10000;
-- 重复执行上述 INSERT 直到数据量满足需求

4.2 对比 TiKV 和 TiFlash 性能

-- 查看执行计划(观察是否使用了 TiFlash)
EXPLAIN
SELECT
    category,
    COUNT(*) AS order_count,
    SUM(amount) AS total_amount,
    AVG(amount) AS avg_amount
FROM orders
GROUP BY category
ORDER BY total_amount DESC;

如果使用了 TiFlash,执行计划中会出现 TableFullScan 且 task 为 batchCop[tiflash]

+---------------------------+----------+---------+-------------------+
| id                        | estRows  | task    | operator info     |
+---------------------------+----------+---------+-------------------+
| Projection_4              | 5.00     | root    | ...               |
| └─TopN_7                  | 5.00     | root    | ...               |
|   └─HashAgg_16            | 5.00     | root    | group by:category |
|     └─TableReader_17      | 1000000  | root    |                   |
|       └─TableFullScan_16  | 1000000  | batchCop[tiflash] | table:orders |
+---------------------------+----------+---------+-------------------+

关键特征

  • task = batchCop[tiflash] 表示在 TiFlash 上执行
  • 列存引擎在聚合查询中可以大幅减少 I/O

4.3 典型分析查询

-- 1. 月度销售趋势
SELECT
    DATE_FORMAT(order_date, '%Y-%m') AS month,
    COUNT(*) AS order_count,
    SUM(amount) AS revenue
FROM orders
WHERE status = 'delivered'
GROUP BY month
ORDER BY month;

-- 2. 品类销售排名
SELECT
    category,
    COUNT(*) AS order_count,
    SUM(amount) AS total_revenue,
    ROUND(SUM(amount) / SUM(SUM(amount)) OVER () * 100, 2) AS revenue_pct
FROM orders
WHERE status != 'cancelled'
GROUP BY category
ORDER BY total_revenue DESC;

-- 3. 用户购买力分析(Top 100)
SELECT
    user_id,
    COUNT(*) AS total_orders,
    SUM(amount) AS total_spent,
    AVG(amount) AS avg_order_value
FROM orders
WHERE status = 'delivered'
GROUP BY user_id
ORDER BY total_spent DESC
LIMIT 100;

-- 4. 多表关联分析
SELECT
    o.category,
    DATE_FORMAT(o.order_date, '%Y-%m') AS month,
    COUNT(DISTINCT o.user_id) AS active_users,
    COUNT(*) AS orders,
    SUM(o.amount) AS revenue
FROM orders o
WHERE o.status = 'delivered'
GROUP BY o.category, month
ORDER BY revenue DESC;

五、HTAP 的局限与注意事项

5.1 适用场景

TiFlash 擅长:

查询类型 说明
全表扫描 + 聚合 COUNT, SUM, AVG 等
多列聚合 GROUP BY 多列
大表 JOIN 列存 Join 效率高
窗口函数 ROW_NUMBER, RANK, LAG 等

TiFlash 不擅长:

查询类型 说明
单行点查 WHERE id = 1(TiKV 更快)
小范围查询 WHERE id BETWEEN 1 AND 10
高并发 OLTP TiFlash 不是为高并发设计的

5.2 副本一致性

TiFlash 数据与 TiKV 数据通过 Raft 保持强一致,不需要担心数据不一致的问题。

5.3 资源隔离

TiFlash 是独立的进程,与 TiKV 不共享资源,因此 OLAP 查询不会影响 OLTP 的性能。


六、实践:搭建实时报表系统

-- 场景: 电商实时报表

-- 1. 创建维度表
CREATE TABLE products (
    id INT PRIMARY KEY,
    name VARCHAR(128),
    category VARCHAR(32),
    price DECIMAL(10, 2)
);

CREATE TABLE users (
    id BIGINT PRIMARY KEY,
    name VARCHAR(64),
    city VARCHAR(32),
    level VARCHAR(16)
);

-- 2. 开启 TiFlash 副本
ALTER TABLE orders SET TIFLASH REPLICA 1;
ALTER TABLE products SET TIFLASH REPLICA 1;
ALTER TABLE users SET TIFLASH REPLICA 1;

-- 3. 实时销售大屏数据
-- 实时销售额
SELECT
    p.category,
    COUNT(o.id) AS order_count,
    SUM(o.amount) AS total_amount
FROM orders o
    JOIN products p ON o.product_id = p.id
WHERE o.status IN ('delivered', 'shipped')
GROUP BY p.category;

-- 各城市销售排名
SELECT
    u.city,
    COUNT(DISTINCT o.user_id) AS buyers,
    SUM(o.amount) AS revenue
FROM orders o
    JOIN users u ON o.user_id = u.id
WHERE o.status = 'delivered'
GROUP BY u.city
ORDER BY revenue DESC
LIMIT 10;

-- 实时趋势(最近 24 小时)
SELECT
    DATE_FORMAT(o.order_date, '%H:00') AS hour,
    COUNT(*) AS orders,
    SUM(o.amount) AS revenue
FROM orders o
WHERE o.order_date >= NOW() - INTERVAL 24 HOUR
GROUP BY hour
ORDER BY hour;

七、监控 TiFlash

-- 查看 TiFlash 副本同步进度
SELECT * FROM information_schema.tiflash_replica;

-- 查看 TiFlash 节点状态
SELECT * FROM information_schema.cluster_info WHERE type = 'tiflash';

-- 查看 TiFlash 相关监控指标(通过 Grafana)
-- 访问 http://<grafana-host>:3000
-- 选择 "TiFlash-Summary" 仪表板

Grafana 中的关键指标:

指标 含义
Flash Storage Write Duration 写入延迟
Flash Read Duration 读取延迟
Flash Coprocessor Requests 协处理请求数
Flash Region Count Region 数量

楼主总结得很清晰!补充几个TiFlash实战要点:

  1. 开启TiFlash副本用这条SQL:
ALTER TABLE employees SET TIFLASH REPLICA 1;

等几秒后查进度:

SELECT * FROM information_schema.tiflash_replica WHERE TABLE_NAME='employees';
  1. 查询自动走TiFlash的条件是表上必须有副本,且SQL优化器认为走列存更优。

学习了,谢谢大佬分享