【MySQL】慢查寻的发现和解决优化(思维导图版)

"慢查询"是数据库领域的核心性能问题之一,指执行时间超过预设阈值(如 MySQL 的 long_query_time,默认 10 秒)的 SQL 语句。解决慢查询需遵循"定位问题 → 分析根源 → 优化执行 → 验证效果 "的闭环流程,具体方案可从 查询本身、索引、数据库配置、架构设计 四个维度展开。

EXPLAIN

通过日志工具定位到慢查询后,需用 EXPLAIN 命令分析其 执行计划 ,明确慢的原因。EXPLAIN 会展示查询的索引使用、表连接方式、数据扫描量等关键信息。

①、id 列:查询的标识符。

  • id 越大越先执行;相同 id 按从上到下顺序执行

复杂子查询可考虑改为 JOIN,减少嵌套层级

②、select_type 列:查询的类型。常见的类型有:

  • SIMPLE:简单查询,不包含子查询或者 UNION 查询。
  • PRIMARY:查询中如果包含子查询,则最外层查询被标记为 PRIMARY。
  • SUBQUERY:子查询。
  • DERIVED:派生表的 SELECT,FROM 子句的子查询。
  • 出现 DERIVED(衍生表)可能产生临时表;

  • 多层 SUBQUERY 可能影响性能

避免过度使用子查询,优先用 JOIN 替代

③、table 列:查的哪个表。

④、type 列:访问类型(查询效率等级)表示 MySQL 在表中找到所需行的方式,性能从最优到最差分别为:system > const > eq_ref > ref > range > index > ALL。

  • system,表只有一行,一般是系统表,往往不需要进行磁盘 IO,速度非常快
  • const:表中只有一行匹配 ,或通过主键或唯一索引获取单行记录。性能非常高。
  • eq_ref:对于每个来自上一张表的记录,最多只返回一条匹配记录,通常用于多表关联且使用主键或唯一索引的查询。效率非常高,适合多表关联查询。
  • ref:使用非唯一索引或前缀索引查询的情况,返回符合条件的多行记录。通常用于普通索引或联合索引查询,效率较高,但不如 const 和 eq_ref。
  • range:只检索给定范围的行,使用索引来检索。在where语句中使用 bettween...and<><=in 等条件查询 type 都是 range
  • index:全索引扫描,即扫描整个索引而不访问数据行。
  • ALL:全表扫描,效率最低。

异常ALL(全表扫描)、index(全索引扫描);
理想constrefrange

确保 WHERE/JOIN 字段有索引,避免无过滤条件的查询

⑤、possible_keys 列:可能会用到的索引,但并不一定实际被使用。

⑥、key 列:实际使用的索引。NULL 表示未使用索引(即使 possible_keys 有值)检查索引是否被正确使用(如避免函数操作索引字段)

⑦、key_len 列:MySQL 决定使用的索引长度(以字节为单位)。判断查询使用了联合索引的哪些字段。联合索引 idx_columns(column1, column2),如果 key_len 等于 column1 的长度,表示查询只使用了 column1。调整联合索引顺序,确保过滤性强的字段在前

⑧、ref 列:用于与索引列比较的值来源。

  • const:表示常量,这个值是在查询中被固定的。例如在 WHERE column = 'value'中。
  • 一个或多个列的名称,通常在 JOIN 操作中,表示 JOIN 条件依赖的字段。
  • NULL,表示没有使用索引,或者查询使用的是全表扫描。

⑨、rows 列:估算查到结果集需要扫描的数据行数,原则上 rows 越少越好。数值远大于实际返回的行数,说明过滤条件差。优化 WHERE 条件,增加过滤性强的条件(如用精确匹配替代模糊匹配)。

⑩、Extra 列:附加信息。

  • Using index:表示用了覆盖索引。
  • Using where:表示使用了 WHERE 过滤。
  • Using temporary :表示使用了临时表来存储中间结果。

异常Using filesort(文件排序)、Using temporary(临时表);
理想Using index(覆盖索引)

  • Using filesort:为排序字段建索引;

  • Using temporary:避免非索引字段 GROUP BY

  • 优先实现 Using index(查询字段全在索引中)

示例 :一条慢查询的 EXPLAIN 结果若显示 type: ALLkey: NULLrows: 100000,说明是"未走索引的全表扫描",根源明确。

解决方案:从 4 个维度优化

维度 1:优化查询语句本身(最直接,成本最低)

多数慢查询是因 SQL 写法不规范导致:

  1. 避免全表扫描/全索引扫描

禁止无过滤条件的 SELECT *(如 SELECT * FROM user)。

  1. 优化分页查询

对于 LIMIT offset, size深度分页问题,不要直接 LIMIT 1000000, 20

延迟关联(Late Row Lookups)和书签(Seek Method)是两种优化分页查询的有效方法。

①、延迟关联

延迟关联适用于需要从多个表中获取数据且主表行数较多的情况。它首先从索引表中检索出需要的行 ID ,然后再根据这些 ID 去关联其他的表获取详细信息。

避免了每条id都要回表(查聚簇索引)

复制代码
SELECT e.id, e.name, d.details
FROM (
    SELECT id
    FROM employees
    ORDER BY id
    LIMIT 1000, 20
) AS sub
JOIN employees e ON sub.id = e.id
JOIN department d ON e.department_id = d.id;

②、书签(Seek Method)

书签方法通过记住上一次查询返回的最后一行的某个值,然后下一次查询从这个值开始,避免了扫描大量不需要的行。

复制代码
SELECT id, name
FROM users
WHERE id > last_max_id  -- 假设last_max_id是上一页最后一行的ID
ORDER BY id
LIMIT 20;
  1. join 优化

①、禁止无限制的 IN****子句(如 IN (1,2,...,10000)****),改用 JOIN****或分批查询。

低效:SELECT * FROM order WHERE user_id IN (SELECT id FROM user WHERE status=1);

优化:SELECT o.* FROM order o JOIN user u ON o.user_id = u.id WHERE u.status=1;

若 IN 中的值是离散的 "硬编码列表"(如 IN (1,2,...,10000)),则将 10000 个值拆分为多个小批次(如每批 500 个),执行多次 IN (1-500)IN (501-1000)...,避免单次 SQL 过长和匹配压力。

②、小表驱动大表

在执行 JOIN 操作时,应尽量让行数较少的表(小表)驱动行数较多的表(大表),这样建立连接的次数就少,查询速度就快了。

③、适当增加冗余字段

在某些情况下,通过在表中适当增加冗余字段来避免 JOIN 操作,可以提高查询效率,尤其是在高频查询的场景下。

④ 避免 CROSS JOIN**(笛卡尔积),必须加连接条件。**

  1. UNION 优化

**条件下推:**将 where、limit 等子句下推到 union 的各个子查询中,以便优化器可以充分利用这些条件进行优化。

复制代码
SELECT * FROM (
  SELECT * FROM A
  UNION
  SELECT * FROM B
) AS sub
WHERE sub.id = 1;

可以改写成:

复制代码
SELECT * FROM A WHERE id = 1
UNION
SELECT * FROM B WHERE id = 1;

通过将查询条件下推到 UNION 的每个分支中,每个分支查询都只处理满足条件的数据,减少了不必要的数据合并和过滤。

维度 2:优化索引(提升查询效率的核心)
  1. 有效使用
    • WHERE子句、ORDER BYGROUP BY以及 JOIN ... ON的字段考虑创建索引。避免 ORDER BY/GROUP BY 对非索引字段操作,会触发 Using filesort(内存/磁盘排序)
    • 多条件用联合索引(最左前缀原则)
    • 利用覆盖索引: 让SQL要查询的字段完全包含在索引中,避免回表
    • 启用索引下推,减少回表次数。
    • 使用前缀索引,减少体积
    • 范围查询(>, <, BETWEEN)的字段放在末尾,避免导致后续索引字段失效
  1. 避免索引失效:
    • 字段操作:避免在 WHERE 子句中对字段做"函数/运算操作"
    • 操作符:避免使用 != 或者 <> 操作符,尽可能使用 =><BETWEEN
    • 数据类型:隐式类型转换(如字符串不加引号)
    • 连接方式:避免 WHERE 子句用 OR 连接非索引字段,改用 UNION
    • 避免LIKE 以通配符开头(%xxx / %xxx%)
  1. 优化无效/低效索引
    • 删除"未被使用的索引"(可通过 sys.schema_unused_indexes 或慢查询日志排查)。
    • 合并"重复索引"(若 idx_a_b 已存在,idx_a 可能冗余)。
    • 对"区分度低的字段"(如 gender 只有 0/1)不建索引。
维度 3:数据库设计优化

也可以优化数据库配置,加大内存缓存,调整连接池参数等等,这个不了解。

  • 数据类型: 使用适当的数据类型(如用INT存数字,而不是VARCHAR)。
  • 范式与反范式: 在数据一致性和查询性能之间做权衡。有时可以适当做反范式设计,增加一些冗余字段来避免复杂的JOIN
维度 4:架构层面优化(解决根本性、高并发问题)

当单库单表性能达到瓶颈(如数据量超 1000 万行,并发 QPS 超 1 万),需从架构层面拆分压力:

  1. 读写分离

主库负责"写操作",从库负责"读操作",通过主从复制同步数据。

  1. 分库分表

当单表数据量过大(如超 5000 万行),索引和查询效率会下降,需拆分表:

  1. 引入缓存

将高频访问的"热点数据"缓存到内存中,适合"读多写少"的数据。

  1. 使用列存数据库/搜索引擎
  • 对"海量数据分析场景"(如日志分析、报表统计),传统行存数据库(MySQL/PostgreSQL)效率低,可改用列存数据库(如 ClickHouse)。
  • 对"全文检索场景"(如商品搜索、文章检索),用搜索引擎(如 Elasticsearch)替代数据库的 LIKE 查询,支持分词、高亮、排序等高级功能。
相关推荐
Penge66618 小时前
Go 接口编译期断言
后端
我是一颗柠檬18 小时前
【MySQL全面教学】MySQL面试高频考点汇总Day15(2026年)
数据库·后端·mysql·面试
橙淮19 小时前
并发编程(六)
java·jvm
拽着尾巴的鱼儿19 小时前
springboot openfeign 自定义feign 接口重试机制
java·spring boot·后端
白露与泡影19 小时前
2026大厂Java面试题大全!牛客网最新版
java·开发语言
Ceelog19 小时前
久坐党自救指南:屏幕前 8 小时,身体到底在经历什么
前端·后端
凯瑟琳.奥古斯特19 小时前
高阶子查询题目精炼
开发语言·数据库·python·职场和发展·数据库开发
身如柳絮随风扬19 小时前
数据库读写分离:从原理到实战,构建高并发系统
数据库·mysql
EntyIU20 小时前
JVM内存与GC笔记
java·jvm·笔记
XS03010620 小时前
并发编程 六
java·后端