浅谈MySQL性能优化:从SQL写法到分页优化

在高并发、大数据量的业务场景下,MySQL的性能优化至关重要。一个低效的SQL语句可能导致数据库CPU飙升、响应延迟,甚至引发服务雪崩。本文将深入剖析MySQL中常见的SQL性能问题,涵盖索引使用、子查询优化、分页策略、SELECT * 的危害 等核心知识点,结合执行计划(EXPLAIN)分析,提供可落地的优化方案,帮助开发者写出高效、稳定的SQL。


一、WHERE子句中 AND 与 OR 的索引使用:为何OR容易导致索引失效?

WHERE 子句是SQL查询的核心,其写法直接影响索引的使用效率。

1. AND 条件:索引友好,可高效使用复合索引

  • 原理AND 是"与"逻辑,多个条件必须同时满足。MySQL可以利用最左前缀原则,使用复合索引进行高效过滤。

  • 示例

    sql 复制代码
    SELECT * FROM users WHERE age = 25 AND city = 'Beijing';

    若存在复合索引 (age, city),MySQL可直接使用该索引快速定位数据,效率极高。

2. OR 条件:容易导致索引失效

  • 问题 :当 OR 连接的字段没有共同索引无法使用同一索引时,MySQL可能放弃使用索引,转而进行全表扫描。

  • 示例

    sql 复制代码
    SELECT * FROM users WHERE age = 25 OR city = 'Beijing';
    • 若只有 (age)(city) 单列索引,MySQL通常无法同时使用两个索引。
    • 优化器可能选择全表扫描,或使用 index_merge(索引合并),但性能仍不如复合索引。

3. 优化建议

  • 使用复合索引 :为 AND 条件创建复合索引。

  • 避免OR,改用UNION

    sql 复制代码
    -- 优化前(可能索引失效)
    SELECT * FROM users WHERE age = 25 OR city = 'Beijing';
    
    -- 优化后(分别使用索引)
    SELECT * FROM users WHERE age = 25
    UNION
    SELECT * FROM users WHERE city = 'Beijing' AND age != 25;
  • 使用IN替代OR (当值为离散值时):

    sql 复制代码
    SELECT * FROM users WHERE city IN ('Beijing', 'Shanghai', 'Guangzhou');

    IN 在大多数情况下能更好地利用索引。


二、IN 与 EXISTS:小表驱动大表的优化原则

INEXISTS 都用于子查询,但执行逻辑不同,性能差异显著。

1. IN:先执行子查询,生成结果集

  • 执行逻辑:先执行子查询,得到结果集(如用户ID列表),再用该列表在主查询中进行匹配。

  • 适用场景子查询结果集小

  • 示例

    sql 复制代码
    -- 查询购买过商品的用户(orders表小)
    SELECT * FROM users WHERE user_id IN (SELECT user_id FROM orders);

2. EXISTS:对主查询每行执行子查询

  • 执行逻辑:对主查询的每一行,执行一次子查询,判断是否存在匹配。

  • 适用场景主查询结果集小,子查询表大。

  • 示例

    sql 复制代码
    -- 查询有订单的用户(users表小)
    SELECT * FROM users u WHERE EXISTS (SELECT 1 FROM orders o WHERE o.user_id = u.user_id);

3. 优化原则:小表驱动大表(Join Buffer)

  • MySQL的嵌套循环连接(NLJ)算法中,外层表(驱动表)应尽可能小,以减少内层表的扫描次数。
  • IN :子查询是内层,主查询是外层 → 子查询结果小则高效
  • EXISTS :主查询是外层,子查询是内层 → 主查询结果小则高效

选择建议

  • 子查询返回少量数据 → 用 IN
  • 主查询返回少量数据 → 用 EXISTS
  • 始终使用 EXPLAIN 验证执行计划。

三、子查询 vs JOIN:为何建议用JOIN替代子查询?

子查询虽然逻辑清晰,但在性能上通常不如 JOIN

1. 子查询的性能问题

  • 无法使用索引优化:某些子查询(尤其相关子查询)可能导致MySQL无法有效使用索引。
  • 重复执行:相关子查询对主查询每行执行一次,效率低下。
  • 优化器限制:MySQL对复杂子查询的优化能力有限。

2. JOIN 的优势

  • 执行计划更优 :MySQL优化器对 JOIN 的处理更成熟,能选择更优的连接算法(如Hash Join、Block Nested Loop)。
  • 可利用索引:连接字段若有索引,可大幅提升性能。
  • 减少重复计算:一次性完成连接,避免重复扫描。

3. 优化示例

sql 复制代码
-- 低效:相关子查询
SELECT u.name, 
       (SELECT COUNT(*) FROM orders o WHERE o.user_id = u.id) as order_count
FROM users u;

-- 高效:JOIN + GROUP BY
SELECT u.name, COUNT(o.id) as order_count
FROM users u
LEFT JOIN orders o ON u.id = o.user_id
GROUP BY u.id, u.name;

建议

尽量将子查询重写为JOIN,尤其在涉及聚合或多表关联时。


四、LIMIT分页优化:大数据量下的性能挑战

传统分页 LIMIT m, n 在数据量大时性能急剧下降。

1. 问题:OFFSET 越大,性能越差

sql 复制代码
-- 查询第10000页,每页10条
SELECT * FROM articles ORDER BY created_at DESC LIMIT 99990, 10;
  • MySQL需扫描前99990行,仅返回10行,I/O和CPU开销巨大。

2. 优化方案

(1) 基于主键或索引分页(推荐)

利用已知的上一页最后一条记录的主键或排序字段,直接定位。

sql 复制代码
-- 第一页
SELECT * FROM articles ORDER BY id DESC LIMIT 10;

-- 第二页(假设上一页最后id=99991)
SELECT * FROM articles WHERE id < 99991 ORDER BY id DESC LIMIT 10;
  • 优点:利用索引,无需OFFSET,性能稳定。
  • 缺点:不支持跳页,需逐页浏览。

(2) 延迟关联(Deferred Join)

先通过索引获取主键,再回表查询完整数据。

sql 复制代码
-- 优化前
SELECT * FROM articles ORDER BY created_at DESC LIMIT 99990, 10;

-- 优化后
SELECT a.* 
FROM articles a
INNER JOIN (
    SELECT id FROM articles ORDER BY created_at DESC LIMIT 99990, 10
) AS b ON a.id = b.id;
  • 优点:子查询只扫描索引,减少回表次数。
  • 适用:无法使用主键分页的场景。

*五、避免 SELECT :性能杀手的真相

SELECT * 是SQL编写中最常见的反模式之一,对性能影响深远。

1. 对性能的负面影响

问题 说明
增加I/O开销 即使只需少数字段,也会读取整行数据,浪费磁盘和网络带宽。
降低缓存效率 Buffer Pool缓存更多无用数据,降低热点数据命中率。
无法利用覆盖索引 若查询字段均在索引中,可直接从索引获取数据(覆盖索引),避免回表。SELECT * 必然触发回表。
增加网络传输 传输大量无用字段,增加应用与数据库间的网络延迟。

2. 优化建议

  • 明确指定字段

    sql 复制代码
    -- ❌ 错误
    SELECT * FROM users WHERE id = 100;
    
    -- ✅ 正确
    SELECT id, name, email FROM users WHERE id = 100;
  • 利用覆盖索引:确保查询字段和条件字段均在索引中,避免回表。

  • 结合业务需求:只查询真正需要的字段,尤其在高并发接口中。


六、总结:MySQL性能优化 Checklist

优化点 推荐做法
WHERE条件 优先使用AND,避免OR;用INUNION替代低效OR
IN vs EXISTS 遵循"小表驱动大表"原则,用EXPLAIN验证
子查询 尽量重写为JOIN,避免相关子查询
分页查询 使用"基于主键分页"或"延迟关联"
SELECT字段 禁用SELECT *,明确指定字段
索引设计 合理创建复合索引,遵循最左前缀原则
执行计划 所有SQL必须通过EXPLAIN分析,关注typekeyrows

结语

MySQL性能优化是一个系统工程,既需要理解底层原理(如B+树、执行计划、索引机制),也需要在实践中不断打磨SQL写法。从避免SELECT *到优化分页查询,每一个细节都可能带来数量级的性能提升。养成良好的SQL编写习惯,善用EXPLAIN工具,才能在面对海量数据和高并发请求时,从容不迫,游刃有余。

相关推荐
ruleslol3 小时前
MySQL的段、区、页、行 详解
数据库·mysql
天若有情6733 小时前
校园二手交易系统实战开发全记录(vue+SpringBoot+MySQL)
vue.js·spring boot·mysql
奋进的芋圆3 小时前
DataSyncManager 详解与 Spring Boot 迁移指南
java·spring boot·后端
それども3 小时前
MySQL affectedRows 计算逻辑
数据库·mysql
是小章啊3 小时前
MySQL 之SQL 执行规则及索引详解
数据库·sql·mysql
计算机程序设计小李同学3 小时前
个人数据管理系统
java·vue.js·spring boot·后端·web安全
Echo娴4 小时前
Spring的开发步骤
java·后端·spring
追逐时光者4 小时前
TIOBE 公布 C# 是 2025 年度编程语言
后端·.net
Victor3564 小时前
Hibernate(32)什么是Hibernate的Criteria查询?
后端