SQL 优化先定位慢在哪里,再修改查询或索引。下面以 MySQL 8.0 / 8.4 为例,重点看扫描行数、返回数据量和执行计划。
先看执行计划
假设订单表有 id、user_id、created_at 三个字段,先检查一条真实的慢查询:
EXPLAINSELECT id, created_atFROM ordersWHERE user_id = 42ORDER BY created_at DESC, id DESCLIMIT 20;先看 key(使用的索引)、rows(预计扫描行数)和 Extra(排序、临时表等信息)。这些是优化器的估计,不是实际耗时。
MySQL 8.0.18 起支持 EXPLAIN ANALYZE,它会实际执行查询,并显示各步骤的时间和行数。先在能承受这次查询开销的环境中使用。MySQL 执行计划说明
只查询需要的列
列表只显示订单编号和创建时间,就明确写出这两列。避免把大文本、图片内容或无用字段一起传给客户端。
SELECT * 并非必然很慢;影响取决于行宽、数据量、索引和网络。比较修改前后结果时,应使用相同数据、参数和运行条件。
大偏移分页
LIMIT 100000, 10 需要找到并跳过前面的记录。只需按顺序翻页时,可以使用上一页最后一条记录的排序键继续查询:
SELECT id, created_atFROM ordersWHERE id > 100000ORDER BY idLIMIT 10;这里的 100000 是上一页最后一个 id,不是页码或偏移量,且假设按唯一主键升序翻页。按创建时间排序时,需要同时保存时间和唯一 ID 作为游标。任意跳页则需要另外设计,不能直接套用这条 SQL。
根据查询建立索引
索引应围绕过滤条件、关联条件和排序设计。例如前面的用户订单查询,可以评估 (user_id, created_at, id) 联合索引,再用执行计划确认效果。
索引也占空间,并增加写入维护成本。不要给每个字段都建索引。MySQL 索引说明
JOIN 是否需要拆开
JOIN 是正常的关系查询方式,不能统一替换成“禁止多表关联”。先检查关联条件是否完整、关联字段是否有合适索引、两侧过滤后还有多少行。
如果拆成多次查询,还要计入网络往返、应用内拼接和一致性成本。MySQL 查询优化说明
批量操作与字段类型
- 批量插入可以减少网络往返,但要限制批次大小,避免超大事务。
- 金额通常使用
DECIMAL;类型的精度和小数位由业务范围决定。 - 同一整数类型的
UNSIGNED不增加存储字节,只改变可表示的数值范围。 NULL表示缺失或未知,是否允许由数据含义决定,不应一律替换为0或空字符串。- 日期时间使用对应的时间类型,并统一时区约定。
验证修改
保留慢查询的参数、执行计划和耗时。在相同条件下对比修改前后,确认结果一致,再检查扫描行数、响应时间以及写入成本。慢查询日志用于发现候选 SQL;单次耗时下降不能代表所有请求都变快。
评论