0
0
0
0
博客/.../

TiDB SQL 调优实战:从”跑不动”到”飞起来”的三个真实案例

 Marvelyu  发表于  2026-09-04

本文主要通过总结TiDB在实践中的SQL调优三个实践案例,引导大家在实践中如何优化业务应用,性能优化,感受让tidb如何“”飞起来“”的真实案例。

案例一:统计信息过期,索引”视而不见”

1.1 问题现象

某电商订单表 orders 约 8 亿行,业务反馈一个简单的查询从 200ms 暴涨到 40 秒

SELECT order_id, amount, statusFROM ordersWHERE user_id = 123456   AND create_time > '2024-01-01'ORDER BY create_time DESC LIMIT 20;

表结构:

CREATE TABLE orders (    order_id BIGINT PRIMARY KEY,    user_id BIGINT,    create_time DATETIME,    amount DECIMAL(10,2),    status TINYINT,    INDEX idx_user_time (user_id, create_time)  -- 联合索引);

1.2 诊断过程

执行 EXPLAIN ANALYZE:

EXPLAIN ANALYZE SELECT order_id, amount, statusFROM ordersWHERE user_id = 123456   AND create_time > '2024-01-01'ORDER BY create_time DESC LIMIT 20;

优化前执行计划:

2ab00618-ad1c-46e1-bb2a-969267e91d7e.png

关键异常分析:

指标

优化前值

说明

IndexRangeScan estRows

100,000

估计扫描 10 万行

IndexRangeScan actRows

100,000

实际扫描 10 万行

Selection 后 actRows

20

过滤后只剩 20 行

问题本质

create_time 条件在索引中,但优化器未用它做范围裁剪

检查统计信息健康度:

SHOW STATS_HEALTHY WHERE table_name = 'orders';

-- 结果:healthy = 25  (远低于 80 的及格线)

根因判定: ANALYZE 已长时间未执行,优化器误判 create_time 筛选率,选择先扫描整个 user_id 再回表过滤。

1.3 优化方案

步骤 1:立即恢复统计信息

ANALYZE TABLE orders;

步骤 2:验证执行计划

重新执行 EXPLAIN ANALYZE:

9208aeb6-41e1-48cb-aa83-cc04bd9c860c.png

关键改进: IndexRangeScan 的 range 变为 [123456 2024-01-01, 123456 +inf],联合索引两字段均参与范围裁剪。

步骤 3:配置自动收集策略(防复发)

-- 设置自动 ANALYZE 阈值SET GLOBAL tidb_auto_analyze_ratio = 0.5;        -- 修改行数超过 50% 时触发SET GLOBAL tidb_auto_analyze_start_time = '00:00 +0800';SET GLOBAL tidb_auto_analyze_end_time = '06:00 +0800';

1.4 效果验证

指标

优化前

优化后

提升倍数

查询耗时

40s

180ms

222x

索引扫描行数

100,000

20

5,000x

Coprocessor 请求量

大量

极少

案例二:时间维度分区表 + 分区裁剪失效

2.1 问题现象

日志表 app_logs 按天分区,30 天约 15 亿行。查询最近 3 天的 ERROR 日志:

SELECT * FROM app_logsWHERE log_level = 'ERROR'   AND log_time > NOW() - INTERVAL 3 DAY;

耗时 12 秒,分区表的优势完全没有体现。

2.2 诊断过程

EXPLAIN SELECT * FROM app_logs WHERE log_level = 'ERROR'   AND log_time > NOW() - INTERVAL 3 DAY;

优化前执行计划:

05df04b6-79d5-488d-89a9-82b9b1c1d188.png

根因判定: PartitionUnion 显示 partition:all,分区裁剪完全失效。问题出在 NOW() - INTERVAL 3 DAY —— TiDB 优化器无法对包含非常量函数的表达式进行分区裁剪。

2.3 优化方案

改写 SQL,使用确定的常量值:

-- 方式一:

应用层传入确定时间SELECT * FROM app_logsWHERE log_level = 'ERROR'   AND log_time > '2024-09-01 14:22:00';  

-- 确定的时间字符串--

方式二:

先计算边界值再查询SET @three_days_ago = DATE_FORMAT(NOW() - INTERVAL 3 DAY, '%Y-%m-%d %H:%i:%s');

-- 然后在应用层将 @three_days_ago 作为参数传入

优化后执行计划:

6cccfc4f-11df-4dc7-b491-9ec41a152873.png

进阶优化(高筛选率场景):

若 log_level = 'ERROR' 筛选率很高(如仅占 1%),增加局部索引:

ALTER TABLE app_logs ADD INDEX idx_level_time (log_level, log_time);

执行计划将变为 IndexLookUp,减少回表数据量。

2.4 效果验证

指标

优化前

优化后

提升倍数

扫描分区数

30 个

3 个

10x

查询耗时

12s

350ms

34x

扫描行数

15 亿

约 5000 万

30x

案例三:写热点 —— 自增主键的”死亡陷阱”

3.1 问题现象

物联网设备上报数据,峰值 10 万 QPS,表结构:

CREATE TABLE device_data (    id BIGINT AUTO_INCREMENT PRIMARY KEY,  -- 自增主键    device_id VARCHAR(32),    data_value DOUBLE,    report_time TIMESTAMP);

TiDB Dashboard 显示 某个 Region 写入流量是其他 Region 的 100 倍,TiKV 节点负载极不均衡,写入延迟飙升,部分请求超时。

3.2 诊断过程

检查表 Region 分布:

SHOW TABLE device_data REGIONS;

TiDB Dashboard Key Visualizer 观察: 

- 现象:一条明显的”亮线”集中在 Key Range 最大值端

- 结论:所有新写入集中在最后一个 Region

根因判定: - TiDB 中主键默认是聚簇索引(Clustered Index)

 - AUTO_INCREMENT 导致新数据 id 永远递增到最大值

- 写入全部集中在最后一个 Region,形成写热点

3.3 优化方案

方案 A:使用 AUTO_RANDOM(官方推荐)

-- 重建表,将自增改为随机分布CREATE TABLE device_data (    id BIGINT AUTO_RANDOM(6) PRIMARY KEY,  -- 6 位 shard 位    device_id VARCHAR(32),    data_value DOUBLE,    report_time TIMESTAMP);

原理: AUTO_RANDOM 在 id 高位插入随机 shard 位,将写入均匀分散到不同 Region。

方案 B:使用联合主键(业务允许时)

CREATE TABLE device_data (    device_id VARCHAR(32),    report_time TIMESTAMP,    data_value DOUBLE,    PRIMARY KEY (device_id, report_time)  -- 按业务维度自然分散) SHARD_ROW_ID_BITS = 4   PRE_SPLIT_REGIONS = 4;

参数说明: - SHARD_ROW_ID_BITS = 4:对非聚簇表打散 _tidb_rowid,产生 16 个分片 - PRE_SPLIT_REGIONS = 4:建表时预分裂 16 个 Region

方案 C:应用层缓冲(必须保留自增时)

若业务强依赖自增 ID(如与外部系统对接),在应用层引入缓冲: 1. 数据先写入 Redis / Kafka 2. 消费者多线程、批量写入 TiDB 3. 降低瞬时并发压力

3.4 效果验证

使用 AUTO_RANDOM 后,通过 TiDB Dashboard → Key Visualizer 观察:

观察维度

优化前

优化后

热力图

一条”亮线”(热点)

均匀分布的”热区”

写入 QPS

10 万(剧烈抖动)

稳定 10 万

P99 写入延迟

500ms

15ms

TiKV 负载均衡

极差

均衡

调优方法论总结

4.1 问题分类与诊断工具

问题类型

首选诊断工具

核心关注指标

执行计划异常

EXPLAIN ANALYZE

estRows vs actRows 偏差、operator 类型

统计信息失效

SHOW STATS_HEALTHY

healthy 值(< 80 需关注)

分区未裁剪

EXPLAIN 查看 PartitionUnion

partition 列是否显示 all

写热点

TiDB Dashboard Key Visualizer

Region 流量分布是否均匀

慢查询分析

INFORMATION_SCHEMA.SLOW_QUERY

Query_time、Cop_time、Process_keys

索引使用

EXPLAIN 查看 access object

是否使用了预期索引

4.2 三条铁律

1. 永远先看执行计划EXPLAIN ANALYZE 是 TiDB SQL 调优的”听诊器”。

重点关注 estRows 和 actRows 的偏差,若偏差超过 10 倍,优先检查统计信息。

2. 统计信息是第一生产力ANALYZE 不是可选项。

生产环境务必配置自动收集策略,配合 tidb_auto_analyze_ratio 和 tidb_auto_analyze_start_time 实现无人值守。

3. 热点比慢查询更致命慢查询影响单个业务,写热点可能拖垮整个集群。

表结构设计阶段就必须考虑 Key 的分布,避免 AUTO_INCREMENT 聚簇索引的陷阱。

4.3 调优决策树

40cdfacf-6689-4237-8afb-63f4f83dfe2d.png

附录:常用诊断命令速查

A.1 统计信息相关

-- 查看表统计信息健康度

SHOW STATS_HEALTHY WHERE table_name = 'your_table';-

- 手动收集统计信息ANALYZE TABLE your_table;

-- 查看列统计信息详情

SHOW STATS_HISTOGRAMS WHERE table_name = 'your_table';

-- 查看自动收集配置SHOW VARIABLES LIKE 'tidb_auto_analyze%';

A.2 执行计划相关

-- 查看执行计划(含实际执行数据)

EXPLAIN ANALYZE SELECT ...;-

- 查看绑定执行计划(SQL Binding)SHOW BINDINGS;

-- 创建执行计划绑定(固定走某索引)

CREATE GLOBAL BINDING FOR   SELECT * FROM  t WHERE a = 1 USING   SELECT * FROM t USE INDEX(idx_a) WHERE a = 1;

A.3 热点诊断相关

-- 查看表 Region 分布

SHOW TABLE your_table REGIONS;

-- 查看 Region 热点(TiDB 内置)SELECT * FROM information_schema.TIKV_REGION_STATUS WHERE table_name = 'your_table' ORDER BY written_bytes DESC LIMIT 10;

-- 查看 Store 负载分布SELECT * FROM information_schema.TIKV_STORE_STATUS;

A.4 慢查询分析

-- 查询最近慢 SQLSELECT * FROM information_schema.SLOW_QUERY WHERE query_time > 1 ORDER BY query_time DESC LIMIT 10;

-- 关注字段-- query_time: 总耗时

-- parse_time: 解析耗时-

- compile_time: 优化器生成计划耗时-

- cop_time: TiKV 执行耗时

-- process_keys: 处理的 key 数量

-- total_keys: 扫描的 key 数量(含旧版本)

A.5 系统参数参考

-- 查看当前会话/全局变量

SHOW [GLOBAL] VARIABLES LIKE 'tidb_%';-- 常用调优参数

SET GLOBAL tidb_auto_analyze_ratio = 0.5;

SET GLOBAL tidb_auto_analyze_start_time = '00:00 +0800';

SET GLOBAL tidb_auto_analyze_end_time = '06:00 +0800';

SET GLOBAL tidb_enable_pseudo_for_outdated_stats = OFF;  -- 过期统计信息不使用伪统计

0
0
0
0

版权声明:本文为 TiDB 社区用户原创文章,遵循 CC BY-NC-SA 4.0 版权协议,转载请附上原文出处链接和本声明。

评论
暂无评论