SQL 优化先定位慢在哪里,再修改查询或索引。下面以 MySQL 8.0 / 8.4 为例,重点看扫描行数、返回数据量和执行计划。

先看执行计划

假设订单表有 id、user_id、created_at 三个字段,先检查一条真实的慢查询:

EXPLAIN
SELECT id, created_at
FROM orders
WHERE user_id = 42
ORDER BY created_at DESC, id DESC
LIMIT 20;

先看 key(使用的索引)、rows(预计扫描行数)和 Extra(排序、临时表等信息)。这些是优化器的估计,不是实际耗时。

MySQL 8.0.18 起支持 EXPLAIN ANALYZE,它会实际执行查询,并显示各步骤的时间和行数。先在能承受这次查询开销的环境中使用。MySQL 执行计划说明

只查询需要的列

列表只显示订单编号和创建时间,就明确写出这两列。避免把大文本、图片内容或无用字段一起传给客户端。

SELECT * 并非必然很慢;影响取决于行宽、数据量、索引和网络。比较修改前后结果时,应使用相同数据、参数和运行条件。

大偏移分页

LIMIT 100000, 10 需要找到并跳过前面的记录。只需按顺序翻页时,可以使用上一页最后一条记录的排序键继续查询:

SELECT id, created_at
FROM orders
WHERE id > 100000
ORDER BY id
LIMIT 10;

这里的 100000 是上一页最后一个 id,不是页码或偏移量,且假设按唯一主键升序翻页。按创建时间排序时,需要同时保存时间和唯一 ID 作为游标。任意跳页则需要另外设计,不能直接套用这条 SQL。

根据查询建立索引

索引应围绕过滤条件、关联条件和排序设计。例如前面的用户订单查询,可以评估 (user_id, created_at, id) 联合索引,再用执行计划确认效果。

索引也占空间,并增加写入维护成本。不要给每个字段都建索引。MySQL 索引说明

JOIN 是否需要拆开

JOIN 是正常的关系查询方式,不能统一替换成“禁止多表关联”。先检查关联条件是否完整、关联字段是否有合适索引、两侧过滤后还有多少行。

如果拆成多次查询,还要计入网络往返、应用内拼接和一致性成本。MySQL 查询优化说明

批量操作与字段类型

  • 批量插入可以减少网络往返,但要限制批次大小,避免超大事务。
  • 金额通常使用 DECIMAL;类型的精度和小数位由业务范围决定。
  • 同一整数类型的 UNSIGNED 不增加存储字节,只改变可表示的数值范围。
  • NULL 表示缺失或未知,是否允许由数据含义决定,不应一律替换为 0 或空字符串。
  • 日期时间使用对应的时间类型,并统一时区约定。

验证修改

保留慢查询的参数、执行计划和耗时。在相同条件下对比修改前后,确认结果一致,再检查扫描行数、响应时间以及写入成本。慢查询日志用于发现候选 SQL;单次耗时下降不能代表所有请求都变快。