MySQL梳理三:查询与优化

MySQL查询优化完整指南:从理论到实践

本文从MySQL查询的基础机制出发,深入探讨单表查询访问方法、联表查询策略、成本计算原理、基于规则的优化技术,最后通过实际案例展示慢SQL的诊断和优化过程。

目录


一、单表查询的访问方法

MySQL 的访问方法定义了查询语句的执行方式,类似于从起点到终点的路线选择。查询优化器根据条件、索引和统计信息选择最优访问方法,以最小化执行成本(I/O、CPU、内存等)。虽然查询结果相同,但不同访问方法的效率差异显著。

1.1 访问方法性能对比

不同访问方法的性能差异如下图所示,从左到右效率递减:
🚀 const
坐火箭
主键/唯一索引等值 🚄 ref
坐高铁
普通索引等值 🚌 ref_or_null
坐大巴
ref + NULL查询 🚗 range
开汽车
索引范围查询 🚲 index_merge
骑自行车
多索引合并 🛵 index
骑摩托
索引全扫描 🐢 ALL
坐乌龟
全表扫描

1.2 具体访问方法详解

1.2.1 const - 最高效访问
  • 描述: 通过主键或唯一二级索引与常数等值比较,定位单条记录
  • 性能: 效率最高("坐火箭"),成本为常数级别,无需扫描或回表
  • 适用场景: 主键或唯一索引的等值查询,联合索引需所有列等值匹配
  • 注意 : 唯一索引查询 NULL 值(如 key IS NULL)无法使用 const,因 NULL 可重复
  • 示例 : SELECT * FROM table WHERE id = 100; 使用主键,定位单条记录
1.2.2 ref - 普通索引等值查询
  • 描述: 使用普通二级索引进行等值查询,可能匹配多条记录,需回表获取完整记录
  • 性能 : 效率次于 const("坐高铁"),适合选择性高的索引,成本随匹配记录数增加
  • 适用场景: 普通二级索引或联合索引最左连续列等值匹配,包括 NULL 值查询
  • 示例 : SELECT * FROM table WHERE key1 = 'abc'; 使用普通索引 idx_key1
1.2.3 ref_or_null - 扩展NULL查询
  • 描述 : 扩展 ref,额外查找索引列为 NULL 的记录
  • 性能 : 效率略低于 ref,因需处理 NULL 检查
  • 适用场景 : 等值查询结合 NULL 检查,如 WHERE key1 = 'abc' OR key1 IS NULL
  • 注意 : 若无 NULL 值,可能降级为 ref
1.2.4 range - 范围查询
  • 描述: 使用索引(聚簇或二级索引)进行范围查询,条件对应数轴上的区间(单点或连续区间)
  • 性能 : 效率依赖范围大小,选择性高时接近 ref,需回表
  • 适用场景 : 支持 >, <, IN, BETWEEN, LIKE(前缀匹配)等操作符
  • 示例 : SELECT * FROM table WHERE key1 IN ('a', 'b'); 生成单点区间
1.2.5 index_merge - 多索引合并
  • 描述: 使用多个二级索引合并结果,分为三种算法:

    • Intersection: 取索引结果交集,适用于等值匹配或主键范围匹配,需按主键排序
    • Union: 取索引结果并集,适用于 OR 连接的等值匹配
    • Sort-Union: 范围查询结果按主键排序后取并集,成本略高
  • 性能 : 效率低于单索引查询(range/ref),因涉及多索引操作和回表,但优于全表扫描

  • 适用场景 : 复杂条件涉及多个索引,如 WHERE key1 = 'a' OR key2 = 'b'

  • 注意: 优化器根据成本选择是否使用,联合索引可替代以降低成本

1.2.6 index - 索引全扫描
  • 描述: 直接扫描二级索引全部叶子节点,查询列和条件均包含在索引中,免回表
  • 性能: 优于全表扫描,因索引记录小,但仍需全索引扫描
  • 适用场景 : 查询列和条件全在索引中,如 SELECT key1 FROM table WHERE key1 = 'a';
  • 注意: 需确保查询不涉及非索引列
1.2.7 all - 全表扫描
  • 描述: 扫描整个聚簇索引,逐行检查条件
  • 性能: 效率最低("坐乌龟"),扫描全表记录,I/O 成本高
  • 适用场景: 无索引可用或条件选择性低
  • 示例 : SELECT * FROM table WHERE non_indexed_col = 1;

1.3 重要注意事项

二级索引与回表

通常使用单个二级索引,先定位记录,再回表获取完整数据。非索引条件在回表后过滤。

范围区间确定
  • AND 连接取交集,OR 连接取并集
  • 无法使用索引的条件替换为 TRUE,简化区间计算
  • 复杂条件需逐一分析,提取有效区间
联合索引优化

多列查询可通过联合索引替代 index_merge,但需平衡其他查询对单列索引的需求。

优化器决策

优化器基于统计信息(ANALYZE TABLE 更新)选择最低成本访问方法,通过合理设计索引和查询,MySQL 优化器能选择高效访问方法,显著提升查询性能。


二、联表查询机制

2.1 联表查询的本质

联表查询将多个表的记录组合成笛卡尔积 ,通过过滤条件生成结果集。笛卡尔积是各表记录逐一匹配形成的组合,记录数为各表行数的乘积(如两表各 100 行,生成 100×100=10000 行)。MySQL 语法简单,在 FROM 子句列出表名即可,但需通过过滤条件(涉及表间比较,如 table1.col1 = table2.col2)控制结果集规模。

2.2 联表查询的分类

2.2.1 内连接(INNER JOIN)
  • 仅保留符合连接条件和过滤条件的记录组合,驱动表与被驱动表可互换
  • 示例 : SELECT * FROM table1 INNER JOIN table2 ON table1.col1 = table2.col2; 仅返回匹配记录(默认的联表查询)
2.2.2 外连接(OUTER JOIN)
  • 驱动表记录即使无匹配也保留,被驱动表字段补 NULL
  • 左外连接 : 左表为驱动表,SELECT * FROM table1 LEFT JOIN table2 ON ... [WHERE ...]
  • 右外连接 : 右表为驱动表,SELECT * FROM table1 RIGHT JOIN table2 ON ...

2.3 联表查询执行过程

联表查询由优化器驱动,执行过程如下图所示:
是 否 开始联表查询 选择驱动表
(根据成本估算) 执行驱动表查询
(使用单表访问方法) 获得驱动表结果集
(N条记录) 对每条驱动表记录 查询被驱动表
(应用连接条件) 应用WHERE条件过滤 将匹配结果加入结果集 还有更多
驱动表记录? 返回最终结果集 优化策略 为被驱动表添加索引 使用join buffer缓存 选择小表做驱动表

查询驱动表:

  • 选择第一个表(驱动表),使用单表访问方法(如 const, ref, all)执行查询,获取符合单表条件的记录
  • 示例: SELECT * FROM table1, table2 WHERE table1.col1 > 10 AND table1.col1 = table2.col2; 先查询 table1 满足 col1 > 10 的记录。

查询被驱动表:

  • 对驱动表结果集的每条记录,查询被驱动表,应用两表条件(如 table1.col1 = table2.col2)和被驱动表单表条件
  • 被驱动表访问次数等于驱动表结果集记录数

2.4 连接算法与优化

2.4.1 嵌套循环连接(Nested Loop Join)
  • 驱动表查询一次,被驱动表查询多次(次数等于驱动表结果集记录数)
  • 多表连接时,上一轮结果集作为新驱动表,重复过程
  • 特点: 简单但效率低,被驱动表可能多次全表扫描
  • 性能: 成本随驱动表记录数和被驱动表访问方式增加
2.4.2 索引优化策略
  • 被驱动表查询可利用索引加速,参考常见访问方法
  • 建议 : 为被驱动表的关键列(如连接条件中的列)添加索引,确保选择性高,查询列表仅包含必要列以触发 index
2.4.3 基于块的嵌套循环连接(Block Nested Loop Join)
  • 被驱动表数据量大时,多次全表扫描导致高 I/O 成本
  • 采用 join buffer(默认 256KB,可通过 join_buffer_size 调整)缓存驱动表结果集记录
  • 具体过程
    1. 将驱动表查询列和条件放入 join buffer
    2. 扫描被驱动表,记录一次性与 join buffer 中多条记录匹配
    3. 减少被驱动表 I/O,最佳情况为 join buffer 容纳所有驱动表记录,仅需扫描被驱动表一次

三、查询成本计算

3.1 MySQL 查询成本构成

MySQL 查询的执行成本主要分为I/O成本和CPU成本:

3.1.1 I/O 成本

从磁盘加载数据或索引页面到内存的耗时,以页面(page)为基本单位,默认页面大小为 16KB,成本常数 默认为 1.0,出现在涉及表数据或索引的加载这样的场景中,例如全表扫描或索引范围查询。

3.1.2 CPU 成本

读取记录、检测搜索条件、排序等操作的耗时,读取并检测一条记录是否符合条件的默认成本为 0.2(无论是否需要条件检测),主要包括条件比较、结果集排序等。

3.1.3 成本常数

1.0(页面读取)和 0.2(记录检测)是默认值,硬编码在 MySQL 源码中,部分微调值(如 1.10.01)用于调整成本估算。

3.2 单表查询成本计算流程

MySQL 查询优化器在执行单表查询前会评估所有可能执行方案的成本,选择成本最低的方案作为执行计划
开始单表查询成本计算 分析搜索条件 找出可能使用的索引 计算全表扫描成本 计算各索引访问成本 考虑index_merge可能性 比较所有方案成本 选择成本最低方案 生成执行计划 成本构成 I/O成本 = 页面数 × 1.0 CPU成本 = 记录数 × 0.2 影响因素 表统计信息 索引选择性 条件过滤率 数据分布

具体步骤包括:

  • 根据搜索条件,找出可能使用的索引
  • 计算全表扫描的成本
  • 计算使用索引的成本
  • 考虑索引合并
  • 选择成本最低的执行方案

3.3 联表查询成本计算

总成本公式

复制代码
总成本 = 单次访问驱动表的成本 + 驱动表扇出 × 单次访问被驱动表的成本

其中:

  • 驱动表扇出: 驱动表查询结果的记录数
  • 当无法直接确定扇出时,优化器通过启发式规则(heuristic)估算剩余条件的过滤效果
3.3.1 联表查询成本评估示例

以内连接方式举例:

sql 复制代码
SELECT * FROM s1 INNER JOIN s2 ON s1.key1 = s2.common_field 
WHERE s1.key2 > 10 AND s1.key2 < 1000 AND s2.key2 > 1000 AND s2.key2 < 2000;

MySQL 优化器评估两种连接顺序(s1驱动s2或s2驱动s1),计算对应的总成本。

3.4 成本计算总结

MySQL 查询优化器通过比较全表扫描和索引访问的 I/O 和 CPU 成本,选择成本最低的执行计划。联表查询中,优化器评估不同连接顺序和访问方法,结合条件过滤和统计数据提高估算准确性。


四、基于规则的查询优化

MySQL 查询优化器通过基于规则的优化手段,将用户编写的复杂或低效查询语句重写为更高效的形式(包括条件化简、外连接消除和子查询优化等),以提升查询性能。

4.1 条件化简

MySQL 查询优化器会对查询中的搜索条件(表达式)进行化简,以减少计算复杂度和提高执行效率。

4.1.1 移除不必要的括号

优化器会移除查询中多余的括号,简化表达式,例如:

sql 复制代码
-- 原始查询
((a = 5 AND b = c) OR ((a > c) AND (c < 5)))

-- 优化后
(a = 5 AND b = c) OR (a > c AND c < 5)
4.1.2 常量传递

当某个列与常量等值匹配,并且与其他表达式通过 AND 连接时,优化器会将常量值传递到其他表达式中:

sql 复制代码
-- 原始查询
a = 5 AND b > a

-- 优化后
a = 5 AND b > 5
4.1.3 等值传递

当多个列之间存在等值关系时,优化器会将等值条件传递:

sql 复制代码
-- 原始查询
a = b AND b = c AND c = 5

-- 优化后
a = 5 AND b = 5 AND c = 5
4.1.4 移除无用条件

对于恒为 TRUE 或 FALSE 的条件,优化器会直接移除:

sql 复制代码
-- 原始查询
(a < 1 AND b = b) OR (a = 6 OR 5 != 5)

-- 优化后
a < 1 OR a = 6
4.1.5 表达式计算

对于只包含常量的表达式,优化器会在查询执行前计算其值:

sql 复制代码
-- 原始查询
a = 5 + 1

-- 优化后
a = 6

但对于涉及复杂函数或非单独列的表达式(如 ABS(a) > 5),优化器不会进行化简。

4.1.6 HAVING 和 WHERE 子句合并

如果查询不包含聚合函数(如 SUM、MAX)或 GROUP BY 子句,优化器会将 HAVING 子句合并到 WHERE 子句中。

4.2 外连接消除

外连接(LEFT/RIGHT JOIN)与内连接(INNER JOIN)的区别在于,驱动表的记录即使在被驱动表中找不到匹配记录,也会被保留(被驱动表字段填充为 NULL)。

4.2.1 空值拒绝(Null Rejection)

如果 WHERE 子句中指定被驱动表的列不为 NULL(如 t2.n2 IS NOT NULL),外连接效果等价于内连接:

sql 复制代码
-- 原始查询
SELECT * FROM t1 LEFT JOIN t2 ON t1.m1 = t2.m2 WHERE t2.n2 IS NOT NULL;

-- 等价于
SELECT * FROM t1 INNER JOIN t2 ON t1.m1 = t2.m2;

这种转换允许优化器调整连接顺序,降低查询成本。

4.3 子查询优化

子查询是嵌套在查询中的子查询,优化器通过物化表和半连接等策略优化其执行效率。

4.3.1 子查询出现位置
  • SELECT 子句 :如 (SELECT m1 FROM t1 LIMIT 1)
  • FROM 子句 :作为派生表,如 (SELECT m2 + 1 AS m, n2 AS n FROM t2 WHERE m2 > 2) AS t
  • WHERE 或 ON 子句 :如 WHERE m1 IN (SELECT m2 FROM t2)
  • ORDER BY 或 GROUP BY 子句:较少使用
4.3.2 子查询分类

按返回结果集分类

  • 标量子查询 :返回单一值(如 (SELECT m1 FROM t1 LIMIT 1)
  • 行子查询 :返回单条记录,包含多列(如 (SELECT m2, n2 FROM t2 LIMIT 1)
  • 列子查询 :返回单列多行(如 (SELECT m2 FROM t2)
  • 表子查询 :返回多行多列(如 (SELECT m2, n2 FROM t2)

按与外层查询关系分类

  • 不相关子查询:独立执行,不依赖外层查询值
  • 相关子查询 :执行依赖外层查询值,如 WHERE m1 IN (SELECT m2 FROM t2 WHERE n1 = n2)
4.3.3 子查询在布尔表达式中的使用
  • 比较操作符(=、>、<、等):与标量子查询或行子查询结合
  • IN/NOT IN、ANY/SOME、ALL:与列子查询或表子查询结合
  • EXISTS/NOT EXISTS:判断子查询结果集是否为空
4.3.4 子查询优化策略

MySQL优化器通过不同的策略优化各类子查询,主要优化路径如下:
子查询优化 标量/行子查询 IN子查询 EXISTS子查询 不相关子查询
先执行子查询 相关子查询
外层逐行执行 物化表策略 半连接策略 创建临时表
去重+索引 转换为内连接 子查询上拉 消除重复记录 松散索引扫描 首次匹配策略 物化后连接 转换为半连接
或保持EXISTS

主要优化策略说明

物化表(Materialization)

  • 将不相关 IN 子查询结果集写入临时表(物化表),对物化表记录去重,建立哈希索引(内存)或 B+ 树索引(磁盘)
  • 将外层查询与物化表进行内连接,由优化器评估连接顺序成本
  • 适用于子查询结果集较大时,物化表通过索引加速 IN 判断

半连接(Semi-join)

  • 将 IN 子查询转换为半连接,仅保留外层查询表的记录,不关心被驱动表匹配的记录数
  • 适用于子查询在 WHERE/ON 子句中,与 IN 语句结合,或者子查询为单一查询(无 UNION、GROUP BY、HAVING 等)
  • 不适用于子查询与 OR 连接、使用 NOT IN或者子查询在 SELECT 子句中

执行策略包括

  • 子查询查询列表为主键/唯一索引列时,将子查询表上拉到外层查询的 FROM 子句
  • 使用临时表记录外层查询记录 ID,消除重复
  • 对子查询表使用松散索引扫描,仅取索引值相同的首条记录
  • 逐条检查外层查询记录,找到第一条匹配的子查询记录即停止
  • 物化子查询后与外层查询表连接

4.4 优化规则总结

MySQL 查询优化器通过条件化简、外连接消除和子查询优化等基于规则的优化手段,将用户编写的低效查询转换为高效形式。这些规则包括移除冗余条件、合并子句、将外连接转为内连接、以及通过物化表和半连接优化 IN 子查询。开发者可以通过编写简洁的 SQL 语句、利用索引和主键、避免冗余操作等方式,进一步配合优化器提升查询性能。


五、执行计划分析

MySQL采用EXPLAIN分析SQL查询的执行计划,帮助优化查询性能。它展示MySQL如何执行查询,包括表访问顺序、索引使用情况等。

5.1 EXPLAIN 输出字段详解

运行EXPLAIN语句后,会返回一个结果集,包含多个字段:

5.1.1 查询标识字段
  • id :表示查询中每个子查询的执行顺序。值越大,优先级越高,越先执行。相同id表示同一执行层级
  • select_type :查询类型,常见值包括:
    • SIMPLE:简单查询,无子查询或联合
    • PRIMARY:最外层查询
    • SUBQUERY:子查询
    • DERIVED:派生表(如FROM子句中的子查询)
    • UNION:UNION操作中的查询
5.1.2 表和分区信息
  • table:显示查询涉及的表名或别名
  • partitions:显示查询涉及的分区(如果表分区了)。无分区表时为空
5.1.3 访问方法和索引信息
  • type:访问类型(即前面所说的访问方法),反映查询效率,常见值从优到劣:

    • system:表只有一行数据
    • const:通过主键或唯一索引直接定位一行
    • eq_ref:通过主键或唯一索引进行等值匹配
    • ref:通过非唯一索引进行等值匹配
    • range:索引范围扫描(如><IN
    • index:全索引扫描
    • ALL:全表扫描(效率最低)
  • possible_keys:MySQL可能使用的索引列表。为空表示没有可用索引

  • key:实际使用的索引。为空表示未使用索引

  • key_len:使用索引的长度(字节),帮助判断联合索引的使用情况。值越小,索引选择性越高

5.1.4 查询条件和统计信息
  • ref :显示与key索引比较的列或常量。例如,const表示常量值,表名.列名表示关联表的列
  • rows:估计扫描的行数,值越小越好,反映查询效率
  • filtered:表示过滤后的行数百分比(0-100%)。值越高,表示过滤效果越好
5.1.5 额外信息
  • Extra :额外信息,常见值:
    • Using index:仅使用索引完成查询(覆盖索引)
    • Using where:在WHERE条件中过滤
    • Using temporary:使用了临时表(可能影响性能)
    • Using filesort:需要额外排序(可能影响性能)
    • Using join buffer:使用了连接缓冲区

5.2 执行计划分析要点

执行计划分析时,需要重点关注以下几个关键字段:
EXPLAIN执行计划分析 关键字段重点关注 type字段
访问方法优先级 key字段
索引使用情况 rows字段
扫描行数估算 Extra字段
额外执行信息 ✅ const/eq_ref/ref
高效访问 ⚠️ range/index
中等效率 ❌ ALL
避免全表扫描 ✅ 有具体索引名
索引命中 ❌ NULL
未使用索引 ✅ 小数值
高效查询 ❌ 大数值
需要优化 ✅ Using index
覆盖索引 ⚠️ Using temporary
使用临时表 ⚠️ Using filesort
额外排序 ⚠️ Using where
需要回表

分析时着重点

  • 关注type,尽量避免ALLindex,优先consteq_refref
  • 检查keypossible_keys,确保使用合适的索引
  • 观察rowsfiltered,减少扫描行数
  • 注意Extra中的Using temporaryUsing filesort,可能需要优化

5.3 实际分析示例

通过分析这些字段,可定位查询瓶颈,优化索引或重写SQL以提高性能。


六、慢SQL治理实战案例

以下是5个经过脱敏处理的实际慢SQL案例,展示了从问题发现到优化解决的完整过程。

6.1 慢SQL治理流程图

慢SQL治理是一个系统性的过程,需要遵循科学的流程来确保优化效果:
慢SQL治理流程 1. 问题发现 2. 问题分析 3. 制定方案 4. 实施优化 5. 效果验证 6. 持续监控 监控告警
慢查询日志
性能指标 EXPLAIN分析 索引使用情况 数据量统计 并发场景分析 SQL改写优化 索引设计优化 架构调整优化 数据清理优化 去除前模糊匹配 改写子查询 优化连接条件 新增复合索引 调整索引顺序 覆盖索引设计 引入搜索引擎 读写分离 分库分表 历史数据归档 无效数据清理 数据压缩 测试环境验证 灰度发布 全量上线 性能指标对比 业务功能验证 系统稳定性检查

慢SQL治理的关键在于系统性思考渐进式优化

  • 发现阶段:建立完善的监控体系,及时发现性能问题
  • 分析阶段:深入分析根本原因,避免头痛医头、脚痛医脚
  • 方案阶段:制定多层次的优化方案,从SQL到架构全方位考虑
  • 实施阶段:谨慎实施,确保系统稳定性
  • 验证阶段:客观评估优化效果,确保问题真正解决
  • 监控阶段:持续观察,防止问题反弹

6.2 案例一:模糊匹配优化

问题SQL(脱敏后)
sql 复制代码
SELECT count(*) 
FROM `脱敏表` 
WHERE (xxx_id = XXX) 
  AND (`name` LIKE '%关键词%')
执行计划分析
字段 含义
id 1 单表查询
select_type SIMPLE 简单查询
table 脱敏表 查询目标表
type ref 使用了索引,但非最优
key idx_xxxid_xxxuserid 使用的索引不含 name
rows 589328 预计扫描 58 万行
filtered 11.11 低过滤率
Extra Using where 没有使用 name 字段索引,存在回表操作
性能瓶颈分析
问题点 描述
%关键词% 前模糊匹配 无法使用 B+Tree 索引
name 无索引 即使改为前缀匹配 LIKE 'xxx%' 也无法优化
使用了错误索引 当前索引 (xxx_id, xxxuserid) 无法支持 name 查询
高数据量下回表扫描 xxx_id = 177 的数据量大(58w),过滤性能差
优化方案

去掉前模糊匹配,改成 LIKE '关键词%',并为 name 字段建立索引。


6.3 案例二:复杂条件查询优化

问题SQL(脱敏后)
sql 复制代码
SELECT count(*) 
FROM `脱敏表` 
WHERE creator IN ('ID1', 'ID2', 'ID3', 'ID4', 'ID5', 'ID6', 'ID7') 
  AND (xxx_id = YYY) 
  AND (status = 1) 
  AND (`name` LIKE '%活动关键词%')
性能瓶颈分析
字段 解读
type ref 使用了 corp_id 索引,但效率不高
key idx_xxxid_xxxuserid 说明使用的是 (corp_id, userid) 的索引
rows 518218 预估要扫描 50 万条数据(即 corp_id=156 的数据量)
filtered 0.56 超低过滤率,说明大多数数据都是"扫了但没用上"
Extra Using where 多条件联合过滤 + 无法使用 name 的索引,导致回表严重
问题分析表
问题点 描述
%模糊匹配% LIKE '%素质学习活动%' 无法走索引,依旧是性能杀手
无 name 索引 和慢 SQL 1 一样,name 字段未建立索引
使用错误索引 当前使用的是 (xxx_id, xxxuserid),和查询条件不匹配
多个 WHERE 条件 creator IN (...) + status=1 + xxx_id=156 增加了查询复杂度
扫描行数巨大 扫描了 50 万行,过滤率不到 1%,极其低效
优化方案

使用搜索引擎(如Elasticsearch)进行模糊匹配,将复杂的文本搜索从数据库中分离出来。


6.4 案例三:URL字段查询优化

问题SQL(脱敏后)
sql 复制代码
SELECT `id`, `media_id` 
FROM `脱敏表` 
WHERE (xxx_id = XXX) 
  AND (user_id = ZZZ) 
  AND (url = 'https://example.com/feed?...') 
  LIMIT 1
执行计划分析
字段 含义
type ref 使用了索引但是模糊匹配级别,不够高效
key idx_xxxid_xxxuserid 当前使用的是 (corp_id, user_id) 索引
rows 27622 即便用了索引,预估扫描仍达 2 万+ 行
filtered 10% 表示大部分数据被过滤掉了(说明 url 没有参与索引)
Extra Using where 表示 MySQL 仍需逐行判断 url=xxx 条件
优化方案

添加新字段(如哈希值),改写SQL

sql 复制代码
SELECT id, media_id
FROM 脱敏表
WHERE xxx_id = XXX
  AND xxxuser_id = ZZZ
  AND url_hash = '哈希值'
  AND url = 'https://example.com/feed?...'
  LIMIT 1

6.5 案例四:高并发系统级性能问题

问题SQL(脱敏后)
sql 复制代码
SELECT id, xxxuser_id1, xxxuser_id2, xxx_id, task_id, ...
FROM table_task
WHERE xxxuser_id2 = '用户ID'
  AND xxx_id = XXX
  AND create_time >= 时间戳
  AND task_status = 2;
执行计划分析
字段 含义
type range 范围扫描,说明部分使用了索引
key idx_wx_user_time 使用了某个复合索引(猜测为 xxx_userid + create_time)
rows 68794 预估需要扫描约 6.8 万行
filtered 1.00 过滤率很低(接近全扫描)
Extra Using index condition; Using where 没有使用覆盖索引,部分条件未被索引消化
问题分析

这条 SQL 单条执行并不慢 ,索引命中了(走的是 (xxx_userid, create_time)),本质的问题是高并发下的系统级退化

问题类型 原因
数据库连接池耗尽 协程数超出 max_open_conns,大量阻塞
单连接响应慢 数据量大,每条查询虽然不慢,但处理行数多,连接占用时间长
Goroutine 累积 协程爆炸,CPU、内存调度竞争,影响系统吞吐
MySQL CPU 使用高 并发下 10w+ 次筛选,造成 DB CPU 持续高压
索引虽走,但行数多 rows=68794 表示每次查近 7 万条,I/O 和网络传输压力大
优化方案
  • 限制协程并发度(从4降到2)
  • 查询范围从10天减少到3天
  • 执行频率从10分钟一次改为30分钟一次

6.6 案例五:数据倾斜与并发优化

问题SQL(脱敏后)
sql 复制代码
SELECT count(*) 
FROM `脱敏表` 
WHERE (xxx_id = YYY) 
  AND (user_id = AAA) 
  AND (state = '状态值') 
  AND (del_way != 0) 
  AND (delete_time >= 时间戳1) 
  AND (delete_time < 时间戳2)
执行计划分析
字段 含义
key idx_xxxuserid_followtime 使用了 (xxxuser_id, follow_time) 联合索引,但不完全匹配查询条件
key_len 8 实际只使用了索引的第一列 xxxuser_id(类型为 BIGINT,占用 8 字节)
ref const xxxuser_id = 51967 为常量匹配,用于索引过滤
rows 53472 预估扫描行数约为 5.3 万,说明该 xxxuser_id 拥有大量数据
filtered 0.00 预估过滤率为 0%,说明几乎所有行都需进一步筛选,回表严重
Extra Using where 表示需回表做完整 WHERE 条件判断,未能使用覆盖索引或索引条件下推
问题分析
问题 描述
❗️数据倾斜 部分 xxxuser_id 拥有极多数据(几十万级),SQL执行时间呈不均衡状态
❗️并发导致放大 每个用户都执行这条 SQL,量一大就撑爆连接池或 DB 资源
❗️索引不命中 delete_time / state 这两个高过滤条件字段只能靠回表判断,扫描行数增多
❗️逻辑层并发扫描 多个用户并发执行时,每人扫几万,系统瞬间承压(4核但代码设置了10个协程数)
优化方案

理想索引

sql 复制代码
CREATE INDEX idx_xxxuserid_delway_state_deltime 
ON 脱敏表(xxxuser_id, del_way, state, delete_time);

实际采用的3种优化

优化手段 背景/动机 预期收益
清理数据库表中的双删数据 当前 del_way != 0 过滤范围太大,无效数据长期滞留影响查询效率;部分旧数据可归档或物理删除 降低数据量,减少每次扫描行数,提升过滤效率
降低协程并发数 当前每个 xxxuser_id 并发触发多条 SQL,容易造成连接池耗尽、系统抖动 控制系统资源消耗,更稳定运行、更平滑负载
将时间范围由 3 天缩小到 1 天 原先扫描范围广、数据量大,导致执行时间不可控;改为每日定时执行或滚动聚合 明确可控的数据窗口,降低查询压力、支持更频繁调度

6.7 慢SQL治理总结

类型 内容
数据优化 清理无效数据、减少扫描量
查询优化 改写 SQL 表达式、提升执行计划
系统优化 限制并发、平滑调度
窗口优化 缩小时间范围、减少数据量

总结

本文从MySQL查询的基础机制出发,系统性地介绍了查询优化的理论基础和实践方法:

  1. 访问方法 :7种不同的访问方法为查询提供了从最高效的const到最低效的all的性能选择
  2. 联表查询:理解笛卡尔积本质和连接算法,合理设计索引和选择驱动表
  3. 成本计算:掌握I/O成本和CPU成本的计算原理,理解优化器的决策依据
  4. 规则优化:利用条件化简、外连接消除等规则,让查询更高效
  5. 执行计划 :通过EXPLAIN分析瓶颈,指导索引设计和SQL优化
  6. 实战案例:5个真实案例展示了从问题发现到解决的完整流程

通过理论学习和实践应用相结合,可以更好地理解MySQL查询优化的精髓,在实际开发中写出高性能的SQL语句

相关推荐
耳东哇15 分钟前
阿里云百炼平台创建智能体-上传文档
数据库·redis·阿里云
掘根2 小时前
【Redis】string字符串
数据结构·数据库·redis
越来越无动于衷3 小时前
智慧社区(八)——社区人脸识别出入管理系统设计与实现
java·开发语言·spring boot·python·mysql
春天的菠菜4 小时前
【mysql】设置mysql数据库只读权限用户
数据库·oracle
找不到、了4 小时前
Mysql使用Canal服务同步数据->ElasticSearch
数据库·mysql·elasticsearch
宋大水5 小时前
【大作业-12】草莓成熟度检测模型,YOLO+PyQt+MySQL
数据库·深度学习·mysql·yolo·目标检测·pyqt·课程设计
_代号0075 小时前
MySQL梳理四:事务日志机制和多版本并发控制(MVCC)
mysql
赵_|大人6 小时前
Qt 自动无法加载数据库为空
开发语言·数据库·qt
2501_920047036 小时前
Mysql-事务
数据库·mysql·oracle