怎么优化慢SQL

优化慢 SQL 是数据库性能调优的核心工作之一。以下是系统性、实用的 慢 SQL 优化方法论,适用于 MySQL(尤其是 InnoDB 引擎),也部分适用于其他关系型数据库。


🔍 一、定位慢 SQL

1. 开启慢查询日志(Slow Query Log)

复制代码
-- 查看是否开启
SHOW VARIABLES LIKE 'slow_query_log';

-- 设置慢查询阈值(例如超过 1 秒记录)
SET GLOBAL long_query_time = 1;

-- 开启慢日志(临时)
SET GLOBAL slow_query_log = ON;

日志文件路径:/var/log/mysql/slow.log(Linux 默认)

建议:生产环境长期开启慢日志,配合 log_queries_not_using_indexes=ON 捕获无索引查询。

2. 使用 EXPLAIN 分析执行计划

复制代码
EXPLAIN SELECT * FROM users WHERE age > 30 ORDER BY name;

重点关注字段:

  • type:访问类型(ALL 表示全表扫描,最差;ref/range 较好;const 最佳)
  • key:实际使用的索引
  • rows:预估扫描行数(越小越好)
  • Extra:是否有 Using filesort(需排序)、Using temporary(建临时表)等性能杀手

💡 进阶:使用 EXPLAIN FORMAT=JSONEXPLAIN ANALYZE(MySQL 8.0+)获取更详细信息。


二、常见慢 SQL 原因 & 优化策略

1. 缺少合适索引

  • 现象type=ALLrows 很大
  • 优化
    • WHEREJOINORDER BYGROUP BY 涉及的字段加索引

    • 遵循最左前缀原则 (复合索引 (a,b,c) 可用于 a=1 AND b=2,但不能用于 b=2 单独查询)

    • 覆盖索引:索引包含所有查询字段,避免回表

      复制代码
      -- 查询只查 id 和 name,可建 (status, name) 索引实现覆盖
      SELECT id, name FROM users WHERE status = 1;

2. 索引失效

常见导致索引失效的操作:

  • 对字段使用函数或表达式:WHERE YEAR(create_time) = 2025
    → 改为范围查询:WHERE create_time BETWEEN '2025-01-01' AND '2025-12-31'
  • 隐式类型转换:WHERE user_id = '123'(user_id 是 INT)
  • 使用 !=NOT INLIKE '%xxx'(前导通配符)
  • OR 条件未全部命中索引(可用 UNION 替代)

3. 大量数据排序(Using filesort)

  • 原因ORDER BY 字段无索引,或与 WHERE 条件索引不匹配
  • 优化
    • ORDER BY 字段建索引(最好与 WHERE 条件组成联合索引)

    • 减少 SELECT *,只查必要字段(减少排序内存占用)

    • 分页深翻页问题:用「游标分页」替代 LIMIT 10000, 20

      复制代码
      -- 传统(慢):
      SELECT * FROM orders ORDER BY id LIMIT 10000, 20;
      
      -- 优化(快):
      SELECT * FROM orders WHERE id > 10000 ORDER BY id LIMIT 20;

4. 临时表(Using temporary)

  • 常见于GROUP BY + ORDER BY 不一致、子查询不当
  • 优化
    • 确保 GROUP BY 字段有索引
    • 避免在 SELECT 中使用非聚合字段(除非 ONLY_FULL_GROUP_BY 关闭)
    • 将复杂子查询改写为 JOIN

5. JOIN 性能差

  • 原则

    • 小表驱动大表(MySQL 通常自动优化)
    • 被驱动表(右表)必须有索引
    • 避免多表 JOIN(>3 张表慎用)
  • 示例

    复制代码
    -- orders 表大,users 表小 → users 驱动 orders
    SELECT * FROM users u
    JOIN orders o ON u.id = o.user_id
    WHERE u.status = 1;

    → 确保 orders.user_id 有索引!

6. 大分页(Deep Pagination)

  • 问题:LIMIT 1000000, 10 会扫描前 100 万行
  • 解决方案:
    • 记录上一页最大 ID,用 WHERE id > last_id LIMIT 10
    • 业务允许时,限制最大翻页深度(如只允许查前 100 页)

7. 锁竞争或事务过大

  • 长事务会阻塞其他操作,导致"看似慢查询"
  • 优化:
    • 缩短事务(及时 COMMIT)
    • 避免在事务中处理业务逻辑
    • 使用 SELECT ... FOR UPDATE 时确保有索引,否则锁全表!

三、高级优化手段

方法 说明
SQL 重写 将子查询改为 JOIN,拆分复杂查询
读写分离 读请求走从库,减轻主库压力
分库分表 数据量超千万级时考虑(如用户表按 user_id 分片)
缓存 高频查询结果缓存到 Redis(注意缓存一致性)
归档历史数据 删除或迁移冷数据,减少表大小
相关推荐
jiayou645 小时前
KingbaseES 实战:深度解析数据库对象访问权限管理
数据库
李广坤1 天前
MySQL 大表字段变更实践(改名 + 改类型 + 改长度)
数据库
爱可生开源社区2 天前
2026 年,优秀的 DBA 需要具备哪些素质?
数据库·人工智能·dba
随逸1772 天前
《从零搭建NestJS项目》
数据库·typescript
加号33 天前
windows系统下mysql多源数据库同步部署
数据库·windows·mysql
シ風箏3 天前
MySQL【部署 04】Docker部署 MySQL8.0.32 版本(网盘镜像及启动命令分享)
数据库·mysql·docker
李慕婉学姐3 天前
Springboot智慧社区系统设计与开发6n99s526(程序+源码+数据库+调试部署+开发环境)带论文文档1万字以上,文末可获取,系统界面在最后面。
数据库·spring boot·后端
百锦再3 天前
Django实现接口token检测的实现方案
数据库·python·django·sqlite·flask·fastapi·pip
tryCbest3 天前
数据库SQL学习
数据库·sql
jnrjian3 天前
ORA-01017 查找机器名 用户名 以及library cache lock 参数含义
数据库·oracle