SQL优化是数据库性能调优的核心环节,也是DBA和开发者的必备技能。最近在排查一个生产环境慢查询问题时,我发现很多团队对执行计划解读和索引策略存在严重认知偏差。这次实战经历让我意识到,真正的SQL优化需要从执行计划分析入手,结合业务场景制定精准的索引策略。
本文将分享一个真实案例:某电商平台订单查询接口从2秒优化到200毫秒的全过程。通过这个案例,你会掌握执行计划的正确解读方法、索引设计的黄金法则,以及如何避免常见的优化误区。这些经验适用于MySQL、Oracle等主流关系型数据库。
在MySQL中,最常用的方式是使用EXPLAIN命令。但很多人不知道的是,EXPLAIN其实有多个变种:
sql复制-- 基础版本(最常用)
EXPLAIN SELECT * FROM orders WHERE user_id = 100;
-- 显示更详细的执行计划(MySQL 8.0+)
EXPLAIN ANALYZE SELECT * FROM orders;
-- 以JSON格式输出完整信息
EXPLAIN FORMAT=JSON SELECT * FROM orders;
提示:在生产环境分析慢查询时,务必使用EXPLAIN ANALYZE获取实际执行数据,而不仅仅是预估计划。
执行计划中有几个关键字段需要特别关注:
type字段(访问类型):
possible_keys vs key:
rows:
Extra:
我们来看一个真实的生产案例。原始查询如下:
sql复制SELECT o.*, u.username
FROM orders o
JOIN users u ON o.user_id = u.id
WHERE o.create_time > '2023-01-01'
ORDER BY o.amount DESC
LIMIT 100;
执行计划显示:
问题诊断:
基于B+树索引的特性,我总结了几个核心原则:
针对前面的案例,我们设计以下索引:
sql复制-- 优化后的索引方案
ALTER TABLE orders ADD INDEX idx_query (create_time, amount, user_id);
ALTER TABLE users ADD INDEX idx_username (id, username);
这个设计考虑了:
优化后的执行计划显示:
查询性能从2s提升到200ms,提升幅度达10倍。
当查询条件包含多个独立索引时,MySQL可以使用Index Merge优化:
sql复制-- MySQL可能使用两个索引的合并
SELECT * FROM products
WHERE category_id = 5 OR price > 1000;
注意:这种优化并不总是有效,有时创建合适的复合索引更好。
MySQL 8.0+支持函数索引,可以优化包含函数的查询:
sql复制-- 创建函数索引
ALTER TABLE users ADD INDEX idx_email_lower ((LOWER(email)));
-- 查询使用函数索引
SELECT * FROM users WHERE LOWER(email) = 'user@example.com';
通过计算索引选择性来评估索引效果:
sql复制-- 计算某列的选择性
SELECT
COUNT(DISTINCT status)/COUNT(*) AS selectivity
FROM orders;
选择性越接近1,索引效果越好。通常建议选择性>0.1的列才考虑建索引。
隐式类型转换:
sql复制-- user_id是varchar类型时,这个查询不会使用索引
SELECT * FROM users WHERE user_id = 100;
使用否定条件:
sql复制-- NOT IN、!=等条件通常无法使用索引
SELECT * FROM products WHERE status != 'out_of_stock';
前导通配符:
sql复制-- LIKE '%xxx'无法使用索引
SELECT * FROM articles WHERE content LIKE '%优化%';
定期分析表:
sql复制ANALYZE TABLE orders;
监控未使用的索引:
sql复制-- 查询从未使用的索引(MySQL 8.0+)
SELECT * FROM sys.schema_unused_indexes;
索引重建策略:
ALTER TABLE ... ENGINE=InnoDB在线重建在实际工作中,我发现几个特别容易忽视的点:
批量导入时的索引处理:
分页查询优化:
sql复制-- 低效写法
SELECT * FROM orders LIMIT 10000, 20;
-- 优化写法(使用覆盖索引+延迟关联)
SELECT o.* FROM orders o
JOIN (SELECT id FROM orders ORDER BY id LIMIT 10000, 20) tmp
ON o.id = tmp.id;
统计信息的重要性:
索引监控脚本示例:
sql复制-- 查找冗余索引
SELECT * FROM sys.schema_redundant_indexes;
-- 查找可能缺失的索引
SELECT * FROM sys.schema_index_statistics
WHERE select_latency > 1000000;
通过这次优化实战,我深刻体会到SQL优化是一门需要理论与实践结合的技艺。每个数据库、每个业务场景都可能需要不同的优化策略。建议大家在掌握基本原理的基础上,多进行实际案例分析,积累自己的优化经验库。