一、什么是 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 数量 |