摘要
大表慢查询是数据库运维中最常见的问题之一,数据量增长到千万级以上后,全表扫描、索引缺失、关联查询低效等问题集中爆发。本文从索引优化、并行查询、执行计划分析、TiFlash 加速四个层面,结合 TiDB 分布式数据库的具体实践,提供一套系统化的大表查询调优方法。
本文适合谁: 遇到大表查询性能瓶颈的 DBA、后端开发工程师及数据库架构师。
一、大表慢查询的常见原因
1.1 典型症状
- 单表数据量超过 1000 万行后,查询响应时间从毫秒级上升到秒级
- 多表 JOIN 查询在数据量增长后响应急剧变慢
- 范围查询(BETWEEN、>、<)和排序(ORDER BY)性能下降
- 统计聚合查询(COUNT、SUM、AVG)耗时过长
1.2 根因分类
| 原因类别 | 典型表现 | 影响程度 |
|---|---|---|
| 索引缺失或不合理 | 全表扫描、索引未被命中 | 高 |
| 统计信息过期 | 执行计划选择错误索引 | 高 |
| 关联查询效率低 | Nested Loop Join 大表嵌套 | 高 |
| 数据倾斜 | 某些 Region 热点导致性能瓶颈 | 中 |
| 内存不足 | 大结果集溢出到磁盘 | 中 |
| 网络开销 | 分布式环境下跨节点数据传输 | 低-中 |
二、索引优化
2.1 复合索引设计原则
复合索引的字段顺序遵循"最左前缀原则",将选择性高的字段放在左侧:
-- 假设业务查询模式:WHERE user_id = ? AND order_status = ? AND create_time BETWEEN ? AND ?
-- 复合索引设计
ALTER TABLE orders ADD INDEX idx_user_status_time (user_id, order_status, create_time);
设计要点:
- 等值条件字段在前,范围条件字段在后
- 选择性(Cardinality / 总行数)高的字段在前
- 单个索引字段数建议不超过 5 个
2.2 覆盖索引避免回表
当查询所需字段全部包含在索引中时,TiDB 直接从索引返回结果,无需回表查询数据行:
-- 查询:SELECT user_id, order_status, create_time FROM orders WHERE user_id = ?
-- 使用覆盖索引
ALTER TABLE orders ADD INDEX idx_user_cover (user_id, order_status, create_time);
2.3 函数索引
TiDB 支持函数索引(从 v6.2 起),可以针对函数表达式创建索引:
-- 查询:SELECT * FROM orders WHERE LOWER(email) = 'test@example.com'
CREATE INDEX idx_email_lower ON orders ((LOWER(email)));
2.4 索引使用情况诊断
通过以下 SQL 检查索引是否被有效使用:
-- 查看表的索引使用统计
SELECT * FROM information_schema.statements_summary
WHERE digest LIKE '%orders%' AND plan_type = 'optimized'
ORDER BY sum_latency DESC LIMIT 20;
-- 检查冗余索引
SELECT index_name, column_name, seq_in_index
FROM information_schema.statistics
WHERE table_name = 'orders' ORDER BY index_name, seq_in_index;
三、TiDB 并行查询优化
3.1 MPP 模式加速分析查询
TiDB 的 MPP(大规模并行处理)模式通过 TiFlash 节点将分析查询下推到多节点并行执行:
-- 启用 MPP 模式(默认在集群配置中设置)
SET tidb_enforce_mpp = ON;
-- 适用于大表聚合、多表 JOIN、子查询等分析型查询
SELECT d.department_name, SUM(o.amount) AS total_amount, COUNT(*) AS order_count
FROM orders o
JOIN departments d ON o.dept_id = d.id
WHERE o.create_time BETWEEN '2025-01-01' AND '2025-12-31'
GROUP BY d.department_name
ORDER BY total_amount DESC;
MPP 模式下,TiFlash 节点会并行扫描分片数据、本地聚合、跨节点 Shuffle 后完成最终结果汇总。
3.2 Hash Join 优化关联查询
对于大表与大表的等值 JOIN,TiDB 优先选择 Hash Join:
-- 确保优化器选择 Hash Join
SET tidb_opt_join_hash_build_side = 'left';
-- 大表 JOIN 示例
SELECT u.user_id, u.name, o.order_id, o.amount
FROM users u
JOIN orders o ON u.user_id = o.user_id
WHERE o.create_time > '2025-01-01';
Hash Join 工作原理:将驱动表(Build 侧)数据加载到内存构建 Hash 表,然后扫描被驱动表(Probe 侧)进行匹配查找。当 Build 侧数据超出内存时,TiDB 支持分区 Hash Join,自动溢出到磁盘。
3.3 Index Join 优化带索引的关联查询
当关联条件有一侧有索引时,TiDB 可使用 Index Join:
-- TiDB 自动选择或手动 Hint 指定 Index Join
SELECT /*+ INL_JOIN(o) */ u.user_id, u.name, o.order_id
FROM users u
JOIN orders o ON u.user_id = o.user_id
WHERE u.region = '华东';
3.4 并行度控制
TiDB 通过以下参数控制查询并行度:
-- 设置最大并行执行算子数
SET tidb_max_executor_group_size = 9999;
-- 设置 Coprocessor 并行度
SET tidb_executor_concurrency = 8;
-- 大表扫描并行度
SET tidb_distsql_scan_concurrency = 15;
四、执行计划分析与调优
4.1 EXPLAIN ANALYZE 使用
TiDB 支持 `EXPLAIN ANALYZE` 实际执行查询并返回详细执行信息:
EXPLAIN ANALYZE
SELECT u.name, SUM(o.amount)
FROM users u
JOIN orders o ON u.user_id = o.user_id
WHERE o.create_time > '2025-01-01'
GROUP BY u.name;
重点关注以下指标:
| 指标 | 说明 | 优化方向 |
|---|---|---|
| time | 算子实际执行时间 | 定位耗时最长的算子 |
| rows | 实际处理行数 vs 估算行数 | 如果偏差大,需更新统计信息 |
| task | 执行模式(cop/tiflash/root) | 确认是否下推到存储层 |
| memory | 内存消耗 | 关注是否溢出到磁盘 |
4.2 统计信息更新
统计信息过期是执行计划错误的常见原因:
-- 手动更新全表统计信息
ANALYZE TABLE orders;
-- 手动更新指定列统计信息
ANALYZE TABLE orders COLUMNS user_id, order_status;
-- 设置自动统计信息收集
SET GLOBAL tidb_auto_analyze_ratio = 0.5;
SET GLOBAL tidb_auto_analyze_start_time = '00:00 +0000';
4.3 SQL Binding 固化执行计划
对于关键业务 SQL,可以使用 SQL Binding 固化最优执行计划:
-- 创建 SQL Binding
CREATE BINDING FOR
SELECT * FROM orders WHERE user_id = 1 AND order_status = 'PAID'
USING
SELECT /*+ USE_INDEX(orders, idx_user_status) */ * FROM orders WHERE user_id = 1 AND order_status = 'PAID';
-- 查看已有的 Bindings
SHOW BINDINGS;
五、TiFlash 加速分析查询
5.1 TiFlash 架构概述
TiFlash 是 TiDB 的列式存储引擎,通过 Raft Learner 角色自动同步 TiKV 的数据,提供列式存储加速分析查询:
TiDB Server → SQL 解析优化 → TiKV(行存,事务)+ TiFlash(列存,分析)
↑
Raft 复制
5.2 合理设置副本
为需要加速分析的表创建 TiFlash 副本:
-- 为 orders 表创建 2 个 TiFlash 副本
ALTER TABLE orders SET TIFLASH REPLICA 2;
-- 查看副本同步状态
SELECT * FROM information_schema.tiflash_replica;
5.3 优化器 Hint 引导查询到 TiFlash
-- 强制使用 TiFlash
SELECT /*+ READ_FROM_STORAGE(TIFLASH[orders]) */
order_status, COUNT(*) AS cnt, SUM(amount) AS total
FROM orders
GROUP BY order_status;
FAQ
Q1:添加索引会锁表吗?
TiDB 的 `ADD INDEX` 操作采用 Online DDL 方式,不会阻塞读写操作。索引在后台异步构建,构建完成后自动生效。但对于超大表(亿级),后台构建过程可能消耗较长时间和额外资源,建议在业务低峰期执行。
Q2:MPP 模式在什么条件下自动触发?
当 TiDB 检测到查询涉及大表且符合 MPP 优化条件时自动触发,包括:查询涉及 TiFlash 表、估算数据量超过阈值、JOIN/聚合/子查询可并行化等。也可通过 `SET tidb_enforce_mpp = ON` 强制启用。
Q3:统计信息多久需要更新一次?
TiDB 默认开启自动统计信息收集,当表中数据变更量超过 `tidb_auto_analyze_ratio`(默认 0.5,即 50%)时自动触发。对于写入频繁的大表,建议适当降低该阈值至 0.1-0.3,或设置定时任务在低峰期手动执行 `ANALYZE`。
Q4:EXPLAIN ANALYZE 执行时间过长怎么办?
`EXPLAIN ANALYZE` 会实际执行查询。如果查询本身很慢,建议先用 `EXPLAIN` 查看预估执行计划,定位问题后再用 `EXPLAIN ANALYZE` 验证。如果查询必须执行,可以先用 `LIMIT` 限制返回行数来缩短分析时间。
总结
大表查询优化是一个系统工程,核心思路是"减少扫描数据量 + 提高并行处理能力"。索引优化从查询维度降低数据扫描量,TiDB 的并行查询(MPP、Hash Join、Index Join)从执行维度提高处理效率,TiFlash 列式存储加速分析型查询,EXPLAIN ANALYZE 提供精确的诊断依据。建议按照"诊断 → 索引优化 → 执行计划调优 → 并行加速"的顺序逐步推进。
下一步行动
- 试用 TiDB:在 TiDB Cloud 免费试用 上创建含 TiFlash 的集群,体验并行查询和 HTAP 加速
- 下载 TiDB 调优指南:访问 TiDB 性能调优文档 获取完整的调优方法论
- 加入 AskTUG 社区:访问 asktug.com 查询慢查询调优经验帖和专家解答