TiDB 性能调优:先查 SQL 层,再动参数
TiDB 调优的经验法则是 80% 的性能问题出在 SQL 层,只有 20% 需要调系统参数。下面按这个取舍组织排查顺序:先 SQL,再 TiDB/TiKV 实例配置,最后操作系统与集群侧参数。
一、性能调优方法论
1.1 调优的基本原则
调优不是一上来就改参数,而是遵循以下步骤:
- 建立基线 → 压测当前配置,记录性能指标
- 识别瓶颈 → 找到系统的短板(CPU/内存/磁盘/网络/SQL)
- 制定方案 → 针对性地优化
- 验证效果 → 再次压测,对比指标
- 持续迭代 → 调优是一个持续过程
1.2 性能瓶颈的层次
应用层
├── SQL 质量 (影响最大)
│ ├── 索引是否合理
│ ├── JOIN 方式是否正确
│ └── 是否有不必要的全表扫描
数据库层
├── TiDB 配置(内存限制、并发度、事务模式)
├── TiKV 配置(Block Cache 大小、线程池大小、Raft Store 配置)
└── PD 配置(调度频率、TSO 批量大小)
系统层
├── CPU、内存、磁盘 I/O (最关键)、网络
二、SQL 层调优
2.1 索引调优
添加缺失索引:
-- 查看执行计划,找到全表扫描
EXPLAIN SELECT * FROM orders WHERE user_id = 12345;
-- 如果是 TableFullScan,添加索引
CREATE INDEX idx_user_id ON orders(user_id);
删除未使用索引:
-- TiDB 6.0+ 可以查看所有索引列表
SELECT TABLE_SCHEMA, TABLE_NAME, KEY_NAME, -- 注意: 是 KEY_NAME 不是 INDEX_NAME
COLUMN_NAME
FROM information_schema.tidb_indexes
WHERE TABLE_SCHEMA = 'your_db';
-- 结合 information_schema.statements_summary 或慢查询日志判断索引是否被使用
-- 如果某索引在 EXPLAIN 中从未被选中,考虑删除
DROP INDEX idx_unused ON orders;
复合索引设计:
-- 场景: 经常按 (status, created_at) 查询,且经常按 created_at 排序
SELECT * FROM orders WHERE status = 1 ORDER BY created_at DESC LIMIT 20;
-- 设计索引时,等值条件列在前,排序列在后
CREATE INDEX idx_status_created ON orders(status, created_at);
-- 这样既可以用索引过滤 status,又可以用索引避免排序
2.2 JOIN 调优
选择合适的 Join 类型:
| Join 类型 | 适用场景 |
|---|---|
| HashJoin | 两表都无合适索引,数据量中等 |
| IndexJoin | 内表有索引,外表数据量小 |
| MergeJoin | 两边都有序(有索引) |
-- 强制使用 IndexJoin(当优化器选择不当时)
SELECT /*+ INL_JOIN(orders) */ *
FROM users JOIN orders ON users.id = orders.user_id
WHERE users.id = 123;
-- 强制使用 HashJoin
SELECT /*+ HASH_JOIN(users, orders) */ *
FROM users JOIN orders ON users.id = orders.user_id
WHERE users.status = 1;
控制 Join 的驱动表,让小表驱动大表。TiDB 优化器通常会自动选择,也可以用 Hint 强制:
SELECT /*+ TIDB_INLJ(small_table) */ *
FROM small_table JOIN big_table ON small_table.id = big_table.small_id;
2.3 子查询优化
-- 差的相关子查询(每行都执行一次子查询)
SELECT name FROM users WHERE id IN (SELECT user_id FROM orders WHERE amount > 1000);
-- 好的方式(改写为 JOIN)
SELECT DISTINCT u.name
FROM users u JOIN orders o ON u.id = o.user_id
WHERE o.amount > 1000;
三、TiDB Server 调优
3.1 内存调优
-- 单查询内存配额(默认 1GB)
SET GLOBAL tidb_mem_quota_query = 1073741824;
-- 如果查询经常 OOM,降低该值
SET GLOBAL tidb_mem_quota_query = 536870912; -- 512MB
-- 如果查询被误杀(OOM 但实际不需要那么多内存),提高该值
SET GLOBAL tidb_mem_quota_query = 2147483648; -- 2GB
TiDB Server 内存使用构成:
- SQL 执行内存(受
tidb_mem_quota_query控制) - 连接内存(每个连接约 10MB)
- 元数据缓存(表结构、统计信息)
- 其他内部结构
控制连接内存:max_connections 默认 0(不限制),生产环境建议 5000–10000。
3.2 并发度调优
-- 控制 SQL 层并发度
SET GLOBAL tidb_distsql_scan_concurrency = 15; -- 默认 15
-- 增大该值可以提升扫描并发,但也会增加 TiKV 压力
-- 控制 Index Lookup Join 并发度
SET GLOBAL tidb_index_lookup_join_concurrency = 4; -- 默认 4
3.3 执行计划缓存
-- 开启 Prepared Plan Cache
SET GLOBAL tidb_prepared_plan_cache_size = 100; -- 缓存 100 个计划
-- 开启 Non-Prepared Plan Cache(TiDB 7.0+)
SET GLOBAL tidb_enable_non_prepared_plan_cache = ON;
SET GLOBAL tidb_non_prepared_plan_cache_size = 100;
执行计划缓存适合参数化查询(WHERE id = ? 形式)、相同结构不同参数值的 SQL。
四、TiKV 调优
4.1 Block Cache 调优(TiKV 最重要的缓存机制)
通过 TiUP 修改配置:
tiup cluster edit-config tidb-cluster
tikv_servers:
- host: 10.0.1.21
config:
storage.block-cache.capacity: "16GB"
Block Cache 大小建议:
| TiKV 内存 | Block Cache |
|---|---|
| 16GB | 8GB |
| 32GB | 16GB |
| 64GB | 32GB |
| 128GB | 48GB |
原则:Block Cache 约占 TiKV 可用内存的 45%–50%。
4.2 线程池调优
tikv_servers:
- host: 10.0.1.21
config:
server.grpc-concurrency: 8 # gRPC 并发线程数(默认 8)
storage.scheduler-worker-pool-size: 4 # 调度线程池大小(默认 4)
readpool.coprocessor.normal-concurrency: 8 # 协处理器线程池(默认 8)
raftstore.store-pool-size: 2
raftstore.apply-pool-size: 2
调优建议:
| CPU 核数 | grpc-concurrency | coprocessor | scheduler |
|---|---|---|---|
| 8 核 | 6 | 6 | 3 |
| 16 核 | 8 | 8 | 4 |
| 32 核+ | 12 | 12 | 6 |
4.3 Raft KV 引擎优化
storage.engine: "raft-kv" 为默认值,基于 RocksDB;TiDB 8.0+ 支持分区 Raft KV 引擎,适用于高并发场景,可减少写入放大。
rocksdb.max-open-files: 1024
rocksdb.max-background-jobs: 8
引擎选择建议:
- 通用场景:raft-kv(平衡读写)
- 高并发写入:raft-kv + 增大后台线程
- 读密集:raft-kv + 增大 Block Cache
RocksDB 调优:
rocksdb.defaultcf.block-cache-size: "8GB"
rocksdb.writecf.block-cache-size: "2GB"
rocksdb.lockcf.block-cache-size: "512MB"
五、操作系统层调优
5.1 CPU 调度
echo performance > /sys/devices/system/cpu/cpu*/cpufreq/scaling_governor
# 绑定 TiKV 进程到特定 CPU 核心(减少 Cache Miss)
taskset -c 0-15 tikv-server ...
5.2 磁盘 I/O 调优
cat /sys/block/nvme0n1/queue/scheduler
echo none > /sys/block/nvme0n1/queue/scheduler # NVMe SSD 推荐
echo 1024 > /sys/block/nvme0n1/queue/nr_requests
blockdev --getra /dev/nvme0n1
blockdev --setra 256 /dev/nvme0n1 # 256 * 512 = 128KB
5.3 网络调优
sysctl -w net.core.rmem_max=16777216
sysctl -w net.core.wmem_max=16777216
sysctl -w net.ipv4.tcp_rmem="4096 87380 16777216"
sysctl -w net.ipv4.tcp_wmem="4096 65536 16777216"
sysctl -w net.ipv4.tcp_fastopen=3
sysctl -w net.netfilter.nf_conntrack_max=1048576
5.4 文件系统调优
# ext4 推荐选项(/etc/fstab)
# /dev/nvme0n1 /tidb-data ext4 defaults,noatime,nodiratime,discard 0 2
mount | grep tidb-data
cat /sys/block/nvme0n1/alignment_offset # 应该为 0
六、垃圾回收(GC)调优
6.1 GC 机制简述
TiDB 使用 MVCC,旧版本数据不会被立即删除,而是由 GC 定期清理。safe_point 之后最老的版本被保留。
6.2 GC 配置
SELECT * FROM mysql.tidb WHERE variable_name LIKE 'tikv_gc%';
-- 设置 GC 间隔(默认 10 分钟)
UPDATE mysql.tidb SET variable_value = '10m' WHERE variable_name = 'tikv_gc_run_interval';
-- 设置数据保留时间(默认 168 小时 = 7 天)
UPDATE mysql.tidb SET variable_value = '24h' WHERE variable_name = 'tikv_gc_life_time';
6.3 GC 调优建议
- 存储压力大 → 缩短
tikv_gc_life_time(如 24h) - 长事务多 → 延长(如 72h)
- 频繁 Stale Read → 延长
- 写入量大 → 缩短
tikv_gc_run_interval(如 5m)
七、Region 调优
7.1 Region 大小
默认 Region 96MB。注意 SHOW CONFIG 不支持 LIMIT,需要用 head 过滤输出。
SHOW CONFIG WHERE name LIKE '%split%';
7.2 预分裂 Region
对即将导入大量数据的表:
CREATE TABLE big_table (id BIGINT PRIMARY KEY, data VARCHAR(255)) PRE_SPLIT_REGIONS = 16;
SPLIT TABLE big_table BETWEEN (0) AND (1000000000) REGIONS 16;
SPLIT TABLE big_table INDEX idx_name BETWEEN ('A') AND ('Z') REGIONS 26;
好处:避免写入集中在一个 Region(热点),导入时可直接写入多个 Region。
7.3 Region 调度
SHOW CONFIG WHERE type = 'pd' AND name LIKE '%schedule%';
# 调整 Region 迁移速度(默认较慢)
curl -X POST http://10.0.1.11:2379/pd/api/v1/config \
-d '{"schedule.region-schedule-limit": 2048}'
# 加快 Leader 迁移
curl -X POST http://10.0.1.11:2379/pd/api/v1/config \
-d '{"schedule.leader-schedule-limit": 8}'
八、负载调优(Resource Control)
8.1 资源组
CREATE RESOURCE GROUP rg_critical RU_PER_SEC = 10000;
CREATE RESOURCE GROUP rg_normal RU_PER_SEC = 5000;
CREATE RESOURCE GROUP rg_low RU_PER_SEC = 1000;
CREATE USER 'analyst'@'%';
ALTER USER 'analyst'@'%' RESOURCE GROUP rg_low;
SELECT /*+ RESOURCE_GROUP(rg_critical) */ * FROM critical_table;
8.2 Runaway Queries 管控
在资源组上配置 runaway 规则,自动终止消耗资源过多的查询。具体语法因 TiDB 版本而异,请参考官方文档。
九、性能压测
9.1 Sysbench
yum install sysbench -y
sysbench oltp_read_write \
--mysql-host=10.0.1.14 --mysql-port=4000 --mysql-user=root --mysql-db=sbtest \
--tables=10 --table-size=1000000 prepare
sysbench oltp_read_write \
--mysql-host=10.0.1.14 --mysql-port=4000 --mysql-user=root --mysql-db=sbtest \
--threads=32 --time=300 --report-interval=10 run
9.2 TPC-C(更贴近真实业务场景)
go-tpc tpcc prepare --host 10.0.1.14 --port 4000 --warehouses 100
go-tpc tpcc run --host 10.0.1.14 --port 4000 --warehouses 100 --threads 32
工具地址:
十、调优检查清单
部署后必做
- 确认操作系统参数已优化(Swap、THP、文件描述符)
- 确认磁盘对齐和 I/O 调度器正确
- 设置合理的 Block Cache 大小
- 开启慢查询日志
- 配置告警规则
日常调优
- 定期
ANALYZE TABLE更新统计信息 - 检查未使用的索引
- 分析慢查询,优化执行计划
- 监控 Region 分布,处理热点
- 检查 GC 配置是否合理
压测后调优
- 对比压测结果与预期目标
- 根据瓶颈调整配置
- 重新压测验证效果
- 记录调优前后的对比数据
补充几点实战经验:
- 先用
EXPLAIN ANALYZE看实际执行耗时,比单纯EXPLAIN更准。 - 发现 TableFullScan 但加索引没效果,可能是数据分布问题,试试
ANALYZE TABLE更新统计信息。 - TiKV 调优可优先调 block-cache 大小,默认约总内存 45%,业务高并发时可调到 60%–70%。