解决慢SQL问题
解决慢 MySQL 查询的核心思路是 “定位瓶颈 → 针对性优化”,需从“索引、SQL 语句、表结构、配置、硬件”5 个维度逐层排查,以下是可落地的实操步骤:
一、第一步:定位慢查询(找到“谁慢”)
先通过工具锁定慢查询 SQL,避免盲目优化:
- 开启 MySQL 慢查询日志
◦ 临时开启(重启失效):
SET GLOBAL slow_query_log = ON; – 开启慢查询日志
SET GLOBAL long_query_time = 1; – 记录执行时间 ≥1 秒的 SQL(根据业务调整阈值)
SET GLOBAL slow_query_log_file = ‘/var/lib/mysql/slow.log’; – 日志存储路径
◦ 永久开启:修改 my.cnf(Linux)或 my.ini(Windows),添加配置后重启 MySQL:
slow_query_log = ON
long_query_time = 1
slow_query_log_file = /var/lib/mysql/slow.log
2. 用工具分析慢查询日志
◦ 用 MySQL 自带的 mysqldumpslow 快速统计:
mysqldumpslow -s c -t 10 /var/lib/mysql/slow.log – 按执行次数(c)排序,取前10条慢SQL
mysqldumpslow -s t -t 10 /var/lib/mysql/slow.log – 按执行时间(t)排序,取前10条
◦ 用可视化工具(如 Percona Toolkit 的 pt-query-digest):更详细分析 SQL 执行频率、锁等待等。
- 用 EXPLAIN 分析 SQL 执行计划
对定位到的慢 SQL,加 EXPLAIN 查看执行计划,判断瓶颈(核心看 type、key、rows 字段):
EXPLAIN SELECT * FROM order WHERE user_id = 123 AND create_time > ‘2024-01-01’;
◦ 关键字段解读:
◦ type:索引使用类型(ALL 表示全表扫描,需优化;range/ref/eq_ref 为优);
◦ key:实际使用的索引(NULL 表示未用索引);
◦ rows:预估扫描的行数(数值越大,效率越低)。
二、第二步:针对性优化(解决“为什么慢”)
根据 EXPLAIN 结果,按“优先级从高到低”优化:
- 索引优化(最常见、性价比最高)
慢查询 80% 是“索引问题”,核心是“让 SQL 用上合适的索引”:
• 场景 1:未用索引(key 为 NULL)
◦ 原因:WHERE/JOIN/ORDER BY 字段无索引,或索引失效(如函数操作索引列、模糊查询 %xxx);
◦ 优化:
◦ 为 WHERE 高频过滤字段、JOIN 关联字段、ORDER BY 排序字段建索引(优先联合索引);
◦ 避免索引失效:如 WHERE DATE(create_time) = ‘2024-01-01’ → 改为 WHERE create_time BETWEEN ‘2024-01-01 00:00:00’ AND ‘2024-01-01 23:59:59’(函数操作索引列会失效)。
• 场景 2:用了索引但效率低(type 为 range/index,rows 大)
◦ 原因:索引区分度低(如 gender 字段)、联合索引未遵循“最左匹配”;
◦ 优化:
◦ 删除区分度低的索引(如 gender),改用联合索引;
◦ 调整联合索引字段顺序(过滤性高的字段放左侧,如 idx_user_create (user_id, create_time) 优于 idx_create_user (create_time, user_id))。
• 场景 3:需回表(Extra 有 Using filesort/Using temporary)
◦ 原因:查询字段未被索引覆盖,需回表查聚簇索引;
◦ 优化:用“覆盖索引”(查询字段包含在索引中),如 SELECT user_id, create_time FROM order 可建联合索引 idx_user_create (user_id, create_time),避免回表。
- SQL 语句优化(避免“低效写法”)
即使有索引,不合理的 SQL 也会变慢:
• 避免全表扫描:禁用 SELECT *,只查需要的字段(减少数据传输+支持覆盖索引);
• 优化 JOIN 操作:
◦ 小表驱动大表(LEFT JOIN 时,左表为小表);
◦ JOIN 字段必须建索引(避免笛卡尔积);
• 优化排序/分组:
◦ 让排序字段包含在索引中(避免 Using filesort),如 ORDER BY create_time 可结合 WHERE user_id = 123 建联合索引 idx_user_create (user_id, create_time);
◦ 禁用 GROUP BY 非索引字段(避免 Using temporary);
• 优化子查询:子查询改 JOIN(子查询可能重复执行,JOIN 更高效),如:
– 低效子查询
SELECT * FROM order WHERE user_id IN (SELECT id FROM user WHERE age > 30);
– 优化为 JOIN
SELECT o.* FROM order o JOIN user u ON o.user_id = u.id WHERE u.age > 30;
3. 表结构优化(解决“数据量大/结构不合理”)
• 分表分库:单表数据量 > 1000 万行时,拆分表减轻压力:
◦ 水平分表(按行拆分,如订单表按 create_time 分月分表 order_202401、order_202402);
◦ 垂直分表(按列拆分,如用户表拆为 user_base(基础信息)和 user_ext(扩展信息),减少宽表扫描);
• 优化字段类型:
◦ 用更小的字段类型(如 INT 替代 BIGINT,VARCHAR(20) 替代 VARCHAR(255));
◦ 禁用 TEXT/BLOB 存高频查询字段(可拆到单独表,或用前缀索引);
• 删除冗余字段:避免表中存在“可通过其他字段计算得出”的冗余数据(如 order_total 可由 order_detail 表汇总,无需存冗余)。
- 配置优化(提升 MySQL 本身性能)
根据服务器硬件调整 my.cnf 配置(需重启 MySQL):
• 缓存优化:
◦ innodb_buffer_pool_size:InnoDB 缓存池大小(建议设为物理内存的 50%~70%,如 16G 内存设为 10G,减少磁盘 IO);
◦ query_cache_size:查询缓存(MySQL 8.0 已移除,5.7 及以下若开启,需确保 query_cache_type=1,但不适用于高频更新表);
• 连接优化:
◦ max_connections:最大连接数(根据业务并发调整,如设为 1000,避免连接耗尽);
◦ wait_timeout:连接超时时间(设为 600 秒,释放闲置连接);
• IO 优化:
◦ innodb_flush_log_at_trx_commit:事务日志刷盘策略(非金融场景设为 2,平衡性能和安全性,避免每次提交都刷盘);
◦ innodb_log_file_size:事务日志文件大小(设为 2G,减少日志切换频率)。
- 硬件/架构优化(终极解决方案)
• 硬件升级:当软件优化到极限时,升级 CPU(多核心提升并发处理)、内存(增大缓存池)、SSD(提升磁盘 IO 速度,替代机械硬盘);
• 读写分离:用主从复制(Master 写,Slave 读),将查询压力分摊到从库(如订单查询走从库,下单走主库);
• 缓存引入:用 Redis 缓存高频查询数据(如商品详情、用户信息),避免每次都查 MySQL(如缓存 user_id=123 的用户信息,有效期 10 分钟)。
三、第三步:验证优化效果
优化后,需验证是否生效:
-
重新执行 EXPLAIN,确认 type 提升(如 ALL→ref)、rows 减少、key 用上目标索引;
-
执行 SHOW PROFILE 查看 SQL 执行耗时(如 Send data 时间减少);
-
观察慢查询日志,确认优化后的 SQL 不再被记录;
-
监控业务指标(如接口响应时间、MySQL 吞吐量),确认整体性能提升。
总结:优化优先级
-
优先索引优化(最快见效,成本最低);
-
其次 SQL 语句优化(调整写法,无侵入);
-
再到表结构/配置优化(需修改结构或配置,有一定成本);
-
最后硬件/架构优化(成本最高,适合大规模业务)。
核心原则:用最小的改动,解决最大的性能问题,避免“过度优化”。