临海网站设计,标小智logo在线设计,手机版网站嵌入代码,wordpress4.8.3中文MySQL查询优化完整指南#xff1a;从理论到实践 本文从MySQL查询的基础机制出发#xff0c;深入探讨单表查询访问方法、联表查询策略、成本计算原理、基于规则的优化技术#xff0c;最后通过实际案例展示慢SQL的诊断和优化过程。 目录
一、单表查询的访问方法二、联表查询机…MySQL查询优化完整指南从理论到实践 本文从MySQL查询的基础机制出发深入探讨单表查询访问方法、联表查询策略、成本计算原理、基于规则的优化技术最后通过实际案例展示慢SQL的诊断和优化过程。 目录
一、单表查询的访问方法二、联表查询机制三、查询成本计算四、基于规则的查询优化五、执行计划分析六、慢SQL治理实战案例 一、单表查询的访问方法
MySQL 的访问方法定义了查询语句的执行方式类似于从起点到终点的路线选择。查询优化器根据条件、索引和统计信息选择最优访问方法以最小化执行成本I/O、CPU、内存等。虽然查询结果相同但不同访问方法的效率差异显著。
1.1 访问方法性能对比
不同访问方法的性能差异如下图所示从左到右效率递减
#mermaid-svg-km2WH36CVAug2nzf {font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}#mermaid-svg-km2WH36CVAug2nzf .error-icon{fill:#552222;}#mermaid-svg-km2WH36CVAug2nzf .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-km2WH36CVAug2nzf .edge-thickness-normal{stroke-width:2px;}#mermaid-svg-km2WH36CVAug2nzf .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-km2WH36CVAug2nzf .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-km2WH36CVAug2nzf .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-km2WH36CVAug2nzf .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-km2WH36CVAug2nzf .marker{fill:#333333;stroke:#333333;}#mermaid-svg-km2WH36CVAug2nzf .marker.cross{stroke:#333333;}#mermaid-svg-km2WH36CVAug2nzf svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-km2WH36CVAug2nzf .label{font-family:"trebuchet ms",verdana,arial,sans-serif;color:#333;}#mermaid-svg-km2WH36CVAug2nzf .cluster-label text{fill:#333;}#mermaid-svg-km2WH36CVAug2nzf .cluster-label span{color:#333;}#mermaid-svg-km2WH36CVAug2nzf .label text,#mermaid-svg-km2WH36CVAug2nzf span{fill:#333;color:#333;}#mermaid-svg-km2WH36CVAug2nzf .node rect,#mermaid-svg-km2WH36CVAug2nzf .node circle,#mermaid-svg-km2WH36CVAug2nzf .node ellipse,#mermaid-svg-km2WH36CVAug2nzf .node polygon,#mermaid-svg-km2WH36CVAug2nzf .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-km2WH36CVAug2nzf .node .label{text-align:center;}#mermaid-svg-km2WH36CVAug2nzf .node.clickable{cursor:pointer;}#mermaid-svg-km2WH36CVAug2nzf .arrowheadPath{fill:#333333;}#mermaid-svg-km2WH36CVAug2nzf .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-km2WH36CVAug2nzf .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-km2WH36CVAug2nzf .edgeLabel{background-color:#e8e8e8;text-align:center;}#mermaid-svg-km2WH36CVAug2nzf .edgeLabel rect{opacity:0.5;background-color:#e8e8e8;fill:#e8e8e8;}#mermaid-svg-km2WH36CVAug2nzf .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-km2WH36CVAug2nzf .cluster text{fill:#333;}#mermaid-svg-km2WH36CVAug2nzf .cluster span{color:#333;}#mermaid-svg-km2WH36CVAug2nzf div.mermaidTooltip{position:absolute;text-align:center;max-width:200px;padding:2px;font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:12px;background:hsl(80, 100%, 96.2745098039%);border:1px solid #aaaa33;border-radius:2px;pointer-events:none;z-index:100;}#mermaid-svg-km2WH36CVAug2nzf :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} 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×10010000 行。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 联表查询执行过程
联表查询由优化器驱动执行过程如下图所示
#mermaid-svg-FwUDvSutKECUms4a {font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}#mermaid-svg-FwUDvSutKECUms4a .error-icon{fill:#552222;}#mermaid-svg-FwUDvSutKECUms4a .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-FwUDvSutKECUms4a .edge-thickness-normal{stroke-width:2px;}#mermaid-svg-FwUDvSutKECUms4a .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-FwUDvSutKECUms4a .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-FwUDvSutKECUms4a .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-FwUDvSutKECUms4a .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-FwUDvSutKECUms4a .marker{fill:#333333;stroke:#333333;}#mermaid-svg-FwUDvSutKECUms4a .marker.cross{stroke:#333333;}#mermaid-svg-FwUDvSutKECUms4a svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-FwUDvSutKECUms4a .label{font-family:"trebuchet ms",verdana,arial,sans-serif;color:#333;}#mermaid-svg-FwUDvSutKECUms4a .cluster-label text{fill:#333;}#mermaid-svg-FwUDvSutKECUms4a .cluster-label span{color:#333;}#mermaid-svg-FwUDvSutKECUms4a .label text,#mermaid-svg-FwUDvSutKECUms4a span{fill:#333;color:#333;}#mermaid-svg-FwUDvSutKECUms4a .node rect,#mermaid-svg-FwUDvSutKECUms4a .node circle,#mermaid-svg-FwUDvSutKECUms4a .node ellipse,#mermaid-svg-FwUDvSutKECUms4a .node polygon,#mermaid-svg-FwUDvSutKECUms4a .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-FwUDvSutKECUms4a .node .label{text-align:center;}#mermaid-svg-FwUDvSutKECUms4a .node.clickable{cursor:pointer;}#mermaid-svg-FwUDvSutKECUms4a .arrowheadPath{fill:#333333;}#mermaid-svg-FwUDvSutKECUms4a .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-FwUDvSutKECUms4a .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-FwUDvSutKECUms4a .edgeLabel{background-color:#e8e8e8;text-align:center;}#mermaid-svg-FwUDvSutKECUms4a .edgeLabel rect{opacity:0.5;background-color:#e8e8e8;fill:#e8e8e8;}#mermaid-svg-FwUDvSutKECUms4a .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-FwUDvSutKECUms4a .cluster text{fill:#333;}#mermaid-svg-FwUDvSutKECUms4a .cluster span{color:#333;}#mermaid-svg-FwUDvSutKECUms4a div.mermaidTooltip{position:absolute;text-align:center;max-width:200px;padding:2px;font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:12px;background:hsl(80, 100%, 96.2745098039%);border:1px solid #aaaa33;border-radius:2px;pointer-events:none;z-index:100;}#mermaid-svg-FwUDvSutKECUms4a :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;}是否开始联表查询选择驱动表(根据成本估算)执行驱动表查询(使用单表访问方法)获得驱动表结果集(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 调整缓存驱动表结果集记录具体过程 将驱动表查询列和条件放入 join buffer扫描被驱动表记录一次性与 join buffer 中多条记录匹配减少被驱动表 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.1 或 0.01用于调整成本估算。
3.2 单表查询成本计算流程
MySQL 查询优化器在执行单表查询前会评估所有可能执行方案的成本选择成本最低的方案作为执行计划
#mermaid-svg-WfycmBDAeJIn1qJU {font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}#mermaid-svg-WfycmBDAeJIn1qJU .error-icon{fill:#552222;}#mermaid-svg-WfycmBDAeJIn1qJU .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-WfycmBDAeJIn1qJU .edge-thickness-normal{stroke-width:2px;}#mermaid-svg-WfycmBDAeJIn1qJU .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-WfycmBDAeJIn1qJU .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-WfycmBDAeJIn1qJU .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-WfycmBDAeJIn1qJU .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-WfycmBDAeJIn1qJU .marker{fill:#333333;stroke:#333333;}#mermaid-svg-WfycmBDAeJIn1qJU .marker.cross{stroke:#333333;}#mermaid-svg-WfycmBDAeJIn1qJU svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-WfycmBDAeJIn1qJU .label{font-family:"trebuchet ms",verdana,arial,sans-serif;color:#333;}#mermaid-svg-WfycmBDAeJIn1qJU .cluster-label text{fill:#333;}#mermaid-svg-WfycmBDAeJIn1qJU .cluster-label span{color:#333;}#mermaid-svg-WfycmBDAeJIn1qJU .label text,#mermaid-svg-WfycmBDAeJIn1qJU span{fill:#333;color:#333;}#mermaid-svg-WfycmBDAeJIn1qJU .node rect,#mermaid-svg-WfycmBDAeJIn1qJU .node circle,#mermaid-svg-WfycmBDAeJIn1qJU .node ellipse,#mermaid-svg-WfycmBDAeJIn1qJU .node polygon,#mermaid-svg-WfycmBDAeJIn1qJU .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-WfycmBDAeJIn1qJU .node .label{text-align:center;}#mermaid-svg-WfycmBDAeJIn1qJU .node.clickable{cursor:pointer;}#mermaid-svg-WfycmBDAeJIn1qJU .arrowheadPath{fill:#333333;}#mermaid-svg-WfycmBDAeJIn1qJU .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-WfycmBDAeJIn1qJU .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-WfycmBDAeJIn1qJU .edgeLabel{background-color:#e8e8e8;text-align:center;}#mermaid-svg-WfycmBDAeJIn1qJU .edgeLabel rect{opacity:0.5;background-color:#e8e8e8;fill:#e8e8e8;}#mermaid-svg-WfycmBDAeJIn1qJU .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-WfycmBDAeJIn1qJU .cluster text{fill:#333;}#mermaid-svg-WfycmBDAeJIn1qJU .cluster span{color:#333;}#mermaid-svg-WfycmBDAeJIn1qJU div.mermaidTooltip{position:absolute;text-align:center;max-width:200px;padding:2px;font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:12px;background:hsl(80, 100%, 96.2745098039%);border:1px solid #aaaa33;border-radius:2px;pointer-events:none;z-index:100;}#mermaid-svg-WfycmBDAeJIn1qJU :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;}开始单表查询成本计算分析搜索条件找出可能使用的索引计算全表扫描成本计算各索引访问成本考虑index_merge可能性比较所有方案成本选择成本最低方案生成执行计划成本构成I/O成本 页面数 × 1.0CPU成本 记录数 × 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 54.1.3 等值传递
当多个列之间存在等值关系时优化器会将等值条件传递
-- 原始查询
a b AND b c AND c 5-- 优化后
a 5 AND b 5 AND c 54.1.4 移除无用条件
对于恒为 TRUE 或 FALSE 的条件优化器会直接移除
-- 原始查询
(a 1 AND b b) OR (a 6 OR 5 ! 5)-- 优化后
a 1 OR a 64.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 tWHERE 或 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优化器通过不同的策略优化各类子查询主要优化路径如下
#mermaid-svg-cSw37y1BkT5n7RJ8 {font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}#mermaid-svg-cSw37y1BkT5n7RJ8 .error-icon{fill:#552222;}#mermaid-svg-cSw37y1BkT5n7RJ8 .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-cSw37y1BkT5n7RJ8 .edge-thickness-normal{stroke-width:2px;}#mermaid-svg-cSw37y1BkT5n7RJ8 .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-cSw37y1BkT5n7RJ8 .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-cSw37y1BkT5n7RJ8 .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-cSw37y1BkT5n7RJ8 .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-cSw37y1BkT5n7RJ8 .marker{fill:#333333;stroke:#333333;}#mermaid-svg-cSw37y1BkT5n7RJ8 .marker.cross{stroke:#333333;}#mermaid-svg-cSw37y1BkT5n7RJ8 svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-cSw37y1BkT5n7RJ8 .label{font-family:"trebuchet ms",verdana,arial,sans-serif;color:#333;}#mermaid-svg-cSw37y1BkT5n7RJ8 .cluster-label text{fill:#333;}#mermaid-svg-cSw37y1BkT5n7RJ8 .cluster-label span{color:#333;}#mermaid-svg-cSw37y1BkT5n7RJ8 .label text,#mermaid-svg-cSw37y1BkT5n7RJ8 span{fill:#333;color:#333;}#mermaid-svg-cSw37y1BkT5n7RJ8 .node rect,#mermaid-svg-cSw37y1BkT5n7RJ8 .node circle,#mermaid-svg-cSw37y1BkT5n7RJ8 .node ellipse,#mermaid-svg-cSw37y1BkT5n7RJ8 .node polygon,#mermaid-svg-cSw37y1BkT5n7RJ8 .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-cSw37y1BkT5n7RJ8 .node .label{text-align:center;}#mermaid-svg-cSw37y1BkT5n7RJ8 .node.clickable{cursor:pointer;}#mermaid-svg-cSw37y1BkT5n7RJ8 .arrowheadPath{fill:#333333;}#mermaid-svg-cSw37y1BkT5n7RJ8 .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-cSw37y1BkT5n7RJ8 .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-cSw37y1BkT5n7RJ8 .edgeLabel{background-color:#e8e8e8;text-align:center;}#mermaid-svg-cSw37y1BkT5n7RJ8 .edgeLabel rect{opacity:0.5;background-color:#e8e8e8;fill:#e8e8e8;}#mermaid-svg-cSw37y1BkT5n7RJ8 .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-cSw37y1BkT5n7RJ8 .cluster text{fill:#333;}#mermaid-svg-cSw37y1BkT5n7RJ8 .cluster span{color:#333;}#mermaid-svg-cSw37y1BkT5n7RJ8 div.mermaidTooltip{position:absolute;text-align:center;max-width:200px;padding:2px;font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:12px;background:hsl(80, 100%, 96.2745098039%);border:1px solid #aaaa33;border-radius:2px;pointer-events:none;z-index:100;}#mermaid-svg-cSw37y1BkT5n7RJ8 :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;}子查询优化标量/行子查询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子句中的子查询UNIONUNION操作中的查询
5.1.2 表和分区信息
table显示查询涉及的表名或别名partitions显示查询涉及的分区如果表分区了。无分区表时为空
5.1.3 访问方法和索引信息 type访问类型即前面所说的访问方法反映查询效率常见值从优到劣 system表只有一行数据const通过主键或唯一索引直接定位一行eq_ref通过主键或唯一索引进行等值匹配ref通过非唯一索引进行等值匹配range索引范围扫描如、、 INindex全索引扫描ALL全表扫描效率最低 possible_keysMySQL可能使用的索引列表。为空表示没有可用索引 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 执行计划分析要点
执行计划分析时需要重点关注以下几个关键字段
#mermaid-svg-7RhWryv4VfjTrrsa {font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}#mermaid-svg-7RhWryv4VfjTrrsa .error-icon{fill:#552222;}#mermaid-svg-7RhWryv4VfjTrrsa .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-7RhWryv4VfjTrrsa .edge-thickness-normal{stroke-width:2px;}#mermaid-svg-7RhWryv4VfjTrrsa .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-7RhWryv4VfjTrrsa .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-7RhWryv4VfjTrrsa .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-7RhWryv4VfjTrrsa .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-7RhWryv4VfjTrrsa .marker{fill:#333333;stroke:#333333;}#mermaid-svg-7RhWryv4VfjTrrsa .marker.cross{stroke:#333333;}#mermaid-svg-7RhWryv4VfjTrrsa svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-7RhWryv4VfjTrrsa .label{font-family:"trebuchet ms",verdana,arial,sans-serif;color:#333;}#mermaid-svg-7RhWryv4VfjTrrsa .cluster-label text{fill:#333;}#mermaid-svg-7RhWryv4VfjTrrsa .cluster-label span{color:#333;}#mermaid-svg-7RhWryv4VfjTrrsa .label text,#mermaid-svg-7RhWryv4VfjTrrsa span{fill:#333;color:#333;}#mermaid-svg-7RhWryv4VfjTrrsa .node rect,#mermaid-svg-7RhWryv4VfjTrrsa .node circle,#mermaid-svg-7RhWryv4VfjTrrsa .node ellipse,#mermaid-svg-7RhWryv4VfjTrrsa .node polygon,#mermaid-svg-7RhWryv4VfjTrrsa .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-7RhWryv4VfjTrrsa .node .label{text-align:center;}#mermaid-svg-7RhWryv4VfjTrrsa .node.clickable{cursor:pointer;}#mermaid-svg-7RhWryv4VfjTrrsa .arrowheadPath{fill:#333333;}#mermaid-svg-7RhWryv4VfjTrrsa .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-7RhWryv4VfjTrrsa .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-7RhWryv4VfjTrrsa .edgeLabel{background-color:#e8e8e8;text-align:center;}#mermaid-svg-7RhWryv4VfjTrrsa .edgeLabel rect{opacity:0.5;background-color:#e8e8e8;fill:#e8e8e8;}#mermaid-svg-7RhWryv4VfjTrrsa .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-7RhWryv4VfjTrrsa .cluster text{fill:#333;}#mermaid-svg-7RhWryv4VfjTrrsa .cluster span{color:#333;}#mermaid-svg-7RhWryv4VfjTrrsa div.mermaidTooltip{position:absolute;text-align:center;max-width:200px;padding:2px;font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:12px;background:hsl(80, 100%, 96.2745098039%);border:1px solid #aaaa33;border-radius:2px;pointer-events:none;z-index:100;}#mermaid-svg-7RhWryv4VfjTrrsa :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;}EXPLAIN执行计划分析关键字段重点关注type字段访问方法优先级key字段索引使用情况rows字段扫描行数估算Extra字段额外执行信息✅ const/eq_ref/ref高效访问⚠️ range/index中等效率❌ ALL避免全表扫描✅ 有具体索引名索引命中❌ NULL未使用索引✅ 小数值高效查询❌ 大数值需要优化✅ Using index覆盖索引⚠️ Using temporary使用临时表⚠️ Using filesort额外排序⚠️ Using where需要回表分析时着重点
关注type尽量避免ALL或index优先const、eq_ref、ref检查key和possible_keys确保使用合适的索引观察rows和filtered减少扫描行数注意Extra中的Using temporary和Using filesort可能需要优化
5.3 实际分析示例
通过分析这些字段可定位查询瓶颈优化索引或重写SQL以提高性能。 六、慢SQL治理实战案例
以下是5个经过脱敏处理的实际慢SQL案例展示了从问题发现到优化解决的完整过程。
6.1 慢SQL治理流程图
慢SQL治理是一个系统性的过程需要遵循科学的流程来确保优化效果
#mermaid-svg-Ap4HOy4Pu4E9pj1O {font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}#mermaid-svg-Ap4HOy4Pu4E9pj1O .error-icon{fill:#552222;}#mermaid-svg-Ap4HOy4Pu4E9pj1O .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-Ap4HOy4Pu4E9pj1O .edge-thickness-normal{stroke-width:2px;}#mermaid-svg-Ap4HOy4Pu4E9pj1O .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-Ap4HOy4Pu4E9pj1O .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-Ap4HOy4Pu4E9pj1O .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-Ap4HOy4Pu4E9pj1O .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-Ap4HOy4Pu4E9pj1O .marker{fill:#333333;stroke:#333333;}#mermaid-svg-Ap4HOy4Pu4E9pj1O .marker.cross{stroke:#333333;}#mermaid-svg-Ap4HOy4Pu4E9pj1O svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-Ap4HOy4Pu4E9pj1O .label{font-family:"trebuchet ms",verdana,arial,sans-serif;color:#333;}#mermaid-svg-Ap4HOy4Pu4E9pj1O .cluster-label text{fill:#333;}#mermaid-svg-Ap4HOy4Pu4E9pj1O .cluster-label span{color:#333;}#mermaid-svg-Ap4HOy4Pu4E9pj1O .label text,#mermaid-svg-Ap4HOy4Pu4E9pj1O span{fill:#333;color:#333;}#mermaid-svg-Ap4HOy4Pu4E9pj1O .node rect,#mermaid-svg-Ap4HOy4Pu4E9pj1O .node circle,#mermaid-svg-Ap4HOy4Pu4E9pj1O .node ellipse,#mermaid-svg-Ap4HOy4Pu4E9pj1O .node polygon,#mermaid-svg-Ap4HOy4Pu4E9pj1O .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-Ap4HOy4Pu4E9pj1O .node .label{text-align:center;}#mermaid-svg-Ap4HOy4Pu4E9pj1O .node.clickable{cursor:pointer;}#mermaid-svg-Ap4HOy4Pu4E9pj1O .arrowheadPath{fill:#333333;}#mermaid-svg-Ap4HOy4Pu4E9pj1O .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-Ap4HOy4Pu4E9pj1O .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-Ap4HOy4Pu4E9pj1O .edgeLabel{background-color:#e8e8e8;text-align:center;}#mermaid-svg-Ap4HOy4Pu4E9pj1O .edgeLabel rect{opacity:0.5;background-color:#e8e8e8;fill:#e8e8e8;}#mermaid-svg-Ap4HOy4Pu4E9pj1O .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-Ap4HOy4Pu4E9pj1O .cluster text{fill:#333;}#mermaid-svg-Ap4HOy4Pu4E9pj1O .cluster span{color:#333;}#mermaid-svg-Ap4HOy4Pu4E9pj1O div.mermaidTooltip{position:absolute;text-align:center;max-width:200px;padding:2px;font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:12px;background:hsl(80, 100%, 96.2745098039%);border:1px solid #aaaa33;border-radius:2px;pointer-events:none;z-index:100;}#mermaid-svg-Ap4HOy4Pu4E9pj1O :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;}慢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使用的索引不含 namerows589328预计扫描 58 万行filtered11.11低过滤率ExtraUsing where没有使用 name 字段索引存在回表操作
性能瓶颈分析
问题点描述%关键词% 前模糊匹配无法使用 BTree 索引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_id156 的数据量filtered0.56超低过滤率说明大多数数据都是扫了但没用上ExtraUsing where多条件联合过滤 无法使用 name 的索引导致回表严重
问题分析表
问题点描述%模糊匹配%LIKE ‘%素质学习活动%’ 无法走索引依旧是性能杀手无 name 索引和慢 SQL 1 一样name 字段未建立索引使用错误索引当前使用的是 (xxx_id, xxxuserid)和查询条件不匹配多个 WHERE 条件creator IN (…) status1 xxx_id156 增加了查询复杂度扫描行数巨大扫描了 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 仍需逐行判断 urlxxx 条件
优化方案
添加新字段如哈希值改写SQL
SELECT id, media_id
FROM 脱敏表
WHERE xxx_id XXXAND xxxuser_id ZZZAND url_hash 哈希值AND url https://example.com/feed?...LIMIT 16.5 案例四高并发系统级性能问题
问题SQL脱敏后
SELECT id, xxxuser_id1, xxxuser_id2, xxx_id, task_id, ...
FROM table_task
WHERE xxxuser_id2 用户IDAND xxx_id XXXAND create_time 时间戳AND task_status 2;执行计划分析
字段值含义typerange范围扫描说明部分使用了索引keyidx_wx_user_time使用了某个复合索引猜测为 xxx_userid create_timerows68794预估需要扫描约 6.8 万行filtered1.00过滤率很低接近全扫描ExtraUsing index condition; Using where没有使用覆盖索引部分条件未被索引消化
问题分析
这条 SQL 单条执行并不慢索引命中了走的是 (xxx_userid, create_time)本质的问题是高并发下的系统级退化。
问题类型原因✅ 数据库连接池耗尽协程数超出 max_open_conns大量阻塞✅ 单连接响应慢数据量大每条查询虽然不慢但处理行数多连接占用时间长✅ Goroutine 累积协程爆炸CPU、内存调度竞争影响系统吞吐✅ MySQL CPU 使用高并发下 10w 次筛选造成 DB CPU 持续高压✅ 索引虽走但行数多rows68794 表示每次查近 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查询的基础机制出发系统性地介绍了查询优化的理论基础和实践方法
访问方法7种不同的访问方法为查询提供了从最高效的const到最低效的all的性能选择联表查询理解笛卡尔积本质和连接算法合理设计索引和选择驱动表成本计算掌握I/O成本和CPU成本的计算原理理解优化器的决策依据规则优化利用条件化简、外连接消除等规则让查询更高效执行计划通过EXPLAIN分析瓶颈指导索引设计和SQL优化实战案例5个真实案例展示了从问题发现到解决的完整流程
通过理论学习和实践应用相结合可以更好地理解MySQL查询优化的精髓在实际开发中写出高性能的SQL语句