怎么优化慢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有点耶2 分钟前
从INDEX到JOIN_ORDER:MySQL 8.0 Hint语法体系与五类实战场景
数据库·mysql·架构
数字新视界6 分钟前
机房动环监控助力智能管理
大数据·服务器·数据库·动环监控系统·动力与环境监控系统
梅菲塔亚10 分钟前
博客_Redis入门到实战
数据库·redis·缓存
abcyuu15 分钟前
用友 U8 Cloud 生产制造模块深度解析:从 BOM 建立到成本核算的技术实现
前端·数据库·制造
夜雪一千16 分钟前
MySQL 的ID用什么数据类型更好?主键ID选型实战
数据库·mysql
蓝胖的四次元口袋23 分钟前
Redis 知识梳理(3)
数据库·redis·缓存
一铭1119932 分钟前
海豹办公 1.9.15 跨端办公软件实践 文档兼容 Access 数据库与移动 WebView 排错
android·数据库·electron·开源软件·access
Lynne30944 分钟前
TSN 在变电站自动化通信网络升级中的应用价值与技术挑战
服务器·网络·数据库
弈栈录1 小时前
用户、会话与消息表应该如何设计
数据库·后端
内存漫游1 小时前
Checkpoint 与 Crash Recovery:数据库恢复机制入门
数据库