1. 首页 > 国产数据库教程 > TiDB教程 > 正文

tidb教程FG083-TiDB慢SQL导致性能下降处理

本文档风哥主要介绍TiDB慢SQL导致性能下降的处理方法,包括慢SQL相关概念、慢SQL的原因、影响、相关参数、预防措施、处理步骤、优化方法、实战案例和最佳实践等,风哥教程参考TiDB官方文档性能优化相关内容编写,适合DBA人员在学习和测试中使用,如果要应用于生产环境则需要自行确认。更多视频教程www.fgedu.net.cn

Part01-基础概念与理论知识

1.1 慢SQL相关概念

TiDB慢SQL相关的基本概念:

# 慢SQL相关概念

## 1. 慢SQL定义
– **慢SQL**:执行时间超过预设阈值的SQL语句
– **慢查询日志**:记录慢SQL的日志文件
– **执行计划**:SQL语句的执行路径和方式
– **查询成本**:执行SQL语句所需的资源和时间

## 2. 慢SQL类型
– **全表扫描**:没有使用索引,扫描整个表的SQL
– **复杂查询**:包含多个表连接、子查询的SQL
– **大结果集**:返回大量数据的SQL
– **排序操作**:需要大量排序的SQL
– **聚合操作**:需要大量聚合计算的SQL

## 3. 执行计划相关概念
– **索引扫描**:使用索引进行数据检索
– **全表扫描**:不使用索引,扫描整个表
– **表连接**:多个表之间的连接操作
– **嵌套循环连接**:通过嵌套循环实现表连接
– **哈希连接**:通过哈希表实现表连接
– **排序合并连接**:通过排序和合并实现表连接

## 4. 性能指标
– **执行时间**:SQL语句的执行时间
– **扫描行数**:SQL语句扫描的数据行数
– **返回行数**:SQL语句返回的数据行数
– **CPU使用率**:执行SQL语句消耗的CPU资源
– **内存使用率**:执行SQL语句消耗的内存资源风哥提示:
– **I/O操作**:执行SQL语句的I/O操作次数

1.2 慢SQL的原因

TiDB慢SQL的常见原因:

# 慢SQL的原因

## 1. 索引问题
– **缺少索引**:查询条件没有对应的索引
– **索引失效**:索引存在但未被使用
– **索引选择错误**:优化器选择了不合适的索引
– **索引覆盖不足**:索引不能覆盖所有查询列

## 2. SQL语句问题
– **全表扫描**:没有使用索引,扫描整个表
– **复杂查询**:包含多个表连接、子查询
– **大结果集**:返回大量数据
– **排序操作**:需要大量排序
– **聚合操作**:需要大量聚合计算
– **重复查询**:相同的查询重复执行

## 3. 数据分布问题
– **数据倾斜**:数据分布不均匀,某些分区数据量过大
– **热点数据**:大量查询集中在少数数据上
– **数据量过大**:表数据量过大,查询效率低下

## 4. 数据库配置问题
– **参数配置不合理**:数据库参数配置不当
– **内存不足**:数据库内存不足,导致性能下降
– **并发度过高**:并发查询过多,导致资源竞争

## 5. 系统环境问题
– **磁盘I/O瓶颈**:磁盘I/O速度慢
– **网络延迟**:网络传输速度慢
– **CPU负载高**:系统CPU负载过高
– **内存不足**:系统内存不足

## 6. 应用程序问题
– **频繁查询**:应用程序频繁执行相同的查询
– **长事务**:长事务占用资源时间过长
– **连接池配置不合理**:连接池配置不当,导致连接竞争
– **业务逻辑问题**:业务逻辑设计不合理,导致大量查询

1.3 慢SQL的影响

TiDB慢SQL会产生以下影响:

慢SQL的影响:

  • 性能下降:慢SQL会消耗大量系统资源,导致数据库性能下降
  • 响应缓慢:慢SQL会导致应用程序响应缓慢,影响用户体验
  • 资源耗尽:慢SQL会消耗大量CPU、内存和I/O资源,可能导致系统崩溃
  • 并发阻塞:慢SQL会占用数据库连接,导致其他查询被阻塞
  • 数据一致性问题:慢SQL可能导致事务长时间占用锁,影响数据一致性
  • 监控告警:慢SQL会触发监控告警,增加运维负担
  • 业务中断:严重的慢SQL可能导致业务中断,影响业务运行

1.4 慢SQL相关参数

TiDB慢SQL相关的配置参数:

# 慢SQL相关参数

## 1. 慢查询日志参数
– **slow-threshold**:慢SQL阈值,默认值为300ms
“`toml
# 在tidb.toml中配置
slow-threshold = 300
“`

– **log-slow-queries**:是否启用慢查询日志,默认值为true
“`toml
# 在tidb.toml中配置
log-slow-queries = true
“`

– **slow-query-file**:慢查询日志文件路径
“`toml
# 在tidb.toml中配置
slow-query-file = “/tidb/log/tidb-slow.log”
“`

– **record-slow-admin**:是否记录管理语句的慢查询,默认值为false
“`toml
# 在tidb.toml中配置
record-slow-admin = false
“`

– **record-slow-query**:是否记录慢查询,默认值为true
“`toml
# 在tidb.toml中配置
record-slow-query = true
“`

## 2. 执行计划相关参数学习交流加群风哥QQ113257174
– **tidb_opt_agg_push_down**:是否启用聚合下推,默认值为true
“`toml
# 在tidb.toml中配置
tidb_opt_agg_push_down = true
“`

– **tidb_opt_distinct_agg_push_down**:是否启用去重聚合下推,默认值为true
“`toml
# 在tidb.toml中配置
tidb_opt_distinct_agg_push_down = true
“`

– **tidb_opt_limit_push_down**:是否启用Limit下推,默认值为true
“`toml
# 在tidb.toml中配置
tidb_opt_limit_push_down = true
“`

– **tidb_opt_scan_factor**:全表扫描的代价因子,默认值为10
“`toml
# 在tidb.toml中配置
tidb_opt_scan_factor = 10
“`

## 3. 内存相关参数
– **tmp-storage-size**:临时存储大小,默认值为-1(无限制)
“`toml
# 在tidb.toml中配置
tmp-storage-size = -1
“`

– **oom-action**:OOM时的操作,默认值为”cancel”
“`toml
# 在tidb.toml中配置
oom-action = “cancel”
“`

## 4. 并发相关参数
– **max-concurrent-queries**:最大并发查询数,默认值为1024
“`toml
# 在tidb.toml中配置
max-concurrent-queries = 1024
“`

– **stmt-count-limit**:单个语句的最大执行时间(秒),默认值为0(无限制)
“`toml
# 在tidb.toml中配置
stmt-count-limit = 0
“`

风哥提示:慢SQL是数据库性能问题的常见原因,需要及时发现并处理,避免影响业务运行。学习交流加群风哥微信: itpux-com

Part02-生产环境规划与建议

2.1 预防措施

为了避免TiDB慢SQL导致性能下降,生产环境中应采取以下预防措施:

# 预防措施

## 1. 索引设计
– **合理创建索引**:根据查询条件创建合适的索引
– **覆盖索引**:创建覆盖查询列的索引,减少回表操作
– **复合索引**:根据查询条件的组合创建复合索引
– **索引维护**:定期检查和优化索引,删除冗余索引

## 2. SQL优化
– **避免全表扫描**:确保查询条件使用索引
– **优化复杂查询**:简化复杂查询,避免多层嵌套
– **限制结果集**:使用LIMIT限制返回数据量
– **避免排序操作**:减少ORDER BY操作,或使用索引排序
– **避免聚合操作**:减少GROUP BY操作,或使用索引加速

## 3. 数据库配置
– **合理设置参数**:根据服务器资源和业务需求设置合理的参数
– **监控慢查询**:启用慢查询日志,及时发现慢SQL
– **优化执行计划**:分析执行计划,优化SQL语句
– **资源限制**:设置合理的资源限制,避免单个查询占用过多资源

## 4. 应用程序优化
– **缓存查询结果**:对频繁查询的结果进行缓存
– **批量操作**:使用批量操作减少SQL执行次数
– **异步处理**:对耗时操作使用异步处理
– **连接池管理**:合理配置连接池,避免连接泄漏

## 5. 监控告警
– **配置慢查询监控**:监控慢SQL的执行情况
– **设置告警阈值**:设置合理的慢SQL告警阈值
– **定期分析**:定期分析慢SQL,找出问题并优化
– **自动处理**:配置自动处理脚本,当发现慢SQL时自动处理

## 6. 系统优化
– **硬件优化**:使用高性能硬件,如SSD、高内存服务器
– **网络优化**:优化网络配置,减少网络延迟
– **存储优化**:合理规划存储,避免I/O瓶颈
– **负载均衡**:使用负载均衡,分散查询压力

2.2 监控配置

生产环境中应配置以下监控项,及时发现慢SQL问题:

# 监控配置

## 1. 慢SQL监控
– **慢SQL数量**:监控慢SQL的数量
– **慢SQL执行时间**:监控慢SQL的执行时间
– **慢SQL占比**:监控慢SQL占总SQL的比例
– **慢SQL趋势**:监控慢SQL的变化趋势

## 2. 执行计划监控
– **全表扫描次数**:监控全表扫描的次数
– **索引使用情况**:监控索引的使用情况
– **扫描行数**:监控SQL语句扫描的数据行数
– **返回行数**:监控SQL语句返回的数据行数

## 3. 资源使用监控
– **CPU使用率**:监控数据库CPU使用率
– **内存使用率**:监控数据库内存使用率
– **I/O操作**:监控数据库I/O操作
– **连接数**:监控数据库连接数

## 4. 告警配置
– **慢SQL告警**:当慢SQL数量超过阈值时告警
– **执行时间告警**:当SQL执行时间超过阈值时告警
– **全表扫描告警**:当全表扫描次数超过阈值时告警
– **资源使用告警**:当资源使用率超过阈值时告警

## 5. 监控工具
– **Prometheus**:收集和存储监控数据
– **Grafana**:可视化监控数据
– **TiDB Dashboard**:TiDB自带的监控工具
– **慢查询日志分析工具**:分析慢查询日志

2.3 索引优化

索引优化是解决慢SQL问题的重要手段:

# 索引优化

## 1. 索引设计原则
– **选择度高**:选择区分度高的列作为索引
– **前缀索引**:对于长字符串,使用前缀索引
– **复合索引**:根据查询条件的组合创建复合索引
– **覆盖索引**:创建覆盖查询列的索引
– **避免过多索引**:过多的索引会影响写入性能

## 2. 索引类型
– **普通索引**:最基本的索引类型
– **唯一索引**:保证列值唯一的索引
– **主键索引**:表的主键,唯一且非空
– **组合索引**:多个列组成的索引
– **前缀索引**:对字符串的前缀部分创建索引
– **空间索引**:用于空间数据类型的索引

## 3. 索引使用建议
– **使用索引列作为查询条件**:确保查询条件使用索引
– **避免在索引列上使用函数**:会导致索引失效
– **避免使用不等于操作符**:可能导致索引失效
– **避免使用LIKE ‘%xxx’**:会导致索引失效
– **使用最左前缀原则**:复合索引的查询条件应使用最左前缀

## 4. 索引维护
– **定期检查索引**:定期检查索引的使用情况
– **删除冗余索引**:删除不必要的索引
– **重建索引**:定期重建索引,提高索引性能
– **分析表**:定期分析表,更新统计信息

## 5. 索引优化案例
– **单列索引**:对频繁查询的单列创建索引
“`sql
CREATE INDEX idx_name ON test.table(name);
“`

– **复合索引**:对频繁组合查询的列创建复合索引
“`sql
CREATE INDEX idx_name_age ON test.table(name, age);
“`

– **覆盖索引**:创建覆盖查询列的索引
“`sql
CREATE INDEX idx_name_age_address ON test.table(name, age, address);
“`

– **前缀索引**:对长字符串创建前缀索引
“`sql
CREATE INDEX idx_email ON test.table(email(20));
“`

Part03-生产环境项目实施方案

3.1 处理步骤

TiDB慢SQL导致性能下降的处理步骤:

# 处理步骤

## 1. 发现慢SQL
– **步骤1**:查看慢查询日志
“`bash
tail -f /tidb/log/tidb-slow.log
“`

– **步骤2**:使用TiDB Dashboard查看慢SQL
“`bash
# 访问TiDB Dashboard
# http://192.168.1.10:2379/dashboard
“`

– **步骤3**:使用SQL语句查询慢SQL
“`sql
— 查询执行时间超过1秒的SQL
SELECT * FROM information_schema.slow_query WHERE query_time > 1 ORDER BY query_time DESC;
“`

## 2. 分析慢SQL
– **步骤1**:查看执行计划
“`sql
EXPLAIN SELECT * FROM test.table WHERE condition;
“`

– **步骤2**:分析SQL语句
– 检查是否使用索引
– 检查是否有全表扫描
– 检查是否有复杂子查询
– 检查是否有大量排序或聚合操作

– **步骤3**:分析表结构和索引
“`sql
— 查看表结构
SHOW CREATE TABLE test.table;
— 查看索引
SHOW INDEX FROM test.table;
“`

## 3. 优化慢SQL
– **步骤1**:优化SQL语句
– 简化复杂查询
– 避免全表扫描
– 限制结果集大小
– 优化排序和聚合操作

– **步骤2**:优化索引
– 添加缺失的索引
– 优化现有索引
– 删除冗余索引

– **步骤3**:优化数据库配置
– 调整慢查询阈值
– 调整内存参数
– 调整并发参数

– **步骤4**:优化应用程序
– 缓存查询结果
– 批量操作
– 异步处理

## 4. 验证优化结果
– **步骤1**:执行优化后的SQL
“`sql
— 执行优化后的SQL并查看执行时间
SELECT /*+ time */ * FROM test.table WHERE condition;
“`

– **步骤2**:查看执行计划
“`sql
EXPLAIN SELECT * FROM test.table WHERE condition;
“`

– **步骤3**:监控性能指标
“`bash
# 查看CPU和内存使用率
top
# 查看I/O操作
iostat -x
“`

## 5. 持续监控
– **步骤1**:设置慢SQL监控
– 配置Prometheus监控
– 设置Grafana告警

– **步骤2**:定期分析慢SQL
– 每周分析慢SQL日志
– 每月进行性能优化

– **步骤3**:文档化优化过程
– 记录优化前后的性能对比
– 记录优化经验和教训

3.2 优化方法

TiDB慢SQL的优化方法:

# 优化方法

## 1. SQL语句优化
– **避免全表扫描**:确保查询条件使用索引
“`sql
— 优化前:全表扫描
SELECT * FROM test.table WHERE name = ‘test’;

— 优化后:使用索引
CREATE INDEX idx_name ON test.table(name);
SELECT * FROM test.table WHERE name = ‘test’;
“`

– **优化复杂查询**:简化复杂查询,避免多层嵌套
“`sql
— 优化前:复杂子查询
SELECT * FROM test.table1 WHERE id IN (SELECT id FROM test.table2 WHERE condition);

— 优化后:使用连接
SELECT t1.* FROM test.table1 t1 JOIN test.table2 t2 ON t1.id = t2.id WHERE t2.condition;
“`

– **限制结果集**:使用LIMIT限制返回数据量
“`sql
— 优化前:返回所有数据
SELECT * FROM test.table;

— 优化后:限制返回数据量
SELECT * FROM test.table LIMIT 1000;
“`

– **避免排序操作**:减少ORDER BY操作,或使用索引排序
“`sql
— 优化前:无索引排序
SELECT * FROM test.table ORDER BY name;

— 优化后:使用索引排序
CREATE INDEX idx_name ON test.table(name);
SELECT * FROM test.table ORDER BY name;
“`

– **避免聚合操作**:减少GROUP BY操作,或使用索引加速
“`sql
— 优化前:无索引聚合
SELECT name, COUNT(*) FROM test.table GROUP BY name;

— 优化后:使用索引加速
CREATE INDEX idx_name ON test.table(name);
SELECT name, COUNT(*) FROM test.table GROUP BY name;
“`

## 2. 索引优化
– **添加缺失的索引**:根据查询条件添加索引
“`sql
— 添加单列索引
CREATE INDEX idx_column ON test.table(column);

— 添加复合索引
CREATE INDEX idx_col1_col2 ON test.table(col1, col2);

— 添加覆盖索引
CREATE INDEX idx_col1_col2_col3 ON test.table(col1, col2, col3);
“`

– **优化现有索引**:调整索引结构,提高索引效率
“`sql
— 删除冗余索引
DROP INDEX idx_column ON test.table;

— 重建索引
ALTER TABLE test.table DROP INDEX idx_column, ADD INDEX idx_column (column);
“`

– **使用前缀索引**:对长字符串使用前缀索引
“`sql
— 创建前缀索引
CREATE INDEX idx_email ON test.table(email(20));
“`

## 3. 数据库配置优化
– **调整慢查询阈值**:根据业务需求调整慢查询阈值
“`toml
# 在tidb.toml中配置
slow-threshold = 500 # 500ms
“`

– **调整内存参数**:根据服务器内存大小调整内存参数
“`toml
# 在tidb.toml中配置
tmp-storage-size = 10737418240 # 10GB
“`

– **调整并发参数**:根据服务器CPU核心数调整并发参数
“`toml
# 在tidb.toml中配置
max-concurrent-queries = 2048
“`

## 4. 应用程序优化
– **缓存查询结果**:对频繁查询的结果进行缓存
“`java
// 使用Redis缓存查询结果
String key = “user:” + userId;
String userInfo = redisTemplate.opsForValue().get(key);
if (userInfo == null) {
// 从数据库查询
userInfo = userService.getUserInfo(userId);
// 存入缓存
redisTemplate.opsForValue().set(key, userInfo, 30, TimeUnit.MINUTES);
}
“`

– **批量操作**:使用批量操作减少SQL执行次数
“`java
// 批量插入
jdbcTemplate.batchUpdate(
“INSERT INTO test.table (id, name) VALUES (?, ?)”,
new BatchPreparedStatementSetter() {
@Override
public void setValues(PreparedStatement ps, int i) throws SQLException {
ps.setInt(1, ids[i]);
ps.setString(2, names[i]);
}
@Override
public int getBatchSize() {
return ids.length;
}
}
);
“`

– **异步处理**:对耗时操作使用异步处理
“`java
// 使用CompletableFuture异步处理
CompletableFuture.runAsync(() -> {
// 执行耗时的数据库操作
reportService.generateReport();
});
“`

## 5. 系统优化
– **硬件优化**:使用高性能硬件
– 使用SSD存储
– 增加内存容量
– 使用多核CPU

– **网络优化**:优化网络配置
– 使用万兆网络
– 减少网络延迟
– 优化网络拓扑

– **存储优化**:合理规划存储
– 分区表
– 分库分表
– 数据压缩

3.3 应急处理流程

TiDB慢SQL导致性能下降的应急处理流程:

# 应急处理流程

## 1. 发现问题
– **监控告警**:监控系统发现慢SQL并触发告警
– **应用异常**:应用程序出现响应缓慢或超时
– **手动检查**:运维人员手动检查发现慢SQL

## 2. 初步诊断
– **定位慢SQL**:找出导致性能下降的慢SQL
– **分析执行计划**:分析慢SQL的执行计划
– **评估影响**:评估慢SQL对系统的影响程度

## 3. 紧急处理
– **终止慢SQL**:终止长时间运行的慢SQL
“`sql
— 查看正在执行的SQL
SHOW PROCESSLIST;
— 终止慢SQL
KILL 123; — 123是连接ID
“`

– **临时优化**:临时调整SQL语句或索引
“`sql
— 临时添加索引
CREATE INDEX idx_temp ON test.table(column);
“`

– **限流措施**:限制慢SQL的执行频率
“`sql
— 设置查询超时
SET SESSION MAX_EXECUTION_TIME=5000; — 5秒
“`

– **资源隔离**:为重要业务预留资源
“`sql
— 创建资源组
CREATE RESOURCE GROUP rg_critical WITH RU_PER_SEC = 10000;
— 将用户分配到资源组
ALTER USER ‘app_user’ RESOURCE GROUP rg_critical;
“`

## 4. 根本解决
– **优化SQL语句**:重构慢SQL,提高执行效率
– **优化索引**:添加或修改索引,提高查询速度
– **优化数据库配置**:调整数据库参数,提高性能
– **优化应用程序**:修改应用程序逻辑,减少慢SQL

## 5. 验证恢复
– **测试优化效果**:执行优化后的SQL,验证性能提升
– **监控系统状态**:监控数据库性能,确保系统稳定
– **测试应用程序**:测试应用程序响应时间,确保业务正常

## 6. 总结预防
– **分析原因**:分析慢SQL产生的根本原因
– **制定措施**:制定预防措施,避免类似问题再次发生
– **更新文档**:更新慢SQL处理文档和最佳实践

风哥提示:慢SQL的应急处理需要快速且准确,避免影响正常业务运行。from tidb视频:www.itpux.com

Part04-生产案例与实战讲解

4.1 全表扫描导致的慢SQL

# 全表扫描导致的慢SQL

## 1. 环境信息
– **TiDB版本**:6.1.0
– **表结构**:test.users表,包含1000万行数据
– **索引情况**:无索引
– **操作系统**:Oracle Linux 9.3

## 2. 故障现象
– **SQL执行时间**:执行时间超过10秒
– **系统负载**:数据库CPU使用率达到100%
– **应用响应**:应用程序响应缓慢

## 3. 故障分析
– **SQL语句**:
“`sql
SELECT * FROM test.users WHERE age > 30;
“`
– **执行计划**:
“`
+————————-+———-+———–+—————+——————————–+-
| id | estRows | task | access object | operator info |
+————————-+———-+———–+—————+——————————–+-
| TableReader_7 | 3333333 | root | | data:Selection_6 |
| └─Selection_6 | 3333333 | cop[tikv] | | gt(test.users.age, 30) |
| └─TableFullScan_5 | 10000000 | cop[tikv] | table:users | keep order:false, stats:pseudo |
+————————-+———-+———–+—————+——————————–+-
“`
– **原因**:没有为age列创建索引,导致全表扫描

## 4. 解决方案
– **步骤1**:添加索引
“`sql
CREATE INDEX idx_age ON test.users(age);
“`

– **步骤2**:验证索引效果
“`sql
EXPLAIN SELECT * FROM test.users WHERE age > 30;
“`
– **执行计划**:
“`
+————————-+———-+———–+————————+———————————–+-
| id | estRows | task | access object | operator info |
+————————-+———-+———–+————————+———————————–+-
| IndexReader_6 | 3333333 | root | | index:IndexScan_5 |
| └─IndexScan_5 | 3333333 | cop[tikv] | table:users, index:idx_age | range:(30, +inf), keep order:false |
+————————-+———-+———–+————————+———————————–+-
“`

– **步骤3**:测试执行时间
“`sql
SELECT /*+ time */ * FROM test.users WHERE age > 30 LIMIT 1000;
“`
– **执行时间**:从10秒以上降至0.1秒以下

## 5. 预防措施
– **建立索引规范**:为频繁查询的列创建索引
– **定期检查慢SQL**:定期分析慢SQL,发现全表扫描
– **使用执行计划**:在开发阶段分析执行计划,避免全表扫描
– **监控索引使用情况**:监控索引的使用情况,及时优化

4.2 复杂查询导致的慢SQL

# 复杂查询导致的慢SQL

## 1. 环境信息
– **TiDB版本**:6.1.0
– **表结构**:test.orders表和test.order_items表
– **数据量**:orders表100万行,order_items表1000万行
– **操作系统**:Oracle Linux 9.3

## 2. 故障现象
– **SQL执行时间**:执行时间超过30秒
– **系统负载**:数据库CPU和内存使用率高
– **应用响应**:应用程序超时

## 3. 故障分析
– **SQL语句**:
“`sql
SELECT o.order_id, o.customer_id, SUM(oi.quantity * oi.price) AS total_amount
FROM test.orders o
JOIN test.order_items oi ON o.order_id = oi.order_id
WHERE o.order_date BETWEEN ‘2023-01-01’ AND ‘2023-12-31’
GROUP BY o.order_id, o.customer_id
ORDER BY total_amount DESC
LIMIT 100;
“`
– **执行计划**:
“`
+——————————–+———-+———–+————————+———————————–+-
| id | estRows | task | access object | operator info |
+——————————–+———-+———–+————————+———————————–+-
| Sort_8 | 100 | root | | sort:total_amount DESC |
| └─Limit_7 | 100 | root | | limit:100 |
| └─HashAgg_9 | 1000000 | root | | group by:o.order_id, o.customer_id, aggregate:sum(oi.quantity * oi.price) |
| └─HashJoin_10 | 10000000 | root | | inner join, equal:[o.order_id, oi.order_id] |
| ├─TableReader_14 | 1000000 | root | | data:Selection_13 |
| │ └─Selection_13 | 1000000 | cop[tikv] | | between(o.order_date, 2023-01-01, 2023-12-31) |
| │ └─TableFullScan_12 | 1000000 | cop[tikv] | table:o | keep order:false, stats:pseudo |
| └─TableReader_16 | 10000000 | root | | data:TableFullScan_15 |
| └─TableFullScan_15 | 10000000 | cop[tikv] | table:oi | keep order:false, stats:pseudo |
+——————————–+———-+———–+————————+———————————–+-
“`
– **原因**:查询包含表连接、聚合和排序操作,且没有使用索引

## 4. 解决方案
– **步骤1**:添加索引
“`sql
— 为orders表的order_date列添加索引
CREATE INDEX idx_order_date ON test.orders(order_date);
— 为order_items表的order_id列添加索引
CREATE INDEX idx_order_id ON test.order_items(order_id);
“`

– **步骤2**:优化SQL语句
“`sql
SELECT o.order_id, o.customer_id, SUM(oi.quantity * oi.price) AS total_amount
FROM test.orders o
JOIN test.order_items oi ON o.order_id = oi.order_id
WHERE o.order_date BETWEEN ‘2023-01-01’ AND ‘2023-12-31’
GROUP BY o.order_id, o.customer_id
ORDER BY total_amount DESC
LIMIT 100;
“`

– **步骤3**:验证优化效果
“`sql
EXPLAIN SELECT o.order_id, o.customer_id, SUM(oi.quantity * oi.price) AS total_amount
FROM test.orders o
JOIN test.order_items oi ON o.order_id = oi.order_id
WHERE o.order_date BETWEEN ‘2023-01-01’ AND ‘2023-12-31’
GROUP BY o.order_id, o.customer_id
ORDER BY total_amount DESC
LIMIT 100;
“`
– **执行计划**:
“`
+——————————–+———-+———–+————————+———————————–+-
| id | estRows | task | access object | operator info |
+——————————–+———-+———–+————————+———————————–+-
| Sort_8 | 100 | root | | sort:total_amount DESC |
| └─Limit_7 | 100 | root | | limit:100 |
| └─HashAgg_9 | 1000000 | root | | group by:o.order_id, o.customer_id, aggregate:sum(oi.quantity * oi.price) |
| └─HashJoin_10 | 10000000 | root | | inner join, equal:[o.order_id, oi.order_id] |
| ├─IndexReader_14 | 1000000 | root | | index:IndexScan_13 |
| │ └─IndexScan_13 | 1000000 | cop[tikv] | table:o, index:idx_order_date | range:[2023-01-01, 2023-12-31], keep order:false |
| └─IndexReader_16 | 10000000 | root | | index:IndexScan_15 |
| └─IndexScan_15 | 10000000 | cop[tikv] | table:oi, index:idx_order_id | keep order:false, stats:pseudo |
+——————————–+———-+———–+————————+———————————–+-
“`

– **步骤4**:测试执行时间
“`sql
SELECT /*+ time */ o.order_id, o.customer_id, SUM(oi.quantity * oi.price) AS total_amount
FROM test.orders o
JOIN test.order_items oi ON o.order_id = oi.order_id
WHERE o.order_date BETWEEN ‘2023-01-01’ AND ‘2023-12-31’
GROUP BY o.order_id, o.customer_id
ORDER BY total_amount DESC
LIMIT 100;
“`
– **执行时间**:从30秒以上降至2秒以下

## 5. 预防措施
– **优化查询结构**:简化复杂查询,避免多层嵌套
– **合理使用索引**:为连接列和过滤列创建索引
– **限制结果集**:使用LIMIT限制返回数据量
– **使用物化视图**:对于复杂查询,使用物化视图预计算结果

4.3 索引失效导致的慢SQL

# 索引失效导致的慢SQL

## 1. 环境信息
– **TiDB版本**:6.1.0
– **表结构**:test.users表,包含500万行数据
– **索引情况**:name列有索引
– **操作系统**:Oracle Linux 9.3

## 2. 故障现象
– **SQL执行时间**:执行时间超过5秒
– **系统负载**:数据库CPU使用率高
– **应用响应**:应用程序响应缓慢

## 3. 故障分析
– **SQL语句**:
“`sql
SELECT * FROM test.users WHERE LOWER(name) = ‘test’;
“`
– **执行计划**:
“`
+————————-+———+———–+—————+——————————–+-
| id | estRows | task | access object | operator info |
+————————-+———+———–+—————+——————————–+-
| TableReader_7 | 2500000 | root | | data:Selection_6 |
| └─Selection_6 | 2500000 | cop[tikv] | | eq(lower(test.users.name), “test”) |
| └─TableFullScan_5 | 5000000 | cop[tikv] | table:users | keep order:false, stats:pseudo |
+————————-+———+———–+—————+——————————–+-
“`
– **原因**:在索引列上使用了LOWER函数,导致索引失效,产生全表扫描

## 4. 解决方案
– **步骤1**:优化SQL语句
“`sql
— 优化前:使用函数导致索引失效
SELECT * FROM test.users WHERE LOWER(name) = ‘test’;

— 优化后:避免在索引列上使用函数
SELECT * FROM test.users WHERE name = ‘test’;
“`

– **步骤2**:如果需要不区分大小写查询
“`sql
— 创建不区分大小写的索引
CREATE INDEX idx_name_ci ON test.users((LOWER(name)));

— 使用索引
SELECT * FROM test.users WHERE LOWER(name) = ‘test’;
“`

– **步骤3**:验证优化效果
“`sql
EXPLAIN SELECT * FROM test.users WHERE name = ‘test’;
“`
– **执行计划**:
“`
+————————-+———+———–+————————+———————————–+-
| id | estRows | task | access object | operator info |
+————————-+———+———–+————————+———————————–+-
| IndexReader_6 | 1 | root | | index:IndexScan_5 |
| └─IndexScan_5 | 1 | cop[tikv] | table:users, index:idx_name | range:[“test”, “test”], keep order:false |
+————————-+———+———–+————————+———————————–+-
“`

– **步骤4**:测试执行时间
“`sql
SELECT /*+ time */ * FROM test.users WHERE name = ‘test’;
“`
– **执行时间**:从5秒以上降至0.01秒以下

## 5. 预防措施
– **避免在索引列上使用函数**:会导致索引失效
– **使用表达式索引**:如果需要在索引列上使用函数,创建表达式索引
– **标准化数据**:在插入数据时标准化数据格式,避免查询时使用函数
– **定期检查索引使用情况**:监控索引的使用情况,及时发现索引失效问题

4.4 并发查询导致的慢SQL

# 并发查询导致的慢SQL

## 1. 环境信息
– **TiDB版本**:6.1.0
– **表结构**:test.products表,包含100万行数据
– **索引情况**:有索引
– **操作系统**:Oracle Linux 9.3

## 2. 故障现象
– **SQL执行时间**:正常情况下执行时间为0.1秒,并发时执行时间超过10秒
– **系统负载**:数据库CPU和内存使用率高
– **应用响应**:应用程序在高并发时响应缓慢

## 3. 故障分析
– **SQL语句**:
“`sql
SELECT * FROM test.products WHERE category = ‘electronics’ ORDER BY price DESC LIMIT 100;
“`
– **执行计划**:
“`
+————————-+———+———–+————————+———————————–+-
| id | estRows | task | access object | operator info |
+————————-+———+———–+————————+———————————–+-
| Limit_7 | 100 | root | | limit:100 |
| └─Sort_6 | 100000 | root | | sort:price DESC |
| └─IndexReader_5 | 100000 | root | | index:IndexScan_4 |
| └─IndexScan_4 | 100000 | cop[tikv] | table:products, index:idx_category | range:[“electronics”, “electronics”], keep order:false |
+————————-+———+———–+————————+———————————–+-
“`
– **原因**:高并发时,多个查询同时执行,导致资源竞争,性能下降

## 4. 解决方案
– **步骤1**:优化SQL语句
“`sql
— 添加覆盖索引,减少回表操作
CREATE INDEX idx_category_price ON test.products(category, price DESC);
“`

– **步骤2**:优化执行计划
“`sql
EXPLAIN SELECT * FROM test.products WHERE category = ‘electronics’ ORDER BY price DESC LIMIT 100;
“`
– **执行计划**:
“`
+————————-+———+———–+——————————+———————————–+-
| id | estRows | task | access object | operator info |
+————————-+———+———–+——————————+———————————–+-
| Limit_6 | 100 | root | | limit:100 |
| └─IndexReader_5 | 100 | root | | index:IndexScan_4 |
| └─IndexScan_4 | 100 | cop[tikv] | table:products, index:idx_category_price | range:[“electronics”, “electronics”], keep order:true |
+————————-+———+———–+——————————+———————————–+-
“`

– **步骤3**:配置资源限制
“`toml
# 在tidb.toml中配置
max-concurrent-queries = 2048
stmt-count-limit = 1000000
“`

– **步骤4**:应用程序优化
– 实现查询缓存
– 批量查询
– 异步处理

– **步骤5**:测试并发性能
“`bash
# 使用sysbench测试并发性能
sysbench –db-driver=mysql –mysql-host=192.168.1.10 –mysql-port=4000 –mysql-user=root –mysql-password=password –mysql-db=test –table-size=1000000 –threads=100 –time=60 –report-interval=10 oltp_read_write run
“`

## 5. 预防措施
– **优化索引**:创建覆盖索引,减少回表操作
– **配置资源限制**:合理配置并发参数,避免资源竞争
– **应用程序优化**:实现查询缓存,减少重复查询
– **负载均衡**:使用负载均衡,分散查询压力
– **监控并发性能**:监控并发查询的性能,及时发现问题

Part05-风哥经验总结与分享

5.1 常见问题与解决方案

TiDB慢SQL导致性能下降的常见问题与解决方案:

# 常见问题与解决方案

## 1. 全表扫描
– **问题**:SQL语句没有使用索引,导致全表扫描
– **解决**:
– 为查询条件添加合适的索引
– 优化SQL语句,确保使用索引
– 分析执行计划,确认索引使用情况

## 2. 索引失效
– **问题**:索引存在但未被使用
– **解决**:
– 避免在索引列上使用函数
– 避免使用不等于操作符
– 避免使用LIKE ‘%xxx’
– 确保查询条件符合索引的最左前缀原则

## 3. 复杂查询
– **问题**:SQL语句包含多个表连接、子查询
– **解决**:
– 简化复杂查询,避免多层嵌套
– 使用连接替代子查询
– 拆分复杂查询为多个简单查询
– 使用物化视图预计算结果

## 4. 大结果集
– **问题**:SQL语句返回大量数据
– **解决**:
– 使用LIMIT限制返回数据量
– 分页查询
– 只查询需要的列
– 使用缓存存储查询结果

## 5. 排序和聚合操作
– **问题**:SQL语句包含大量排序或聚合操作
– **解决**:
– 使用索引排序
– 减少排序和聚合操作
– 增加服务器内存
– 使用列式存储(TiFlash)加速分析查询

## 6. 并发查询
– **问题**:高并发时性能下降
– **解决**:
– 优化索引,减少资源竞争
– 配置合理的并发参数
– 实现查询缓存
– 使用负载均衡,分散查询压力

## 7. 数据倾斜
– **问题**:数据分布不均匀,某些分区数据量过大
– **解决**:
– 合理设计数据分区
– 避免热点数据
– 使用分片技术分散数据
– 定期清理和归档数据

## 8. 统计信息过时
– **问题**:统计信息过时,导致优化器选择错误的执行计划
– **解决**:
– 定期分析表,更新统计信息
– 使用ANALYZE TABLE命令更新统计信息
– 监控统计信息的更新情况

5.2 最佳实践

TiDB慢SQL优化的最佳实践:

最佳实践:

  • 合理设计索引:根据查询条件创建合适的索引,避免过多或过少的索引
  • 优化SQL语句:简化复杂查询,避免全表扫描,限制结果集大小
  • 分析执行计划:在开发阶段分析执行计划,确保SQL语句高效执行
  • 监控慢SQL:启用慢查询日志,定期分析慢SQL,及时发现问题
  • 更新统计信息:定期分析表,更新统计信息,确保优化器选择正确的执行计划
  • 配置合理参数:根据服务器资源和业务需求配置合理的数据库参数
  • 应用程序优化:实现查询缓存,批量操作,异步处理,减少数据库压力
  • 硬件优化:使用高性能硬件,如SSD、高内存服务器,提高数据库性能
  • 定期维护:定期清理数据,重建索引,优化表结构
  • 持续优化:持续监控和优化数据库性能,适应业务变化

5.3 优化技巧

TiDB慢SQL优化的实用技巧:

# 优化技巧

## 1. SQL语句优化技巧
– **使用索引提示**:当优化器选择错误的索引时,使用索引提示
“`sql
SELECT /*+ USE_INDEX(t, idx_name) */ * FROM test.table t WHERE name = ‘test’;
“`

– **使用强制索引**:强制使用指定的索引
“`sql
SELECT * FROM test.table FORCE INDEX (idx_name) WHERE name = ‘test’;
“`

– **使用覆盖索引**:创建覆盖查询列的索引,减少回表操作
“`sql
CREATE INDEX idx_name_age ON test.table(name, age);
SELECT name, age FROM test.table WHERE name = ‘test’;
“`

– **使用前缀索引**:对长字符串使用前缀索引,减少索引大小
“`sql
CREATE INDEX idx_email ON test.table(email(20));
“`

– **避免使用SELECT ***:只查询需要的列,减少数据传输
“`sql
— 优化前
SELECT * FROM test.table;
— 优化后
SELECT id, name, age FROM test.table;
“`

– **使用LIMIT**:限制返回数据量,减少网络传输和处理时间
“`sql
SELECT * FROM test.table LIMIT 1000;
“`

– **优化排序操作**:使用索引排序,避免文件排序
“`sql
— 优化前:文件排序
SELECT * FROM test.table ORDER BY name;
— 优化后:索引排序
CREATE INDEX idx_name ON test.table(name);
SELECT * FROM test.table ORDER BY name;
“`

– **优化聚合操作**:使用索引加速聚合操作
“`sql
— 优化前:无索引聚合
SELECT name, COUNT(*) FROM test.table GROUP BY name;
— 优化后:使用索引加速
CREATE INDEX idx_name ON test.table(name);
SELECT name, COUNT(*) FROM test.table GROUP BY name;
“`

## 2. 索引优化技巧
– **复合索引顺序**:将选择性高的列放在前面
“`sql
CREATE INDEX idx_col1_col2 ON test.table(col1, col2);
“`

– **使用部分索引**:只对部分数据创建索引,减少索引大小
“`sql
CREATE INDEX idx_status ON test.table(status) WHERE status = ‘active’;
“`

– **使用表达式索引**:对函数表达式创建索引
“`sql
CREATE INDEX idx_lower_name ON test.table((LOWER(name)));
“`

– **定期重建索引**:定期重建索引,提高索引性能
“`sql
ALTER TABLE test.table DROP INDEX idx_name, ADD INDEX idx_name (name);
“`

– **删除冗余索引**:删除不必要的索引,减少索引维护开销
“`sql
DROP INDEX idx_name ON test.table;
“`

## 3. 数据库配置优化技巧
– **调整慢查询阈值**:根据业务需求调整慢查询阈值
“`toml
# 在tidb.toml中配置
slow-threshold = 500 # 500ms
“`

– **调整内存参数**:根据服务器内存大小调整内存参数
“`toml
# 在tidb.toml中配置
tmp-storage-size = 10737418240 # 10GB
“`

– **调整并发参数**:根据服务器CPU核心数调整并发参数
“`toml
# 在tidb.toml中配置
max-concurrent-queries = 2048
“`

– **启用查询缓存**:对于频繁查询的结果启用查询缓存
“`toml
# 在tidb.toml中配置
prepared-plan-cache-size = 1000
“`

## 4. 应用程序优化技巧
– **实现查询缓存**:使用Redis等缓存工具缓存查询结果
“`java
// 使用Redis缓存查询结果
String key = “product:” + productId;
String productInfo = redisTemplate.opsForValue().get(key);
if (productInfo == null) {
// 从数据库查询
productInfo = productService.getProductInfo(productId);
// 存入缓存
redisTemplate.opsForValue().set(key, productInfo, 30, TimeUnit.MINUTES);
}
“`

– **批量操作**:使用批量操作减少SQL执行次数
“`java
// 批量更新
jdbcTemplate.batchUpdate(
“UPDATE test.table SET status = ? WHERE id = ?”,
new BatchPreparedStatementSetter() {
@Override
public void setValues(PreparedStatement ps, int i) throws SQLException {
ps.setString(1, statuses[i]);
ps.setInt(2, ids[i]);
}
@Override
public int getBatchSize() {
return ids.length;
}
}
);
“`

– **异步处理**:对耗时操作使用异步处理
“`java
// 使用CompletableFuture异步处理
CompletableFuture.supplyAsync(() -> {
// 执行耗时的数据库操作
return reportService.generateReport();
}).thenAccept(report -> {
// 处理结果
System.out.println(“Report generated: ” + report);
});
“`

– **连接池优化**:合理配置连接池,避免连接泄漏
“`java
HikariConfig config = new HikariConfig();
config.setMaximumPoolSize(20);
config.setMinimumIdle(5);
config.setIdleTimeout(30000);
config.setConnectionTimeout(30000);
config.setMaxLifetime(1800000);
“`

## 5. 系统优化技巧
– **使用TiFlash**:对于分析查询,使用TiFlash加速
“`sql
— 创建TiFlash副本
ALTER TABLE test.table SET TIFLASH REPLICA 1;

— 强制使用TiFlash
SELECT /*+ READ_FROM_STORAGE(TIFLASH[test.table]) */ * FROM test.table;
“`

– **分区表**:对于大表,使用分区表提高查询性能
“`sql
— 创建分区表
CREATE TABLE test.table (
id INT PRIMARY KEY,
created_at DATETIME
) PARTITION BY RANGE (YEAR(created_at)) (
PARTITION p2023 VALUES LESS THAN (2024),
PARTITION p2024 VALUES LESS THAN (2025),
PARTITION p2025 VALUES LESS THAN (2026)
);
“`

– **数据压缩**:启用数据压缩,减少存储和I/O开销
“`sql
— 启用表压缩
ALTER TABLE test.table COMPRESSION = ‘LZ4’;
“`

– **负载均衡**:使用负载均衡,分散查询压力
“`bash
# 配置HAProxy负载均衡
vim /etc/haproxy/haproxy.cfg
“`

风哥提示:慢SQL优化是一个持续的过程,需要定期分析和调整,才能保持数据库的高性能。更多视频教程www.fgedu.net.cn

本文由风哥教程整理发布,仅用于学习测试使用,转载注明出处:http://www.fgedu.net.cn/10327.html

联系我们

在线咨询:点击这里给我发消息

微信号:itpux-com

工作日:9:30-18:30,节假日休息