怎么优化慢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=JSON 或 EXPLAIN ANALYZE(MySQL 8.0+)获取更详细信息。


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

1. 缺少合适索引

  • 现象 :type=ALL,rows 很大
  • 优化 :
    • 为 WHERE、JOIN、ORDER BY、GROUP 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 IN、LIKE '%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(注意缓存一致性)
归档历史数据 删除或迁移冷数据,减少表大小
相关推荐
这个DBA有点耶39 分钟前
信创场景下数据同步软件怎么选?对比开源CDC与原生闭环工具的结构性差异
数据库·mysql·架构
多客:duokelijie1 小时前
代练订单系统选型思路:跨品类游戏接入、订单分层管理、自动结算逻辑拆解!
数据库·mysql·微信小程序·前端框架·uni-app·php·持续部署
PingCAP1 小时前
平凯星辰上榜!2026 年胡润全球独角兽企业
数据库
PingCAP1 小时前
轨道交通 AFC 系统升级,平凯 Loop 把跨部门协同变成可追溯闭环
数据库
java_logo3 小时前
Docker 部署 CloudBeaver:轻松搭建网页数据库管理平台
数据库·docker·容器·数据库管理·dbeaver·轩辕镜像·cloudbeaver
李白客4 小时前
AI 数据库落地 NL2SQL:准确率卡在四成,问题在表注释不在模型
数据库
FluxDB4 小时前
FluxDB vs DBX vs DBeaver:GPUI、Tauri、Eclipse 三种技术栈实测
数据库·mysql
杨航 AI4 小时前
JEV的用法
java·前端·数据库
数据库小学妹5 小时前
MySQL分页结果不稳定:唯一键兜底与游标分页
数据库·mysql·dba
风翼靓崽5 小时前
mysql数据备份/恢复
数据库·mysql