怎么优化慢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(注意缓存一致性)
归档历史数据 删除或迁移冷数据,减少表大小
相关推荐
爱学java的ptt1 天前
mysql的存储引擎
数据库·mysql
小宇的天下1 天前
innovus Flip chip 产品设计方法(3)
数据库·windows·microsoft
GalenZhang8881 天前
使用 Python SDK 将数据写入飞书多维表格
数据库·python·飞书·多维表格
云和数据.ChenGuang1 天前
GaussDB 期末考试题与面试题
数据库·opengauss·gaussdb·数据库期末试题
不屈的铝合金1 天前
SQL 语言概述与数据库核心前置配置了解
数据库·sql·mysql·约束·sql 语句分类·字符集配置·校对规则
萧曵 丶1 天前
可重复读(Repeatable Read)隔离级别下幻读产生的原因
数据库·sql·mysql
Antoine-zxt1 天前
MySQL宕机日志迷局破解指南:从前台启动到精准排错
数据库·mysql·adb
松涛和鸣1 天前
DAY47 FrameBuffer
c语言·数据库·单片机·sqlite·html
阳宗德1 天前
基于CentOS Linux release 7.1实现了Oracle Database 11g R2 企业版容器化运行
linux·数据库·docker·oracle·centos