MySQL SQL优化
2026/3/20大约 13 分钟
MySQL SQL 优化
一、SQL 优化概述
1.1 为什么需要 SQL 优化
1.2 SQL 优化的一般流程
二、慢查询日志
2.1 配置慢查询日志
-- 查看慢查询日志状态
SHOW VARIABLES LIKE '%slow_query%';
SHOW VARIABLES LIKE 'long_query_time';
-- 开启慢查询日志
SET GLOBAL slow_query_log = ON;
SET GLOBAL slow_query_log_file = '/var/log/mysql/slow.log';
SET GLOBAL long_query_time = 2; -- 超过2秒的查询记录
-- 记录没有使用索引的查询
SET GLOBAL log_queries_not_using_indexes = ON;
SET GLOBAL log_throttle_queries_not_using_indexes = 100; -- 每分钟最多记录100条
# 配置文件方式
[mysqld]
slow_query_log = 1
slow_query_log_file = /var/log/mysql/slow.log
long_query_time = 2
log_queries_not_using_indexes = 1
2.2 分析慢查询日志
# 使用 mysqldumpslow 分析
# 按执行时间排序,取前10条
mysqldumpslow -s t -t 10 /var/log/mysql/slow.log
# 按执行次数排序
mysqldumpslow -s c -t 10 /var/log/mysql/slow.log
# 按平均执行时间排序
mysqldumpslow -s at -t 10 /var/log/mysql/slow.log
# 常用参数
# -s: 排序方式
# t: 执行时间
# c: 执行次数
# l: 锁时间
# r: 返回记录数
# at/ac/al/ar: 平均值
# -t: 返回记录数
# -g: 正则匹配
2.3 使用 pt-query-digest
# 安装 percona-toolkit
yum install percona-toolkit
# 分析慢查询日志
pt-query-digest /var/log/mysql/slow.log
# 输出到文件
pt-query-digest /var/log/mysql/slow.log > slow_report.txt
# 分析指定时间范围
pt-query-digest --since '2024-01-01 00:00:00' --until '2024-01-02 00:00:00' slow.log
三、EXPLAIN 执行计划详解
3.1 EXPLAIN 基本使用
EXPLAIN SELECT * FROM users WHERE username = 'john';
+----+-------------+-------+------------+------+---------------+------+---------+------+------+----------+-------------+
| id | select_type | table | partitions | type | possible_keys | key | key_len | ref | rows | filtered | Extra |
+----+-------------+-------+------------+------+---------------+------+---------+------+------+----------+-------------+
| 1 | SIMPLE | users | NULL | ref | idx_username | idx_username | 202 | const | 1 | 100.00 | NULL |
+----+-------------+-------+------------+------+---------------+------+---------+------+------+----------+-------------+
3.2 字段详解
id 字段
-- id 相同:按顺序执行
EXPLAIN SELECT * FROM t1, t2 WHERE t1.id = t2.id;
-- id: 1, 1 (先执行 t1,再执行 t2)
-- id 不同:id 大的先执行
EXPLAIN SELECT * FROM t1 WHERE id IN (SELECT id FROM t2);
-- id: 1 (t1), 2 (t2) - 先执行子查询 t2
-- id 有 NULL:表示 UNION 的临时表
EXPLAIN SELECT * FROM t1 UNION SELECT * FROM t2;
select_type 字段
| 类型 | 说明 |
|---|---|
| SIMPLE | 简单查询,不包含子查询或 UNION |
| PRIMARY | 最外层查询 |
| SUBQUERY | 子查询中的第一个 SELECT |
| DERIVED | 派生表(FROM 子句中的子查询) |
| UNION | UNION 中的第二个及以后的 SELECT |
| UNION RESULT | UNION 的结果集 |
| DEPENDENT SUBQUERY | 相关子查询 |
type 字段(重要)
从优到差排序:
system > const > eq_ref > ref > range > index > ALL
-- const: 主键或唯一索引等值查询
EXPLAIN SELECT * FROM users WHERE id = 1;
-- eq_ref: 关联查询时,使用主键或唯一索引
EXPLAIN SELECT * FROM orders o JOIN users u ON o.user_id = u.id;
-- ref: 非唯一索引等值查询
EXPLAIN SELECT * FROM users WHERE status = 1;
-- range: 索引范围扫描
EXPLAIN SELECT * FROM users WHERE id > 100;
EXPLAIN SELECT * FROM users WHERE id IN (1, 2, 3);
EXPLAIN SELECT * FROM users WHERE created_at > '2024-01-01';
-- index: 全索引扫描
EXPLAIN SELECT id FROM users; -- id 是主键
-- ALL: 全表扫描(应避免)
EXPLAIN SELECT * FROM users WHERE age > 18; -- age 无索引
key_len 计算
-- key_len 用于判断复合索引使用了多少列
-- 示例:索引 idx_a_b_c (a, b, c)
-- a: INT NOT NULL (4 字节)
-- b: VARCHAR(20) NOT NULL utf8mb4 (20*4+2 = 82 字节)
-- c: INT NOT NULL (4 字节)
EXPLAIN SELECT * FROM t WHERE a = 1;
-- key_len = 4
EXPLAIN SELECT * FROM t WHERE a = 1 AND b = 'test';
-- key_len = 4 + 82 = 86
EXPLAIN SELECT * FROM t WHERE a = 1 AND b = 'test' AND c = 1;
-- key_len = 4 + 82 + 4 = 90
Extra 字段(重要)
-- Using index: 覆盖索引(好)
EXPLAIN SELECT id, username FROM users WHERE username = 'john';
-- Using where: 使用 WHERE 过滤(正常)
EXPLAIN SELECT * FROM users WHERE status = 1;
-- Using temporary: 使用临时表(需优化)
EXPLAIN SELECT DISTINCT username FROM users;
-- Using filesort: 文件排序(需优化)
EXPLAIN SELECT * FROM users ORDER BY age;
-- Using index condition: 索引条件下推 ICP(好)
EXPLAIN SELECT * FROM users WHERE username LIKE 'john%' AND age > 18;
-- Using join buffer: 连接缓冲(考虑添加索引)
EXPLAIN SELECT * FROM t1 JOIN t2 ON t1.col = t2.col;
-- Impossible WHERE: WHERE 条件永远不成立
EXPLAIN SELECT * FROM users WHERE 1 = 0;
-- Select tables optimized away: 使用聚合函数访问索引
EXPLAIN SELECT MIN(id) FROM users;
3.3 EXPLAIN FORMAT=JSON
EXPLAIN FORMAT=JSON
SELECT * FROM users WHERE username = 'john'\G
{
"query_block": {
"select_id": 1,
"cost_info": {
"query_cost": "1.00"
},
"table": {
"table_name": "users",
"access_type": "ref",
"possible_keys": ["idx_username"],
"key": "idx_username",
"used_key_parts": ["username"],
"key_length": "202",
"ref": ["const"],
"rows_examined_per_scan": 1,
"rows_produced_per_join": 1,
"filtered": "100.00",
"cost_info": {
"read_cost": "0.25",
"eval_cost": "0.10",
"prefix_cost": "1.00",
"data_read_per_join": "1K"
}
}
}
}
3.4 EXPLAIN ANALYZE(MySQL 8.0.18+)
EXPLAIN ANALYZE
SELECT * FROM users WHERE username = 'john';
-> Index lookup on users using idx_username (username='john')
(cost=0.35 rows=1)
(actual time=0.025..0.027 rows=1 loops=1)
-- cost: 预估成本
-- rows: 预估行数
-- actual time: 实际执行时间(首行时间..总时间)
-- rows: 实际返回行数
-- loops: 循环次数
四、SQL 改写优化
4.1 SELECT 优化
-- 1. 避免 SELECT *
-- 不好
SELECT * FROM users WHERE id = 1;
-- 好
SELECT id, username, email FROM users WHERE id = 1;
-- 2. 避免在 SELECT 中使用函数
-- 不好
SELECT DATE_FORMAT(created_at, '%Y-%m-%d') FROM orders;
-- 好(在应用层格式化)
SELECT created_at FROM orders;
-- 3. 使用 LIMIT 限制结果集
SELECT * FROM orders WHERE user_id = 1 ORDER BY created_at DESC LIMIT 10;
4.2 WHERE 优化
-- 1. 避免在索引列上使用函数
-- 不好
SELECT * FROM users WHERE YEAR(created_at) = 2024;
-- 好
SELECT * FROM users
WHERE created_at >= '2024-01-01' AND created_at < '2025-01-01';
-- 2. 避免隐式类型转换
-- 不好(phone 是 VARCHAR 类型)
SELECT * FROM users WHERE phone = 13800138000;
-- 好
SELECT * FROM users WHERE phone = '13800138000';
-- 3. 避免使用 OR,考虑使用 UNION
-- 不好
SELECT * FROM users WHERE username = 'john' OR email = 'john@example.com';
-- 好
SELECT * FROM users WHERE username = 'john'
UNION
SELECT * FROM users WHERE email = 'john@example.com';
-- 4. IN 子句优化
-- 不好(IN 列表过长)
SELECT * FROM users WHERE id IN (1, 2, 3, ..., 10000);
-- 好(使用临时表或 JOIN)
CREATE TEMPORARY TABLE tmp_ids (id BIGINT PRIMARY KEY);
INSERT INTO tmp_ids VALUES (1), (2), ...;
SELECT u.* FROM users u JOIN tmp_ids t ON u.id = t.id;
-- 5. LIKE 优化
-- 不好(无法使用索引)
SELECT * FROM users WHERE username LIKE '%john%';
-- 好(可以使用索引)
SELECT * FROM users WHERE username LIKE 'john%';
-- 如果必须前缀模糊,考虑全文索引
ALTER TABLE users ADD FULLTEXT INDEX ft_username(username);
SELECT * FROM users WHERE MATCH(username) AGAINST('john');
4.3 JOIN 优化
-- 1. 小表驱动大表
-- MySQL 优化器通常会自动选择,但可以用 STRAIGHT_JOIN 强制
-- 假设 departments 小,employees 大
-- 让小表作为驱动表
SELECT /*+ JOIN_ORDER(d, e) */ *
FROM departments d
JOIN employees e ON d.id = e.dept_id;
-- 2. 确保 JOIN 字段有索引
-- 被驱动表的关联字段必须有索引
CREATE INDEX idx_dept_id ON employees(dept_id);
-- 3. 避免 JOIN 过多表
-- 不好:一次 JOIN 7-8 个表
-- 好:拆分为多个查询,或使用临时表
-- 4. 使用 EXISTS 替代 IN(子查询结果集大时)
-- 不好
SELECT * FROM users WHERE id IN (SELECT user_id FROM orders);
-- 好
SELECT * FROM users u WHERE EXISTS (SELECT 1 FROM orders o WHERE o.user_id = u.id);
-- 5. 合理使用索引覆盖
SELECT e.id, e.name, d.name as dept_name
FROM employees e
JOIN departments d ON e.dept_id = d.id;
-- 创建覆盖索引
CREATE INDEX idx_emp_cover ON employees(dept_id, id, name);
4.4 ORDER BY 优化
-- 1. 利用索引排序
-- 如果 WHERE 条件和 ORDER BY 列能使用同一索引
CREATE INDEX idx_user_time ON orders(user_id, created_at);
SELECT * FROM orders
WHERE user_id = 1
ORDER BY created_at DESC;
-- 使用索引排序,无需 filesort
-- 2. 避免在 ORDER BY 中使用表达式
-- 不好
SELECT * FROM users ORDER BY YEAR(created_at);
-- 好
SELECT * FROM users ORDER BY created_at;
-- 3. 多字段排序确保排序方向一致
-- 好(都是 ASC 或都是 DESC)
SELECT * FROM users ORDER BY status ASC, created_at ASC;
-- 不好(排序方向不一致,可能无法使用索引)
SELECT * FROM users ORDER BY status ASC, created_at DESC;
-- MySQL 8.0 支持降序索引
CREATE INDEX idx_status_time ON users(status ASC, created_at DESC);
-- 4. ORDER BY + LIMIT 优化
-- 优化器会在找到足够行数后停止排序
SELECT * FROM users ORDER BY created_at DESC LIMIT 10;
4.5 GROUP BY 优化
-- 1. 利用索引进行分组
CREATE INDEX idx_status ON users(status);
SELECT status, COUNT(*) FROM users GROUP BY status;
-- 使用松散索引扫描(Loose Index Scan)
-- 2. 避免在 GROUP BY 中使用表达式
-- 不好
SELECT DATE(created_at), COUNT(*) FROM orders GROUP BY DATE(created_at);
-- 好(添加冗余列或使用生成列)
ALTER TABLE orders ADD COLUMN created_date DATE GENERATED ALWAYS AS (DATE(created_at));
CREATE INDEX idx_date ON orders(created_date);
SELECT created_date, COUNT(*) FROM orders GROUP BY created_date;
-- 3. 避免排序
-- GROUP BY 默认会排序,可以加 ORDER BY NULL 跳过
SELECT status, COUNT(*) FROM users GROUP BY status ORDER BY NULL;
4.6 LIMIT 分页优化
-- 问题:深度分页性能差
SELECT * FROM orders ORDER BY id LIMIT 1000000, 10;
-- 需要扫描前 100 万行
-- 优化方案1:使用主键范围查询
SELECT * FROM orders WHERE id > 1000000 ORDER BY id LIMIT 10;
-- 要求:id 连续,且按 id 排序
-- 优化方案2:延迟关联
SELECT o.*
FROM orders o
JOIN (SELECT id FROM orders ORDER BY id LIMIT 1000000, 10) t
ON o.id = t.id;
-- 子查询只访问索引,不回表
-- 优化方案3:记录上次位置
-- 第一页
SELECT * FROM orders ORDER BY id LIMIT 10;
-- 记录最后一条的 id = 10
-- 下一页
SELECT * FROM orders WHERE id > 10 ORDER BY id LIMIT 10;
-- 优化方案4:使用 LIMIT 配合 OFFSET 的书签方式
SELECT * FROM orders
WHERE (created_at, id) > ('2024-01-01 00:00:00', 12345)
ORDER BY created_at, id
LIMIT 10;
4.7 INSERT 优化
-- 1. 批量插入
-- 不好
INSERT INTO users (name) VALUES ('a');
INSERT INTO users (name) VALUES ('b');
INSERT INTO users (name) VALUES ('c');
-- 好
INSERT INTO users (name) VALUES ('a'), ('b'), ('c');
-- 2. 关闭自动提交,手动提交
SET autocommit = 0;
INSERT INTO users (name) VALUES ('a');
INSERT INTO users (name) VALUES ('b');
COMMIT;
-- 3. 使用 LOAD DATA 批量导入
LOAD DATA INFILE '/path/to/data.csv'
INTO TABLE users
FIELDS TERMINATED BY ','
LINES TERMINATED BY '\n';
-- 4. 按主键顺序插入
-- 避免页分裂
-- 5. 禁用索引(大量数据导入时)
ALTER TABLE users DISABLE KEYS;
-- 导入数据
ALTER TABLE users ENABLE KEYS;
4.8 UPDATE/DELETE 优化
-- 1. 避免更新主键
-- 主键更新会导致数据物理位置变化
-- 2. 小批量更新/删除
-- 不好
DELETE FROM logs WHERE created_at < '2020-01-01';
-- 好
REPEAT
DELETE FROM logs WHERE created_at < '2020-01-01' LIMIT 1000;
-- 休眠一下,避免长时间持有锁
UNTIL ROW_COUNT() = 0 END REPEAT;
-- 3. 避免在循环中执行 SQL
-- 不好(在应用代码中)
for id in ids:
cursor.execute("UPDATE users SET status = 0 WHERE id = %s", id)
-- 好
cursor.execute("UPDATE users SET status = 0 WHERE id IN (%s)", ','.join(ids))
五、子查询优化
5.1 子查询类型
-- 1. 标量子查询(返回单值)
SELECT (SELECT MAX(salary) FROM employees) as max_salary;
-- 2. 列子查询(返回一列)
SELECT * FROM users WHERE id IN (SELECT user_id FROM orders);
-- 3. 行子查询(返回一行)
SELECT * FROM users WHERE (id, name) = (SELECT id, name FROM admins LIMIT 1);
-- 4. 表子查询(返回表)
SELECT * FROM (SELECT * FROM orders WHERE amount > 100) t;
5.2 子查询优化策略
-- 1. 将子查询改写为 JOIN
-- 原始
SELECT * FROM users
WHERE id IN (SELECT user_id FROM orders WHERE amount > 100);
-- 优化
SELECT DISTINCT u.*
FROM users u
JOIN orders o ON u.id = o.user_id
WHERE o.amount > 100;
-- 2. 使用 EXISTS 替代 IN
-- 原始(当 orders 表很大时性能差)
SELECT * FROM users
WHERE id IN (SELECT user_id FROM orders);
-- 优化
SELECT * FROM users u
WHERE EXISTS (SELECT 1 FROM orders o WHERE o.user_id = u.id);
-- 3. 将相关子查询改写为 JOIN
-- 原始
SELECT
u.*,
(SELECT COUNT(*) FROM orders o WHERE o.user_id = u.id) as order_count
FROM users u;
-- 优化
SELECT u.*, COALESCE(t.order_count, 0) as order_count
FROM users u
LEFT JOIN (
SELECT user_id, COUNT(*) as order_count
FROM orders
GROUP BY user_id
) t ON u.id = t.user_id;
-- 4. 使用 CTE 提高可读性(MySQL 8.0+)
WITH order_stats AS (
SELECT user_id, COUNT(*) as order_count, SUM(amount) as total_amount
FROM orders
GROUP BY user_id
)
SELECT u.*, os.order_count, os.total_amount
FROM users u
LEFT JOIN order_stats os ON u.id = os.user_id;
六、SHOW PROFILE 分析
6.1 开启 Profile
-- 查看 Profile 状态
SHOW VARIABLES LIKE 'profiling';
-- 开启 Profile
SET profiling = 1;
-- 执行查询
SELECT * FROM users WHERE username = 'john';
-- 查看所有 Profile
SHOW PROFILES;
+----------+------------+------------------------------------------+
| Query_ID | Duration | Query |
+----------+------------+------------------------------------------+
| 1 | 0.00012575 | SELECT * FROM users WHERE username = ... |
+----------+------------+------------------------------------------+
-- 查看指定查询的详细 Profile
SHOW PROFILE FOR QUERY 1;
+----------------------+----------+
| Status | Duration |
+----------------------+----------+
| starting | 0.000020 |
| checking permissions | 0.000005 |
| Opening tables | 0.000015 |
| init | 0.000010 |
| System lock | 0.000005 |
| optimizing | 0.000008 |
| statistics | 0.000012 |
| preparing | 0.000008 |
| executing | 0.000002 |
| Sending data | 0.000025 |
| end | 0.000003 |
| query end | 0.000005 |
| closing tables | 0.000005 |
| freeing items | 0.000010 |
| cleaning up | 0.000005 |
+----------------------+----------+
-- 查看 CPU 和 I/O 信息
SHOW PROFILE CPU, BLOCK IO FOR QUERY 1;
6.2 Profile 重点关注
-- 重点关注以下状态的耗时:
Sending data -- 发送数据(包括读取数据),耗时长说明数据量大
Sorting result -- 排序,耗时长需要优化 ORDER BY
Creating tmp table -- 创建临时表,需要优化
Copying to tmp table -- 复制到临时表
七、性能诊断工具
7.1 Performance Schema
-- 开启 Performance Schema(默认开启)
SHOW VARIABLES LIKE 'performance_schema';
-- 查看耗时最长的 SQL
SELECT
DIGEST_TEXT,
COUNT_STAR,
SUM_TIMER_WAIT / 1000000000000 as total_time_sec,
AVG_TIMER_WAIT / 1000000000 as avg_time_ms,
SUM_ROWS_EXAMINED,
SUM_ROWS_SENT
FROM performance_schema.events_statements_summary_by_digest
ORDER BY SUM_TIMER_WAIT DESC
LIMIT 10;
-- 查看当前正在执行的 SQL
SELECT * FROM performance_schema.events_statements_current;
-- 查看锁等待
SELECT * FROM performance_schema.data_lock_waits;
7.2 sys Schema
-- MySQL 5.7+ 内置 sys 库,封装了 Performance Schema
-- 查看耗时最长的 SQL(格式化输出)
SELECT * FROM sys.statements_with_runtimes_in_95th_percentile;
-- 查看全表扫描的 SQL
SELECT * FROM sys.statements_with_full_table_scans;
-- 查看冗余索引
SELECT * FROM sys.schema_redundant_indexes;
-- 查看未使用的索引
SELECT * FROM sys.schema_unused_indexes;
-- 查看表的统计信息
SELECT * FROM sys.schema_table_statistics;
-- 查看 I/O 最多的文件
SELECT * FROM sys.io_global_by_file_by_bytes;
-- 查看等待事件
SELECT * FROM sys.wait_classes_global_by_avg_latency;
八、优化案例
8.1 案例一:索引优化
-- 原始 SQL(全表扫描)
SELECT * FROM orders
WHERE user_id = 1000
AND status = 'pending'
ORDER BY created_at DESC
LIMIT 10;
-- EXPLAIN 分析
+----+-------------+--------+------+---------------+------+---------+------+--------+-----------------------------+
| id | select_type | table | type | possible_keys | key | key_len | ref | rows | Extra |
+----+-------------+--------+------+---------------+------+---------+------+--------+-----------------------------+
| 1 | SIMPLE | orders | ALL | NULL | NULL | NULL | NULL | 100000 | Using where; Using filesort |
+----+-------------+--------+------+---------------+------+---------+------+--------+-----------------------------+
-- 优化:创建复合索引
CREATE INDEX idx_user_status_time ON orders(user_id, status, created_at);
-- 优化后
+----+-------------+--------+------+----------------------+----------------------+---------+-------------+------+-----------------------+
| id | select_type | table | type | possible_keys | key | key_len | ref | rows | Extra |
+----+-------------+--------+------+----------------------+----------------------+---------+-------------+------+-----------------------+
| 1 | SIMPLE | orders | ref | idx_user_status_time | idx_user_status_time | 90 | const,const | 10 | Using index condition |
+----+-------------+--------+------+----------------------+----------------------+---------+-------------+------+-----------------------+
8.2 案例二:分页优化
-- 原始 SQL(深度分页)
SELECT * FROM products
ORDER BY id
LIMIT 500000, 20;
-- 执行时间:2.5s
-- 优化方案:延迟关联
SELECT p.*
FROM products p
JOIN (
SELECT id FROM products ORDER BY id LIMIT 500000, 20
) t ON p.id = t.id;
-- 执行时间:0.3s
8.3 案例三:子查询优化
-- 原始 SQL(相关子查询)
SELECT
u.id,
u.username,
(SELECT COUNT(*) FROM orders o WHERE o.user_id = u.id) as order_count,
(SELECT SUM(amount) FROM orders o WHERE o.user_id = u.id) as total_amount
FROM users u
WHERE u.status = 1;
-- 执行时间:3.2s(假设 users 有 1 万行)
-- 优化:改写为 JOIN
SELECT
u.id,
u.username,
COALESCE(o.order_count, 0) as order_count,
COALESCE(o.total_amount, 0) as total_amount
FROM users u
LEFT JOIN (
SELECT user_id, COUNT(*) as order_count, SUM(amount) as total_amount
FROM orders
GROUP BY user_id
) o ON u.id = o.user_id
WHERE u.status = 1;
-- 执行时间:0.2s
8.4 案例四:JOIN 优化
-- 原始 SQL
SELECT o.*, u.username, p.product_name
FROM orders o
LEFT JOIN users u ON o.user_id = u.id
LEFT JOIN products p ON o.product_id = p.id
WHERE o.created_at > '2024-01-01'
ORDER BY o.created_at DESC
LIMIT 100;
-- EXPLAIN 显示 Using temporary; Using filesort
-- 优化步骤:
-- 1. 确保关联字段有索引
CREATE INDEX idx_user_id ON orders(user_id);
CREATE INDEX idx_product_id ON orders(product_id);
-- 2. 添加覆盖索引
CREATE INDEX idx_created ON orders(created_at, user_id, product_id);
-- 3. 使用延迟关联
SELECT o.*, u.username, p.product_name
FROM (
SELECT id, user_id, product_id
FROM orders
WHERE created_at > '2024-01-01'
ORDER BY created_at DESC
LIMIT 100
) t
JOIN orders o ON t.id = o.id
LEFT JOIN users u ON t.user_id = u.id
LEFT JOIN products p ON t.product_id = p.id;
九、优化检查清单
9.1 索引检查
□ WHERE 条件列是否有索引
□ JOIN 关联列是否有索引
□ ORDER BY 列是否有索引
□ 复合索引顺序是否正确
□ 是否存在冗余索引
□ 是否存在未使用的索引
□ 索引选择性是否足够高
9.2 查询检查
□ 是否使用 SELECT *
□ 是否存在索引失效场景
□ 是否有深度分页
□ 是否有相关子查询
□ JOIN 表数量是否过多
□ 是否有大的 IN 列表
□ LIMIT 是否合理
9.3 表设计检查
□ 字段类型是否合适
□ 是否需要反范式化
□ 是否需要分区
□ 是否需要分表
□ 主键设计是否合理
十、总结
SQL 优化核心原则
- 减少数据访问:只返回需要的列和行
- 减少扫描行数:合理使用索引
- 减少排序和临时表:优化 ORDER BY 和 GROUP BY
- 减少网络传输:批量操作,减少交互次数
常用优化手段
| 场景 | 优化方法 |
|---|---|
| 全表扫描 | 添加合适的索引 |
| 索引失效 | 改写 SQL,避免索引失效场景 |
| 深度分页 | 延迟关联或游标分页 |
| 慢 JOIN | 小表驱动大表,添加关联索引 |
| 慢子查询 | 改写为 JOIN |
| filesort | 创建覆盖索引 |
| 大批量操作 | 分批处理 |