SQL 调优是一个系统性工程,需要从发现问题 到解决问题的全流程掌握。下面从方法论到具体技巧详细讲解。
一、调优流程图
#mermaid-svg-gqtm0fChI78T9LYT{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-gqtm0fChI78T9LYT .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-gqtm0fChI78T9LYT .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-gqtm0fChI78T9LYT .error-icon{fill:#552222;}#mermaid-svg-gqtm0fChI78T9LYT .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-gqtm0fChI78T9LYT .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-gqtm0fChI78T9LYT .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-gqtm0fChI78T9LYT .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-gqtm0fChI78T9LYT .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-gqtm0fChI78T9LYT .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-gqtm0fChI78T9LYT .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-gqtm0fChI78T9LYT .marker{fill:#333333;stroke:#333333;}#mermaid-svg-gqtm0fChI78T9LYT .marker.cross{stroke:#333333;}#mermaid-svg-gqtm0fChI78T9LYT svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-gqtm0fChI78T9LYT p{margin:0;}#mermaid-svg-gqtm0fChI78T9LYT .label{font-family:"trebuchet ms",verdana,arial,sans-serif;color:#333;}#mermaid-svg-gqtm0fChI78T9LYT .cluster-label text{fill:#333;}#mermaid-svg-gqtm0fChI78T9LYT .cluster-label span{color:#333;}#mermaid-svg-gqtm0fChI78T9LYT .cluster-label span p{background-color:transparent;}#mermaid-svg-gqtm0fChI78T9LYT .label text,#mermaid-svg-gqtm0fChI78T9LYT span{fill:#333;color:#333;}#mermaid-svg-gqtm0fChI78T9LYT .node rect,#mermaid-svg-gqtm0fChI78T9LYT .node circle,#mermaid-svg-gqtm0fChI78T9LYT .node ellipse,#mermaid-svg-gqtm0fChI78T9LYT .node polygon,#mermaid-svg-gqtm0fChI78T9LYT .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-gqtm0fChI78T9LYT .rough-node .label text,#mermaid-svg-gqtm0fChI78T9LYT .node .label text,#mermaid-svg-gqtm0fChI78T9LYT .image-shape .label,#mermaid-svg-gqtm0fChI78T9LYT .icon-shape .label{text-anchor:middle;}#mermaid-svg-gqtm0fChI78T9LYT .node .katex path{fill:#000;stroke:#000;stroke-width:1px;}#mermaid-svg-gqtm0fChI78T9LYT .rough-node .label,#mermaid-svg-gqtm0fChI78T9LYT .node .label,#mermaid-svg-gqtm0fChI78T9LYT .image-shape .label,#mermaid-svg-gqtm0fChI78T9LYT .icon-shape .label{text-align:center;}#mermaid-svg-gqtm0fChI78T9LYT .node.clickable{cursor:pointer;}#mermaid-svg-gqtm0fChI78T9LYT .root .anchor path{fill:#333333!important;stroke-width:0;stroke:#333333;}#mermaid-svg-gqtm0fChI78T9LYT .arrowheadPath{fill:#333333;}#mermaid-svg-gqtm0fChI78T9LYT .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-gqtm0fChI78T9LYT .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-gqtm0fChI78T9LYT .edgeLabel{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-gqtm0fChI78T9LYT .edgeLabel p{background-color:rgba(232,232,232, 0.8);}#mermaid-svg-gqtm0fChI78T9LYT .edgeLabel rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-gqtm0fChI78T9LYT .labelBkg{background-color:rgba(232, 232, 232, 0.5);}#mermaid-svg-gqtm0fChI78T9LYT .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-gqtm0fChI78T9LYT .cluster text{fill:#333;}#mermaid-svg-gqtm0fChI78T9LYT .cluster span{color:#333;}#mermaid-svg-gqtm0fChI78T9LYT div.mermaidTooltip{position:absolute;text-align:center;max-width:200px;padding:2px;font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:12px;background:hsl(80, 100%, 96.2745098039%);border:1px solid #aaaa33;border-radius:2px;pointer-events:none;z-index:100;}#mermaid-svg-gqtm0fChI78T9LYT .flowchartTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-gqtm0fChI78T9LYT rect.text{fill:none;stroke-width:0;}#mermaid-svg-gqtm0fChI78T9LYT .icon-shape,#mermaid-svg-gqtm0fChI78T9LYT .image-shape{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-gqtm0fChI78T9LYT .icon-shape p,#mermaid-svg-gqtm0fChI78T9LYT .image-shape p{background-color:rgba(232,232,232, 0.8);padding:2px;}#mermaid-svg-gqtm0fChI78T9LYT .icon-shape .label rect,#mermaid-svg-gqtm0fChI78T9LYT .image-shape .label rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-gqtm0fChI78T9LYT .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#mermaid-svg-gqtm0fChI78T9LYT .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#mermaid-svg-gqtm0fChI78T9LYT :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} 否
是
是
否
发现慢查询
使用 EXPLAIN 分析
是否走索引?
优化索引设计
扫描行数是否过多?
优化查询条件
考虑数据量是否过大
分库分表/读写分离
二、发现问题:定位慢查询
1. 开启慢查询日志
sql
-- 查看慢查询配置
SHOW VARIABLES LIKE 'slow_query%';
SHOW VARIABLES LIKE 'long_query_time';
-- 开启慢查询日志
SET GLOBAL slow_query_log = ON;
SET GLOBAL long_query_time = 1; -- 超过1秒的记录
-- 查看慢查询日志
mysqldumpslow -s t -t 10 /var/lib/mysql/slow-query.log
2. 查看正在执行的慢查询
sql
-- 查看当前正在执行的所有查询
SHOW PROCESSLIST;
-- 找出执行时间长的
SELECT * FROM information_schema.PROCESSLIST
WHERE TIME > 5 AND COMMAND != 'Sleep'
ORDER BY TIME DESC;
三、分析问题:使用 EXPLAIN
1. EXPLAIN 基本用法
sql
EXPLAIN SELECT * FROM users WHERE name = '张三'\G
-- 输出关键字段
| 字段 | 说明 | 好的信号 | 坏的信号 |
|---|---|---|---|
| type | 访问类型 | const/ref/range | ALL(全表扫描) |
| possible_keys | 可能用的索引 | 有候选 | NULL |
| key | 实际用的索引 | 有值 | NULL |
| rows | 扫描行数 | 小 | 大 |
| Extra | 额外信息 | Using index | Using filesort |
2. 关注 Extra 字段
sql
-- ✅ 好
Using index -- 覆盖索引,不需要回表
Using index condition -- 索引下推
-- ⚠️ 需要优化
Using filesort -- 需要额外排序
Using temporary -- 用了临时表
四、解决问题:核心优化技巧
1. 索引优化
sql
-- 为 WHERE 条件建索引
CREATE INDEX idx_name ON users(name);
-- 为 ORDER BY 建索引
CREATE INDEX idx_create_time ON orders(create_time);
-- 复合索引注意最左前缀
CREATE INDEX idx_name_age ON users(name, age);
2. 避免 SELECT *
sql
-- ❌ 不好
SELECT * FROM users WHERE name = '张三';
-- ✅ 好(只查需要的字段)
SELECT id, name FROM users WHERE name = '张三';
3. 避免在索引列上使用函数
sql
-- ❌ 无法使用索引
SELECT * FROM orders WHERE YEAR(create_time) = 2024;
-- ✅ 可以走索引
SELECT * FROM orders
WHERE create_time >= '2024-01-01'
AND create_time < '2025-01-01';
4. 分页优化
sql
-- ❌ 深分页问题
SELECT * FROM orders ORDER BY id LIMIT 100000, 10;
-- ✅ 使用游标分页
SELECT * FROM orders
WHERE id > 100000
ORDER BY id
LIMIT 10;
5. JOIN 优化
sql
-- 小表驱动大表
-- 为 JOIN 字段建索引
CREATE INDEX idx_user_id ON orders(user_id);
五、高级优化技巧
1. 使用覆盖索引
sql
-- 创建包含所有查询字段的索引
CREATE INDEX idx_covering ON users(name, age, id);
-- 查询可以直接从索引获取数据
SELECT id, name, age FROM users WHERE name = '张三';
-- Extra: Using index
2. 合理使用 EXISTS 替代 IN
sql
-- IN 在大数据量时可能慢
SELECT * FROM users
WHERE id IN (SELECT user_id FROM orders WHERE amount > 1000);
-- EXISTS 可能更快
SELECT * FROM users u
WHERE EXISTS (SELECT 1 FROM orders o
WHERE o.user_id = u.id AND o.amount > 1000);
3. 批量操作优化
sql
-- 批量插入
INSERT INTO users (name) VALUES
('张三'), ('李四'), ('王五'); -- 一次插入多条
-- 批量更新使用临时表
CREATE TEMPORARY TABLE temp_updates (
id INT PRIMARY KEY,
age INT
);
六、监控和验证
1. 查看索引使用情况
sql
-- 查看索引使用次数
SELECT
index_name,
rows_selected,
rows_inserted
FROM performance_schema.table_io_waits_summary_by_index_usage
WHERE object_schema = 'db_name';
-- 查看从未使用的索引
SELECT * FROM sys.schema_unused_indexes;
2. 查看查询缓存命中率
sql
SHOW STATUS LIKE 'Qcache%';
SHOW STATUS LIKE 'Handler_read%';
七、总结:SQL 调优 Checklist
- 是否开启了慢查询日志?
- 是否用 EXPLAIN 分析了问题 SQL?
- WHERE 条件字段是否有索引?
- ORDER BY 字段是否有索引?
- 是否避免了 SELECT *?
- 是否避免了在索引列上使用函数?
- JOIN 字段是否有索引?
- 是否小表驱动大表?
- 分页是否过深?
- 是否有冗余或未用的索引?
一句话理解 :SQL 调优就像医生看病,先查症状(慢查询日志),再诊断病因(EXPLAIN),最后对症下药(索引优化、SQL重写)。