MySQL梳理三:查询与优化

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

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

目录

  • 一、单表查询的访问方法
  • 二、联表查询机制
  • 三、查询成本计算
  • 四、基于规则的查询优化
  • 五、执行计划分析
  • 六、慢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 联表查询成本评估示例

以内连接方式举例:

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 移除不必要的括号

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

-- 原始查询
((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 连接时,优化器会将常量值传递到其他表达式中:

-- 原始查询
a = 5 AND b > a-- 优化后
a = 5 AND b > 5
4.1.3 等值传递

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

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

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

-- 原始查询
(a < 1 AND b = b) OR (a = 6 OR 5 != 5)-- 优化后
a < 1 OR a = 6
4.1.5 表达式计算

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

-- 原始查询
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),外连接效果等价于内连接:

-- 原始查询
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(脱敏后)
SELECT count(*) 
FROM `脱敏表` 
WHERE (xxx_id = XXX) AND (`name` LIKE '%关键词%')
执行计划分析
字段含义
id1单表查询
select_typeSIMPLE简单查询
table脱敏表查询目标表
typeref使用了索引,但非最优
keyidx_xxxid_xxxuserid使用的索引不含 name
rows589328预计扫描 58 万行
filtered11.11低过滤率
ExtraUsing where没有使用 name 字段索引,存在回表操作
性能瓶颈分析
问题点描述
%关键词% 前模糊匹配无法使用 B+Tree 索引
name 无索引即使改为前缀匹配 LIKE ‘xxx%’ 也无法优化
使用了错误索引当前索引 (xxx_id, xxxuserid) 无法支持 name 查询
高数据量下回表扫描xxx_id = 177 的数据量大(58w),过滤性能差
优化方案

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


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

问题SQL(脱敏后)
SELECT count(*) 
FROM `脱敏表` 
WHERE creator IN ('ID1', 'ID2', 'ID3', 'ID4', 'ID5', 'ID6', 'ID7') AND (xxx_id = YYY) AND (status = 1) AND (`name` LIKE '%活动关键词%')
性能瓶颈分析
字段解读
typeref使用了 corp_id 索引,但效率不高
keyidx_xxxid_xxxuserid说明使用的是 (corp_id, userid) 的索引
rows518218预估要扫描 50 万条数据(即 corp_id=156 的数据量)
filtered0.56超低过滤率,说明大多数数据都是"扫了但没用上"
ExtraUsing 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(脱敏后)
SELECT `id`, `media_id` 
FROM `脱敏表` 
WHERE (xxx_id = XXX) AND (user_id = ZZZ) AND (url = 'https://example.com/feed?...') LIMIT 1
执行计划分析
字段含义
typeref使用了索引但是模糊匹配级别,不够高效
keyidx_xxxid_xxxuserid当前使用的是 (corp_id, user_id) 索引
rows27622即便用了索引,预估扫描仍达 2 万+ 行
filtered10%表示大部分数据被过滤掉了(说明 url 没有参与索引)
ExtraUsing where表示 MySQL 仍需逐行判断 url=xxx 条件
优化方案

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

SELECT id, media_id
FROM 脱敏表
WHERE xxx_id = XXXAND xxxuser_id = ZZZAND url_hash = '哈希值'AND url = 'https://example.com/feed?...'LIMIT 1

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

问题SQL(脱敏后)
SELECT id, xxxuser_id1, xxxuser_id2, xxx_id, task_id, ...
FROM table_task
WHERE xxxuser_id2 = '用户ID'AND xxx_id = XXXAND create_time >= 时间戳AND task_status = 2;
执行计划分析
字段含义
typerange范围扫描,说明部分使用了索引
keyidx_wx_user_time使用了某个复合索引(猜测为 xxx_userid + create_time)
rows68794预估需要扫描约 6.8 万行
filtered1.00过滤率很低(接近全扫描)
ExtraUsing 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(脱敏后)
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)
执行计划分析
字段含义
keyidx_xxxuserid_followtime使用了 (xxxuser_id, follow_time) 联合索引,但不完全匹配查询条件
key_len8实际只使用了索引的第一列 xxxuser_id(类型为 BIGINT,占用 8 字节)
refconstxxxuser_id = 51967 为常量匹配,用于索引过滤
rows53472预估扫描行数约为 5.3 万,说明该 xxxuser_id 拥有大量数据
filtered0.00预估过滤率为 0%,说明几乎所有行都需进一步筛选,回表严重
ExtraUsing where表示需回表做完整 WHERE 条件判断,未能使用覆盖索引或索引条件下推
问题分析
问题描述
❗️数据倾斜部分 xxxuser_id 拥有极多数据(几十万级),SQL执行时间呈不均衡状态
❗️并发导致放大每个用户都执行这条 SQL,量一大就撑爆连接池或 DB 资源
❗️索引不命中 delete_time / state这两个高过滤条件字段只能靠回表判断,扫描行数增多
❗️逻辑层并发扫描多个用户并发执行时,每人扫几万,系统瞬间承压(4核但代码设置了10个协程数)
优化方案

理想索引

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语句

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。
如若转载,请注明出处:http://www.pswp.cn/news/917450.shtml
繁体地址,请注明出处:http://hk.pswp.cn/news/917450.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

从零开始的python学习(九)P129+P130+P131+P132+P133

本文章记录观看B站python教程学习笔记和实践感悟&#xff0c;视频链接&#xff1a;【花了2万多买的Python教程全套&#xff0c;现在分享给大家&#xff0c;入门到精通(Python全栈开发教程)】 https://www.bilibili.com/video/BV1wD4y1o7AS/?p6&share_sourcecopy_web&v…

LCL滤波器及其电容电流前馈有源阻尼设计软件【LCLAD_designer】

本文主要介绍针对阮新波著《LCL型并网逆变器的控制技术》书籍 第二章&#xff08;LCL滤波器设计&#xff09;及第五章&#xff08;LCL型并网逆变器的电容电流反馈有源阻尼设计&#xff09;开发的一款交互式软件【LCL&AD_designer】&#xff0c;开发平台MATLAB_R2022b/app d…

【Conda】配置Conda镜像源

Conda 镜像源配置指南 适用系统&#xff1a;Windows 10&#xff08;含 Miniconda / Anaconda&#xff09; & Linux&#xff08;Ubuntu / CentOS / Debian 等&#xff09;1. 为什么要设置镜像源 在中国大陆直接访问 repo.anaconda.com 经常遇到速度慢、连接超时、SSL 错误等…

八股取士--docker

基础概念类 1. 什么是Docker&#xff1f;它解决了什么问题&#xff1f; 解析&#xff1a; Docker是一个开源的容器化平台&#xff0c;用于开发、交付和运行应用程序。 主要解决的问题&#xff1a; 环境一致性&#xff1a;解决"在我机器上能跑"的问题资源利用率&#…

C++:STL中的栈和队列的适配器deque

学习完string类、容器vector和容器list&#xff0c;再去学习其他容器的学习成本就非常低&#xff0c;容器的使用方法都大差不差&#xff0c;而栈和队列的底层使用了适配器&#xff0c;去模拟实现就没有那么麻烦&#xff0c;适配器也是一种容器&#xff0c;但是这种容器兼备栈和…

9类主流数据库 - 帮你更好地进行数据库选型!

作者&#xff1a;唐叔在学习 专栏&#xff1a;数据库学习 标签&#xff1a;数据库选型、MySQL、Redis、MongoDB、大数据存储、NoSQL、数据库优化、数据架构、AI数据库 大家好&#xff0c;我是你们的老朋友唐叔&#xff01;今天咱们来聊聊程序员吃饭的家伙之一 —— 数据库。在这…

推送本地项目到Gitee远程仓库

文章目录前言前面已加学习了下载gitee软件&#xff0c;网址在上一篇文章。在gitee创建账号与仓库。现在来学习如何讲本地项目推送到Gitee远程仓库一、流程总结前言 前面已加学习了下载gitee软件&#xff0c;网址在上一篇文章。在gitee创建账号与仓库。现在来学习如何讲本地项目…

CMake 命令行参数完全指南(5)

​**40. --version**​ ​解释​&#xff1a;显示CMake版本 ​示例​&#xff1a; cmake --version # 输出&#xff1a;cmake version 3.25.2​**41. --warn-uninitialized**​ ​解释​&#xff1a;警告未初始化的变量 ​适用场景​&#xff1a;检测脚本错误 ​示例​&#xf…

基于Python实现生产者—消费者分布式消息队列:构建高可用异步通信系统

深入剖析分布式消息队列的核心原理与Python实现&#xff0c;附完整架构设计和代码实现引言&#xff1a;分布式系统的通信基石在微服务架构和云原生应用普及的今天&#xff0c;服务间的异步通信成为系统设计的核心挑战。当单体应用拆分为数十个微服务后&#xff0c;服务间通信呈…

【大模型核心技术】Agent 理论与实战

一、基本概念 LLM 特性&#xff1a;擅长理解和生成文本&#xff0c;但采用 “一次性” 响应模式&#xff0c;本质上是无记忆的生成模型。Agent 本质&#xff1a;包含 LLM 的系统应用&#xff0c;具备自主规划、工具调用和环境反馈能力&#xff0c;是将 LLM 从 “聊天机器人” 升…

Maven - 依赖的生命周期详解

作者&#xff1a;唐叔在学习 专栏&#xff1a;唐叔的Java实践 标签&#xff1a;Maven依赖管理、Java项目构建、依赖传递性、Spring Boot依赖、Maven最佳实践、项目构建工具、依赖冲突解决、POM文件详解 文章目录一、开篇二、Maven依赖生命周期2.1 依赖声明阶段&#xff1a;POM文…

从零打造大语言模型--处理文本数据

从零打造大语言模型 第 1 章&#xff1a;处理文本数据 章节导读 在把文本投喂进 Transformer 之前&#xff0c;需要两步&#xff1a;① 将字符流切分成离散 Token&#xff1b;② 把 Token 映射成连续向量。 1.1 理解词嵌入&#xff08;Word Embedding&#xff09; 嵌入向量 一…

【Spring】Bean的生命周期,部分源码解释

文章目录Bean 的生命周期执行流程代码演示执行结果源码阅读AbstractAutowireCapableBeanFactorydoCreateBeaninitializeBeanBean 的生命周期 生命周期指的是一个对象从诞生到销毁的整个生命过程&#xff0c;我们把这个过程就叫做一个对象的声明周期 Bean 的声明周期分为以下 …

[spring-cloud: 服务发现]-源码解析

DiscoveryClient DiscoveryClient 接口定义了常见的服务发现操作&#xff0c;如获取服务实例、获取所有服务ID、验证客户端可用性等&#xff0c;通常用于 Eureka 或 Consul 等服务发现框架。 public interface DiscoveryClient extends Ordered {/*** Default order of the dis…

QML 基础语法与对象模型

QML (Qt Meta-Object Language) 是一种声明式语言&#xff0c;专为创建流畅的用户界面和应用程序逻辑而设计。作为 Qt 框架的一部分&#xff0c;QML 提供了简洁、直观的语法来描述 UI 组件及其交互方式。本文将深入解析 QML 的基础语法和对象模型。 一、QML 基础语法 1. 基本对…

HTTPS的概念和工作过程

一.HTTPS是什么HTTPS也是一个应用层协议&#xff0c;是在HTTP协议的基础上引入了一个加密层&#xff08;SSL&#xff09;HTTP协议内容都是按照文本的方式明文传输的&#xff0c;这就导致传输过程中可能出现被篡改的情况最著名的就是十多年前网络刚发展的时期&#xff0c;出现“…

Unity —— Android 应用构建与发布​

文章目录1 ​Gradle模板​​&#xff1a;了解Gradle模板的作用及使用方法&#xff0c;以增强对构建流程的控制。​2 ​Gradle模板变量​​&#xff1a;参考文档——自定义Gradle模板文件中可用的变量列表。2.1 修改Unity应用的Gradle工程文件2.1.1 通过Gradle模板文件2.1.2 导出…

【iOS】strong和copy工作流程探寻、OC属性关键字复习

文章目录前言strong和copy的区别为什么要用copy&#xff1f;什么时候用什么修饰&#xff1f;strong&#xff08;ARC自动管理&#xff09;strong修饰变量的底层流程图底层代码核心实现小结copy底层流程图对比与strong的关键不同之处内部调用关系&#xff08;伪代码&#xff09;小…

程序代码篇---多循环串口程序切换

上位机版&#xff08;Python&#xff09;要实现根据串口接收结果高效切换四个 while 循环函数&#xff0c;我们可以采用状态机模式&#xff0c;配合非阻塞串口读取来设计程序结构。这种方式可以实现快速切换&#xff0c;避免不必要的资源消耗。下面是一个高效的实现方案&#x…

rk3568上,实现ota,计算hash,验证签名,判断激活分区,并通过dd命令,写入对应AB分区

通过自定义升级程序&#xff0c;更直观的理解ota升级原理。 一、模拟计算hash&#xff0c;验证签名&#xff0c;判断激活分区&#xff0c;并通过dd命令&#xff0c;写入对应分区 #include <stdio.h> #include <stdlib.h> #include <string.h> #include <u…