本文档详细介绍TiDB执行计划与SQL优化方法,包括执行计划概念、组件、类型、分析、SQL优化策略、实施方案、实战案例等内容。风哥教程参考TiDB官方文档执行计划相关内容,适合DBA和开发人员在日常使用TiDB时参考。更多视频教程www.fgedu.net.cn
Part01-基础概念与理论知识
1.1 执行计划概念
执行计划是数据库优化器生成的查询执行方案,用于指导SQL语句的执行。
- 定义:数据库优化器根据SQL语句和统计信息生成的执行方案
- 作用:指导SQL语句的执行,影响查询性能
- 生成过程:优化器分析SQL语句,评估不同执行方案的成本,选择最优方案
- 了解SQL语句的执行方式
- 识别性能瓶颈
- 优化查询性能
- 验证索引使用情况
1.2 执行计划组件
TiDB执行计划包含以下主要组件:
1.2.1 访问类型(Access Type)
– ALL:全表扫描
– index:索引扫描
– range:范围扫描
– ref:非唯一索引扫描
– eq_ref:唯一索引扫描
– const:常量扫描
– system:系统表扫描
– NULL:不需要访问表
1.2.2 索引使用(Key)
– PRIMARY:使用主键索引
– idx_name:使用名为idx_name的索引
– NULL:未使用索引
1.2.3 行估计(Rows)
– 估计扫描的行数
– 影响执行计划的选择
– 基于统计信息计算
1.2.4 额外信息(Extra)
# 额外信息
– Using index:使用覆盖索引
– Using where:使用WHERE条件过滤
– Using temporary:使用临时表
– Using filesort:使用文件排序
– Using join buffer:使用连接缓冲区
– Using index condition:使用索引条件下推
1.3 执行计划类型
## 1. 表扫描
– 全表扫描:扫描整个表
– 索引扫描:扫描索引
– 范围扫描:扫描索引的特定范围
## 2. 连接类型
– Nested Loop Join:嵌套循环连接
– Hash Join:哈希连接
– Merge Join:合并连接
## 3. 排序操作
– Using filesort:使用文件排序
– Using index:使用索引排序
## 4. 聚合操作
– Using temporary:使用临时表
– Using index:使用索引聚合
## 5. 子查询处理
– 相关子查询:依赖外部查询
– 非相关子查询:独立于外部查询
Part02-生产环境规划与建议
2.1 执行计划分析
2.1.1 使用EXPLAIN分析执行计划
## 1. 基本语法
EXPLAIN SELECT * FROM fgedu_users WHERE name = ‘张三’;
## 2. 查看详细执行计划
EXPLAIN ANALYZE SELECT * FROM fgedu_users WHERE name = ‘张三’;
## 3. 分析执行计划输出
– id:执行计划的唯一标识
– select_type:查询类型
– table:表名
– type:访问类型
– possible_keys:可能使用的索引
– key:实际使用的索引
– key_len:索引长度
– ref:引用的列
– rows:估计扫描的行数
– filtered:过滤后的行数百分比
– Extra:额外信息
2.1.2 执行计划分析步骤
## 1. 查看访问类型
– 优先选择访问类型为const、eq_ref、ref、range的执行计划
– 避免全表扫描(ALL)
## 2. 检查索引使用
– 确保使用了合适的索引
– 检查key列是否为NULL
– 验证索引是否被正确使用
## 3. 分析行估计
– 检查rows列的估计值是否合理
– 与实际行数比较,评估统计信息的准确性
## 4. 检查额外信息
– 避免Using temporary和Using filesort
– 优先使用Using index(覆盖索引)
– 注意Using where的使用情况
## 5. 分析连接类型
– 对于小表,Nested Loop Join可能更高效
– 对于大表,Hash Join可能更高效
– 对于有序数据,Merge Join可能更高效
2.2 SQL优化策略
2.2.1 索引优化
# 索引优化
## 1. 选择合适的索引
– 为频繁查询的列创建索引
– 为WHERE条件、ORDER BY和GROUP BY的列创建索引
– 为JOIN条件的列创建索引
## 2. 优化复合索引
– 遵循最左前缀原则
– 将最常用的列放在前面
– 将选择性高的列放在前面
## 3. 使用覆盖索引
– 包含查询所需的所有列
– 避免回表操作
– 减少IO操作
## 4. 避免索引失效
– 避免在索引列上使用函数
– 避免使用不等于(!=)操作符
– 避免使用IS NULL或IS NOT NULL
– 避免使用LIKE ‘%xxx’(前缀模糊匹配)
2.2.2 SQL语句优化
## 1. 优化WHERE子句
– 将最选择性的条件放在前面
– 避免使用OR操作符,使用UNION代替
– 避免使用LIKE ‘%xxx’(前缀模糊匹配)
– 合理使用IN和EXISTS
## 2. 优化JOIN操作
– 确保连接列有索引
– 小表驱动大表
– 合理使用连接类型
– 避免笛卡尔积
## 3. 优化聚合操作
– 利用索引加速GROUP BY
– 合理使用HAVING和WHERE
– 避免在HAVING中使用复杂表达式
## 4. 优化子查询
– 合理使用子查询
– 避免嵌套过深的子查询
– 考虑使用JOIN代替子查询
## 5. 优化排序操作
– 利用索引的有序性
– 确保ORDER BY子句与索引顺序一致
– 避免使用不同方向的排序
2.3 性能考虑
## 1. 查询性能
– 减少数据扫描范围
– 利用索引加速查询
– 避免全表扫描
– 减少回表操作
## 2. 写入性能
– 批量写入数据
– 合理设置事务大小
– 控制索引数量
– 避免大事务
## 3. 存储开销
– 合理设计表结构
– 控制索引数量和大小
– 定期清理无用数据
– 合理设置分区策略
## 4. 系统资源
– 监控CPU使用率
– 监控内存使用率
– 监控IOPS
– 监控网络吞吐量
## 5. 维护成本
– 定期更新统计信息
– 定期重建索引
– 监控慢查询
– 定期优化SQL语句
Part03-生产环境项目实施方案
3.1 执行计划实施方案
3.1.1 执行计划分析工具
## 1. EXPLAIN命令
– 基本使用:EXPLAIN SELECT …
– 详细分析:EXPLAIN ANALYZE SELECT …
– 查看执行计划:SHOW EXPLAIN FOR <query_id>
## 2. TiDB Dashboard
– 慢查询分析:查看慢查询的执行计划
– 执行计划可视化:图形化展示执行计划
– 性能分析:分析查询性能瓶颈
## 3. 第三方工具
– MySQL Workbench:可视化执行计划
– Navicat:执行计划分析
– DBeaver:执行计划分析
## 4. 自定义脚本
– 编写脚本分析执行计划
– 自动识别性能问题
– 生成优化建议
3.1.2 执行计划优化流程
## 1. 收集慢查询
– 开启慢查询日志
– 设置合理的long_query_time
– 收集慢查询语句
## 2. 分析执行计划
– 使用EXPLAIN分析执行计划
– 识别性能瓶颈
– 确定优化方向
## 3. 优化SQL语句
– 调整WHERE条件
– 优化JOIN操作
– 调整ORDER BY和GROUP BY
– 优化子查询
## 4. 优化索引
– 创建合适的索引
– 调整复合索引的列顺序
– 使用覆盖索引
– 清理无用索引
## 5. 验证优化效果
– 测试查询性能
– 分析执行计划变化
– 确认优化效果
## 6. 监控与维护
– 监控优化后的查询性能
– 定期分析执行计划
– 及时调整优化策略
3.2 SQL优化实施方案
3.2.1 SQL优化步骤
## 1. 识别问题SQL
– 分析慢查询日志
– 监控查询性能
– 识别高频查询
## 2. 分析执行计划
– 使用EXPLAIN分析执行计划
– 识别性能瓶颈
– 确定优化方向
## 3. 优化SQL语句
– 调整WHERE条件
– 优化JOIN操作
– 调整ORDER BY和GROUP BY
– 优化子查询
## 4. 优化索引
– 创建合适的索引
– 调整复合索引的列顺序
– 使用覆盖索引
– 清理无用索引
## 5. 测试优化效果
– 测试查询性能
– 分析执行计划变化
– 确认优化效果
## 6. 应用优化方案
– 部署优化后的SQL语句
– 更新应用程序代码
– 监控优化效果
3.2.2 SQL优化工具
## 1. TiDB内置工具
– TiDB Dashboard:慢查询分析
– EXPLAIN命令:执行计划分析
– ANALYZE TABLE:更新统计信息
## 2. 第三方工具
– pt-query-digest:分析慢查询日志
– MySQLTuner:MySQL配置和性能分析
– Percona Monitoring and Management:监控和分析
## 3. 自定义脚本
– 慢查询分析脚本
– 执行计划分析脚本
– 索引使用情况分析脚本
## 4. 监控工具
– Prometheus + Grafana:监控查询性能
– Nagios:监控系统状态
– Zabbix:监控系统状态
3.3 监控实施方案
3.3.1 监控指标设置
## 1. 查询性能监控
– 慢查询数量:监控慢查询的数量
– 查询响应时间:监控查询的响应时间
– 扫描行数:监控查询扫描的数据行数
– 索引使用情况:监控索引的使用频率
## 2. 执行计划监控
– 全表扫描次数:监控全表扫描的次数
– 临时表使用次数:监控临时表的使用次数
– 文件排序次数:监控文件排序的次数
– 索引扫描次数:监控索引扫描的次数
## 3. 系统资源监控
– CPU使用率:监控CPU的使用情况
– 内存使用率:监控内存的使用情况
– IOPS:监控磁盘IO操作
– 网络吞吐量:监控网络传输速度
## 4. 告警设置
– 慢查询告警:当慢查询数量超过阈值时告警
– 全表扫描告警:当全表扫描次数超过阈值时告警
– 系统资源告警:当系统资源使用率超过阈值时告警
3.3.2 监控工具使用
## 1. Prometheus + Grafana
– 部署Prometheus收集监控数据
– 配置Grafana仪表盘
– 设置告警规则
## 2. TiDB Dashboard
– 使用TiDB内置的Dashboard
– 监控集群状态
– 查看慢查询
– 分析性能问题
## 3. 自定义监控脚本
– 编写自定义监控脚本
– 定期检查执行计划
– 生成性能报告
## 4. 日志分析
– 分析慢查询日志
– 识别性能问题
– 排查故障
Part04-生产案例与实战讲解
4.1 执行计划实战案例
## 1. 案例背景
– 表:fgedu_orders(订单表)
– 数据量:1000万行
– 查询:SELECT * FROM fgedu_orders WHERE user_id = 1 AND status = ‘completed’;
## 2. 分析过程
### 步骤1:查看执行计划
mysql> EXPLAIN SELECT * FROM fgedu_orders WHERE user_id = 1 AND status = ‘completed’;
### 步骤2:分析执行计划输出
– id: 1
– select_type: SIMPLE
– table: fgedu_orders
– type: ALL
– possible_keys: NULL
– key: NULL
– key_len: NULL
– ref: NULL
– rows: 10000000
– filtered: 10.00
– Extra: Using where
### 步骤3:识别问题
– 访问类型为ALL(全表扫描)
– 未使用索引
– 估计扫描1000万行
### 步骤4:优化方案
– 创建复合索引:(user_id, status)
### 步骤5:实施优化
mysql> ALTER TABLE fgedu_orders ADD INDEX idx_user_id_status (user_id, status);
### 步骤6:验证优化效果
mysql> EXPLAIN SELECT * FROM fgedu_orders WHERE user_id = 1 AND status = ‘completed’;
### 步骤7:分析优化后的执行计划
– id: 1
– select_type: SIMPLE
– table: fgedu_orders
– type: ref
– possible_keys: idx_user_id_status
– key: idx_user_id_status
– key_len: 82
– ref: const,const
– rows: 100
– filtered: 100.00
– Extra: NULL
## 3. 优化效果
– 访问类型从ALL变为ref
– 使用了复合索引idx_user_id_status
– 估计扫描行数从1000万行减少到100行
– 查询性能显著提升
4.2 SQL优化实战案例
## 1. 案例背景
– 表:fgedu_users(用户表)和fgedu_orders(订单表)
– 数据量:fgedu_users 100万行,fgedu_orders 1000万行
– 查询:SELECT u.name, o.order_no, o.amount FROM fgedu_users u JOIN fgedu_orders o ON u.id = o.user_id WHERE u.status = ‘active’ AND o.created_at > ‘2024-01-01’;
## 2. 分析过程
### 步骤1:查看执行计划
mysql> EXPLAIN SELECT u.name, o.order_no, o.amount FROM fgedu_users u JOIN fgedu_orders o ON u.id = o.user_id WHERE u.status = ‘active’ AND o.created_at > ‘2024-01-01’;
### 步骤2:分析执行计划输出
– id: 1
– select_type: SIMPLE
– table: u
– type: ALL
– possible_keys: PRIMARY
– key: NULL
– key_len: NULL
– ref: NULL
– rows: 1000000
– filtered: 10.00
– Extra: Using where
– id: 1
– select_type: SIMPLE
– table: o
– type: ref
– possible_keys: idx_user_id
– key: idx_user_id
– key_len: 4
– ref: fgedudb.u.id
– rows: 10
– filtered: 50.00
– Extra: Using where
### 步骤3:识别问题
– fgedu_users表使用全表扫描
– 未使用status索引
– 连接操作效率低
### 步骤4:优化方案
– 在fgedu_users表上创建status索引
– 在fgedu_orders表上创建(user_id, created_at)复合索引
– 调整查询语句,确保使用索引
### 步骤5:实施优化
mysql> ALTER TABLE fgedu_users ADD INDEX idx_status (status);
mysql> ALTER TABLE fgedu_orders ADD INDEX idx_user_id_created_at (user_id, created_at);
### 步骤6:验证优化效果
mysql> EXPLAIN SELECT u.name, o.order_no, o.amount FROM fgedu_users u JOIN fgedu_orders o ON u.id = o.user_id WHERE u.status = ‘active’ AND o.created_at > ‘2024-01-01’;
### 步骤7:分析优化后的执行计划
– id: 1
– select_type: SIMPLE
– table: u
– type: ref
– possible_keys: PRIMARY,idx_status
– key: idx_status
– key_len: 82
– ref: const
– rows: 100000
– filtered: 100.00
– Extra: NULL
– id: 1
– select_type: SIMPLE
– table: o
– type: ref
– possible_keys: idx_user_id,idx_user_id_created_at
– key: idx_user_id_created_at
– key_len: 12
– ref: fgedudb.u.id
– rows: 5
– filtered: 100.00
– Extra: Using where; Using index
## 3. 优化效果
– fgedu_users表使用ref访问类型,使用idx_status索引
– fgedu_orders表使用ref访问类型,使用idx_user_id_created_at索引
– 估计扫描行数显著减少
– 查询性能显著提升
4.3 性能分析与调优
## 1. 测试环境
– 数据库:TiDB 7.5.0
– 服务器:8核16GB
– 表:fgedu_orders(1000万行)
## 2. 测试场景
– 未优化的查询
– 优化后的查询
## 3. 性能测试
### 场景1:简单查询
– 未优化:SELECT * FROM fgedu_orders WHERE user_id = 1 AND status = ‘completed’;
– 响应时间:1.5秒
– 扫描行数:1000万行
– 执行计划:全表扫描
– 优化后:创建复合索引 (user_id, status)
– 响应时间:0.05秒
– 扫描行数:100行
– 执行计划:索引扫描
### 场景2:连接查询
– 未优化:SELECT u.name, o.order_no FROM fgedu_users u JOIN fgedu_orders o ON u.id = o.user_id WHERE u.status = ‘active’;
– 响应时间:2.0秒
– 扫描行数:1000万行
– 执行计划:全表扫描
– 优化后:创建索引 idx_status (status) 和 idx_user_id (user_id)
– 响应时间:0.1秒
– 扫描行数:10万行
– 执行计划:索引扫描
### 场景3:排序查询
– 未优化:SELECT * FROM fgedu_orders WHERE status = ‘completed’ ORDER BY created_at DESC;
– 响应时间:1.8秒
– 扫描行数:1000万行
– 执行计划:全表扫描 + 文件排序
– 优化后:创建复合索引 (status, created_at)
– 响应时间:0.15秒
– 扫描行数:100万行
– 执行计划:索引扫描 + 索引排序
## 4. 调优建议
– 为频繁查询的列创建索引
– 合理设计复合索引的列顺序
– 利用覆盖索引减少回表操作
– 优化SQL语句,确保使用索引
– 定期分析执行计划,及时调整优化策略
Part05-风哥经验总结与分享
5.1 最佳实践
TiDB执行计划与SQL优化的最佳实践:
- 执行计划分析最佳实践:
- 定期分析执行计划
- 关注访问类型和索引使用
- 检查行估计和额外信息
- 比较实际执行时间和估计时间
- SQL优化最佳实践:
- 为频繁查询的列创建索引
- 合理设计复合索引
- 使用覆盖索引减少回表操作
- 优化WHERE条件,确保使用索引
- 合理使用JOIN操作
- 监控与维护最佳实践:
- 开启慢查询日志
- 定期分析慢查询
- 监控系统资源使用情况
- 定期更新统计信息
- 定期重建索引
- 性能优化最佳实践:
- 平衡查询性能和写入性能
- 根据业务需求调整优化策略
- 优化系统参数
- 合理配置硬件资源
5.2 性能优化技巧
## 1. 执行计划分析技巧
– 使用EXPLAIN ANALYZE获取详细执行计划
– 关注type列,优先选择const、eq_ref、ref、range
– 检查key列,确保使用了合适的索引
– 分析rows列,评估扫描行数是否合理
– 注意Extra列,避免Using temporary和Using filesort
## 2. SQL语句优化技巧
– 避免SELECT *,只选择需要的列
– 合理使用WHERE条件,确保使用索引
– 优化JOIN操作,小表驱动大表
– 利用索引的有序性,优化ORDER BY操作
– 合理使用子查询,避免嵌套过深
## 3. 索引优化技巧
– 为频繁查询的列创建索引
– 合理设计复合索引的列顺序
– 使用覆盖索引减少回表操作
– 避免在索引列上使用函数
– 定期清理无用索引
## 4. 系统优化技巧
– 调整TiDB、TiKV、PD参数
– 优化操作系统参数
– 配置合适的硬件资源
– 建立完善的监控体系
## 5. 维护优化技巧
– 定期更新统计信息
– 定期重建索引
– 监控慢查询
– 分析执行计划变化
– 及时调整优化策略
5.3 常见问题与解决
## 1. 执行计划问题
### 问题1:全表扫描
– 症状:执行计划显示type为ALL
– 原因:未创建合适的索引,或查询条件不符合索引使用条件
– 解决:创建合适的索引,优化查询条件
### 问题2:索引未被使用
– 症状:执行计划显示key为NULL
– 原因:查询条件未包含索引列,或在索引列上使用了函数
– 解决:优化查询条件,确保包含索引列,避免在索引列上使用函数
### 问题3:统计信息过期
– 症状:执行计划估计行数与实际行数差异较大
– 原因:统计信息未及时更新
– 解决:定期更新统计信息,执行ANALYZE TABLE
### 问题4:临时表和文件排序
– 症状:执行计划显示Using temporary和Using filesort
– 原因:ORDER BY或GROUP BY的列未创建索引
– 解决:创建合适的索引,优化排序操作
## 2. SQL语句问题
### 问题1:慢查询
– 症状:查询响应时间长
– 原因:未使用索引,或索引设计不合理
– 解决:优化查询语句,创建合适的索引
### 问题2:复杂查询
– 症状:查询逻辑复杂,执行时间长
– 原因:子查询嵌套过深,或JOIN操作过多
– 解决:简化查询逻辑,拆分复杂查询
### 问题3:大事务
– 症状:事务执行时间长,占用系统资源
– 原因:事务包含过多操作
– 解决:拆分大事务,合理设置事务大小
## 3. 系统问题
### 问题1:系统资源不足
– 症状:查询性能下降,系统响应慢
– 原因:CPU、内存、IO等资源不足
– 解决:升级硬件,优化系统参数
### 问题2:参数配置不当
– 症状:查询性能未达到最佳
– 原因:系统参数配置不合理
– 解决:调整TiDB、TiKV、PD参数
### 问题3:监控缺失
– 症状:无法及时发现性能问题
– 原因:未建立完善的监控体系
– 解决:部署监控工具,设置合理的告警阈值
本文由风哥教程整理发布,仅用于学习测试使用,转载注明出处:http://www.fgedu.net.cn/10327.html
