发表时间: 2024-10-28 20:25:00+08:00 来自: shiyi 分类: 数据库 标签: MySQL

在后端开发中,SQL 性能常常是系统瓶颈之一。

一次不合理的查询,可能导致:

❌ 接口超时
❌ 数据库 CPU 飙升
❌ 锁等待加剧
❌ 整个系统雪崩

而一次合理的优化,则能让一个 2 秒的查询缩短到 20ms。

本文总结了 MySQL 最常用也最实用的查询优化技巧,覆盖慢查询排查、执行计划分析、索引优化、SQL 重写等场景。


一、开启慢查询日志(第一步先找到问题)

在优化 SQL 前,你必须知道“哪些 SQL 慢”。

在 MySQL 配置文件中启用慢查询:

slow_query_log = 1
slow_query_log_file = /var/log/mysql/slow.log
long_query_time = 1   # 超过 1 秒记录
log_queries_not_using_indexes = 1

然后查看:

mysqldumpslow slow.log

你会找到最常被执行、最慢的 SQL,按“执行次数”排序特别有用。


二、使用 EXPLAIN 分析执行计划

拿到 SQL 后,第一件事:

EXPLAIN SELECT * FROM user WHERE email = 'test@test.com';

重点关注:

字段 说明
type 访问类型(越往下越差)
key 使用的索引
rows 预计扫描行数
Extra 是否使用了覆盖索引/是否发生回表

最佳访问类型:

const > eq_ref > ref > range > index > ALL

出现 ALL = 全表扫描 → 必须优化。

rows 越低越好

rows 代表 MySQL 预估要扫描多少行。

例如:

rows = 450000   ❌
rows = 3        ✅

rows 高通常代表:

  • 没走索引
  • 条件不精准
  • 索引设计不合理

三、创建合适的索引(优化 SQL 的核心)

1. 精确匹配的字段需要索引

SELECT * FROM user WHERE email = 'xxx';

必须在 email 上建立索引:

ALTER TABLE user ADD INDEX idx_email(email);

2. 使用覆盖索引提高性能

覆盖索引 = 查询只需要索引中的字段,不需要回表。

SELECT id, username FROM user WHERE email = 'xxx';

如果 index(email, id, username),则无需回表,性能极高。


3. 尽量使用“最左前缀原则”的联合索引

联合索引:

INDEX(a, b, c)

可用于:

✅ a

✅ a + b

✅ a + b + c

不能用于:

❌ b

❌ b + c

设计索引时应该优先将区分度高的字段放前面

例如:

(性别, 年龄, 城市)

几乎没意义,因为性别只有两个值。


四、避免索引失效(非常关键)

索引常见失效场景:

1. 使用函数

WHERE DATE(create_time) = '2025-01-01'

优化为:

WHERE create_time >= '2025-01-01' 
  AND create_time < '2025-01-02'

2. like 前置通配符

WHERE name LIKE '%abc'

无法走索引。

优化:

WHERE name LIKE 'abc%'

3. 字段类型不一致

WHERE phone = 13333333333   -- phone 是 varchar

改为:

WHERE phone = '13333333333'

4. OR 条件未全走索引

WHERE email = 'a@test.com' OR phone = '13333333333'

其中一个没索引 → 整条都失效。

优化方法:

  • 给两个字段都加索引
  • 或拆成两条 SQL

五、优化分页查询(limit 100k, 20)

分页深度越大,越慢:

SELECT * FROM user LIMIT 100000, 20;

MySQL 实际做的是:

扫 100020 行 → 丢掉前 100000 行 → 返回后 20 行 非常慢

优化方法:

1. 覆盖索引 + 子查询

SELECT *
FROM user
WHERE id >= (
    SELECT id FROM user ORDER BY id LIMIT 100000, 1
)
LIMIT 20;

2. 使用“游标分页”(最推荐)

SELECT * FROM user
WHERE id > last_id
ORDER BY id
LIMIT 20;

性能可提升数十倍。


六、避免 SELECT *

SELECT * FROM orders;

问题:

  • 回表成本高
  • 占用带宽
  • 无法使用覆盖索引
  • 字段变更引发风险

替换为:

SELECT id, order_no, status FROM orders;

七、减少关联查询的数量

多表 JOIN 不一定慢,但 JOIN 太多一定慢。

推荐:一次查 ID → 批量 in 查询

例如订单 + 用户信息:

一条 SQL JOIN 三四张表:

SELECT *
FROM order o
JOIN user u ON o.user_id = u.id
JOIN ...

推荐方式:

  1. 先查订单列表
  2. 使用用户 ID 列表批量查询用户
SELECT * FROM user WHERE id IN (1, 2, 3, 4);

缓存友好、延迟低、可水平扩展。


八、常见 SQL 重写技巧

使用 EXISTS 替代 IN(大表场景)

SELECT * FROM product WHERE id IN (SELECT product_id FROM order_item);

改为:

SELECT * FROM product p WHERE EXISTS (
  SELECT 1 FROM order_item oi WHERE oi.product_id = p.id
);

更高效。


避免在 WHERE 中进行计算

WHERE amount * 2 > 100

改为:

WHERE amount > 50

避免负向条件

WHERE status != 1

改为:

WHERE status IN (2, 3, 4)

九、善用索引统计信息

SHOW INDEX FROM user;
SHOW TABLE STATUS LIKE 'user';

查看表大小、行数、索引分布,可以帮助你更好地优化。