深度解析!MySQL 与 Oracle 执行计划的硬核对比与实战攻略
✨哈喽,进来的小伙伴们,你们好耶!✨
✈️✈️本篇内容: MySQL与Oracle详解与对比!
🚀🚀主页还有更多MySQL、Oracle、Java的相关内容,感兴趣的话欢迎进我的主页!
🔍🔍后续会继续更新数据开发相关内容,期待你的关注!道阻且长,你我同行!
💎在数据库开发与管理过程中,理解执行计划是优化数据库性能的重要一环。执行计划展示了数据库如何执行 SQL 语句,它包含了数据检索、连接操作等一系列步骤的策略。MySQL 和 Oracle 作为主流关系型数据库,其执行计划既有共通之处,也因自身特性存在诸多差异。接下来,我们将对 MySQL 与 Oracle 执行计划进行深度解析与全面对比,帮助开发者和数据库管理员更好地掌握其原理,优化数据库性能。
一、执行计划基本概念
执行计划是数据库优化器根据 SQL 语句、表结构、索引情况等因素,生成的用于执行该 SQL 语句的具体步骤和策略。它是数据库执行 SQL 语句的蓝图,通过执行计划,我们可以了解数据库如何从表中读取数据、如何进行连接操作、如何应用过滤条件等。借助执行计划,开发人员和数据库管理员能够发现 SQL 语句执行过程中潜在的性能问题,进而针对性地进行优化,提高数据库运行效率。
执行计划的作用:
- 验证索引使用情况
- 分析多表连接顺序
- 识别全表扫描与大表过滤
- 检测子查询与临时表
- 评估排序操作效率
- 验证查询优化器选择
二、查看执行计划
-- MySQL
explain + sql
-- 例:
explain select * from a where id=1-- Oracle
explain plan for + sql
SELECT * FROM TABLE(DBMS_XPLAN.DISPLAY);
-- 例:
explain plan for select * from a where id = 1;
三、MySQL执行计划各字段含义
id
表示执行顺序,id的数字越大越先执行,如果数字一样,那么从上往下依次执行,如果为null表示这是一个结果集,不需要用它来进行查询。
select_type
查询的类型,主要用于区分普通查询、联合查询、子查询等复杂查询。
取值 | 含义 |
---|---|
simple | 简单的select查询,查询中不包含子查询或者union |
primary | 查询中包含任何复杂的子部分,最外层查询则被标记为primary |
union | 若第二个select出现在union之后,则被标记为union;若union包含在from子句的子查询中,外层select将被标记为derived |
dependent union | 与union一样,出现在union 或union all语句中,但是这个查询要受到外部查询的影响 |
union result | 包含union的结果集,在union和union all语句中,因为它不需要参与查询,所以id字段为null |
subquery | 在select 或 where列表中包含了子查询 |
dependent subquery | 与dependent union类似,表示这个subquery的查询要受到外部表查询的影响 |
derived | 在from列表中包含的子查询被标记为derived(衍生),mysql或递归执行这些子查询,把结果放在临时表里 |
table
显示查询的表名,如果查询使用了别名,那么这里显示的是别名,如果不涉及对数据表的操作,那么这显示为null,如果显示为尖括号括起来的就表示这个是临时表,后边的N就是执行计划中的id,表示结果来自于这个查询产生。如果是尖括号括起来的<union M,N>,与类似,也是一个临时表,表示这个结果来自于union查询的id为M,N的结果集。
type
MySQL的官网解释为:连接类型(the join type)。它描述了找到所需数据使用的扫描方式。
访问类型,sql查询优化中一个很重要的指标,结果值从好到坏依次是:
system > const > eq_ref > ref > fulltext > ref_or_null > index_merge > unique_subquery > index_subquery > range > index > ALL,除了all之外,其他的type都可以使用到索引,除了index_merge之外,其他的type只可以用到一个索引
扫描方式汇总:
取值 | 含义 |
---|---|
system | 系统表,少量数据,往往不需要进行磁盘IO |
const | 常量连接 |
eq_ref | 主键索引(primary key)或者非空唯一索引(unique not null)等值扫描 |
ref | 非主键非唯一索引等值扫描 |
ref_or_null | 与ref方法类似,只是增加了null值的比较。实际用的不多 |
range | 范围扫描 |
index | 索引树扫描 |
index_merge | 表示查询使用了两个以上的索引,最后取交集或者并集 |
fulltext | 全文索引检索 |
unique_subquery | 用于where中的in形式子查询,子查询返回不重复值唯一值 |
index_subquery | 用于in形式子查询,子查询可能返回重复值,可以使用索引将子查询去重 |
ALL | 全表扫描(full table scan) |
扫描方式详解:
system
扫描类型为system,说明数据已经加载到内存里,不需要进行磁盘IO。扫描速度最快。
const
const扫描条件为:
- 命中主键(primary key)或者唯一(unique)索引;
- 被连接的部分是一个常量值
eq_ref
eq_ref表示使用唯一性索引进行连接操作,即使用索引查找来匹配连接条件,这种方式适用于连接条件中的列是主键或唯一索引的情况。
扫描条件为:对于前表的每一行,后表只有一行被扫描
出现在要连接的各个表的查询计划中,驱动表只返回一行数据,且这行数据是第二个表的主键或者唯一索引,且必须为not null,唯一索引和主键是多列时,只有所有的列都用作比较时才会出现eq_ref.
ref
对于前表的每一行,后表可能有多于一行的数据被扫描。
不想eq_ref那样要求连接顺序,也没有主键和唯一索引的要求,只要使用相等条件检索时就可能出现,常见于辅助索引的等值查找。或者多列主键、唯一索引中,使用第一个列之外的列作为等值查找也会出现,总之,返回数据不唯一的等值查找就可能出现。
range
索引范围扫描,常见于使用>,<,is,null,between,in,like等运算符的查询中。
index
索引全表扫描,把索引从头到尾扫一遍,常见于使用索引列就可以处理不需要读取数据文件的查询、可以使用索引排序或者分组查询。
all
全表扫描数据文件,然后在server层进行过滤返回符合要求的记录。
possible_keys
查询涉及到的字段上存在索引,则该索引将被列出,但不一定被实际使用。
key
实际使用的索引,如果为null,则没有使用索引。
查询中如果使用了覆盖索引,则该索引仅出现在key列表中。
key_len
用于处理查询的索引长度,如果是单列索引,那就整个索引长度算进去,如果是多列索引,那么查询不一定都能使用到所有的列,具体使用到了多少个列的索引,这里就会计算进去,没有使用到的列,这里不会计算进去。留意下这个列的值,算一下你的多列索引总长度就知道有没有使用到所有的列了。要注意,mysql的ICP特性使用到的索引不会计入其中。另外,key_len只计算where条件用到的索引长度,而排序和分组就算用到了索引,也不会计算到key_len中。
ref
如果是使用的常数等值查询,这里会显示const,如果是连接查询,被驱动表的执行计划这里会显示驱动表的关联字段,如果是条件使用了表达式或者函数,或者条件列发生了内部隐式转化,这里可能显示为func。
rows
根据表统计信息及索引选用情况,大致估算出找到所需的记录锁需要读取的行数。
extra
取值 | 含义 | 举例 |
Using where | Extra为Using where说明, SQL使用了where条件过滤数据。 | explain select * from billing_item_dis where id > 4; |
Using index | Extra为Using index说明, SQL所需要返回的所有列数据均在一棵索引树上, 而无需访问实际的行记录。 | explain select id from billing_item_dis; |
Using index condition | Extra为Using index condition说明, 确实命中了索引,但不是所有的 列数据都在索引树上,还需要访问实际的行记录。 | explain select * from billing_item_dis t1, billing_item_result t2 where t1.user_id = t2.id; |
Using filesort | Extra为Using filesort说明,得到所需结果集, 需要对所有记录进行文件排序。典型的,在一个没有建立索引的列上进行了order by,就会触发,常见的优化方案是,在order by的列上添加索引,避免每次查询都全量排序。 | explain select id from billing_item_dis order by item_name; |
Using temporary | Extra为Using temporary说明, 需要建立临时表(temporary table)来暂存中间结果。 这类SQL语句性能较低,往往也需要进行优化。 典型的,group by和order by同时存在,且作用于不同的字段时,就会建立临时表,以便计算出最终的结果集。 | explain select item_name, COUNT(*) from billing_item_dis GROUP BY item_name order by item_name; |
四、Oracle执行计划各字段含义
explain执行顺序
Operation表示sql执行过程,查看怎么执行的,有两个规则:
- 根据Operation缩进判断,缩进最多的最先执行;
- Operation缩进相同时,最上面的是最先执行的;
访问数据
Oracle访问表中数据的方法有两种,一种直接访问表中数据,另一种是先访问索引,如果索引数据不符合目标SQL,就回表,符合就不回表,直接访问索引就可以。
Oracle直接访问表中数据的方法又分为两种:一种是全表扫描;另外一种是ROWID扫描
全表扫描(TABLE ACCESS FULL)
全表扫描是Oracle直接访问数据的一种方法,全表扫描时从第一个区(EXTENT)的第一个块(BLOCK)开始扫描,一直扫描的到表的高水位线(High Water Mark),这个范围内的数据块都会扫描到
全表扫描是采用多数据块一起扫的,并不是一个个数据库扫的,然后我们经常说全表扫描慢是针对数据量很多的情况,数据量少的话,全表扫描并不慢的,不过随着数据量越多,高水位线也就越高,也就是说需要扫描的数据库越多,自然扫描所需要的IO越多,时间也越多
注意:数据量越多,全表扫描所需要的时间就越多,然后直接删了表数据呢?查询速度会变快?其实并不会的,因为即使我们删了数据,高位水线并不会改变,也就是同样需要扫描那么多数据块
ROWID扫描(TABLE ACCESS BY ROWID)
ROWID也就是表数据行所在的物理存储地址,所谓的ROWID扫描是通过ROWID所在的数据行记录去定位。ROWID是一个伪列,数据库里并没有这个列,它是数据库查询过程中获取的一个物理地址,用于表示数据对应的行数。
访问索引
- 索引唯一扫描(INDEX UNIQUE SCAN)
- 索引全扫描(INDEX FULL SCAN)
- 索引范围扫描(INDEX RANGE SCAN)
- 索引快速全扫描(INDEX FAST FULL SCAN)
- 索引跳跃式扫描(INDEX SKIP SCAN)
索引唯一扫描(INDEX UNIQUE SCAN)
索引唯一性扫描(INDEX UNIQUE SCAN)是针对唯一性索引(UNIQUE INDEX)来说的,也就是建立唯一性索引才能索引唯一性扫描,唯一性扫描,其结果集只会返回一条记录。
索引范围扫描(INDEX RANGE SCAN)
索引范围扫描(INDEX RANGE SCAN)适用于所有类型的B树索引,一般不包括唯一性索引,因为唯一性索引走索引唯一性扫描。 当扫描的对象是非唯一性索引的情况,where谓词条件为Between、=、<、>等等的情况就是索引范围扫描,注意,可以是等值查询,也可以是范围查询。如果where条件里有一个索引键值列没限定为非空的,那就可以走索引范围扫描,如果改索引列是非空的,那就走索引全扫描
前面说了,同样的SQL建的索引不同,就可能是走索引唯一性扫描,也有可能走索引范围扫描。在同等的条件下,索引范围扫描所需要的逻辑读和索引唯一性扫描对比,逻辑读如何?索引范围扫描可能返回多条记录,所以优化器为了确认,肯定会多扫描,所以在同等条件,索引范围扫描所需要的逻辑读至少会比相应的唯一性扫描的逻辑读多1
索引全扫描(INDEX FULL SCAN)
索引全扫描(INDEX FULL SCAN)适用于所有类型的B树索引(包括唯一性索引和非唯一性索引)。
索引全扫描过程简述:索引全扫描是指扫描目标索引所有叶子块的索引行,但不意思着需要扫描所有的分支块,索引全扫描时只需要访问必要的分支块,然后定位到位于改索引最左边的叶子块的第一行索引行,就可以利用改索引叶子块之间的双向指针链表,从左往右依次顺序扫描所有的叶子块的索引行。
索引快速全扫描(INDEX FAST FULL SCAN)
索引快速全扫描和索引全扫描很类似,也适用于所有类型的B树索引(包括唯一性索引和非唯一性索引)。和索引全扫描类似,也是扫描所有叶子块的索引行,这些都是索引快速全扫描和索引全扫描的相同点
索引快速全扫描和索引全扫描区别:
- 索引快速全扫描只适应于CBO(基于成本的优化器)
- 索引快速全扫描可以使用多块读,也可以并行执行
- 索引全扫描会按照叶子块排序返回,而索引快速全扫描则是按照索引段内存储块顺序返回
- 索引快速全扫描的执行结果不一定是有序的,而索引全扫描的执行结果是有序的,因为索引快速全扫描是根据索引行在磁盘的物理存储顺序来扫描的,不是根据索引行的逻辑顺序来扫描的
索引跳跃式扫描(INDEX SKIP SCAN)
索引跳跃式扫描(INDEX SKIP SCAN)适用于所有类型的***复合B树索引***(包括唯一性索引和非唯一性索引),索引跳跃式扫描可以使那些在where条件中没有目标索引的前导列指定查询条件但是有索引的非前导列指定查询条件的目标SQL依然可以使用跳跃索引
表连接方法
两个表之间的表连接方法有排序合并连接、嵌套循环连接、哈希连接、笛卡尔连接
-
排序合并连接(merge sort join)
merge sort join是先将关联表的关联列各自做排序,然后从各自的排序表中抽取数据,到另一个排序表中做匹配。 -
嵌套循环连接(Nested loop join)
Nested loops 工作方式是循环从一张表中读取数据(驱动表outer table),然后访问另一张表(被查找表 inner table,通常有索引)。驱动表中的每一行与inner表中的相应记录JOIN。类似一个嵌套的循环。对于被连接的数据子集较小的情况,nested loop连接是个较好的选择。 -
哈希连接(Hash join)
散列连接是CBO 做大数据集连接时的常用方式,优化器使用两个表中较小的表(或数据源)利用连接键在内存中建立散列表,然后扫描较大的表并探测散列表,找出与散列表匹配的行。 -
笛卡尔连接(Cross join)
如果两个表做表连接而没有连接条件,而会产生笛卡尔积,在实际工作中应该尽可能避免笛卡尔积。
explain参数信息
Starts
含义:操作步骤被执行的次数
诊断价值:对于嵌套循环(NESTED LOOPS),若内层表的Starts
= 外层表的A-Rows
,说明执行方式正确。
异常示例:若Starts
远高于预期,可能存在不必要的重复计算(如索引扫描次数过多)。
E-Rows
含义:优化器基于统计信息估算的返回行数。
诊断价值:与 A-Rows 对比:若差异超过 1 个数量级(如 E-Rows=100,A-Rows=10000),可能导致优化器选择错误的执行路径(如本应走索引却选择全表扫描)。
影响因素:统计信息陈旧、数据分布不均(如高度倾斜的索引列)。
Cost (%CPU)
含义:Cost:优化器估算的总执行成本(基于 CPU、IO 等资源消耗)。%CPU:CPU 成本在总成本中的占比。
诊断价值:
高 CPU 占比(如 > 70%):可能存在函数索引、表达式过滤或排序操作消耗大量 CPU。
高 IO 占比:可能需要优化磁盘 IO(如增加 buffer cache、优化物理存储)。
A-Rows(Actual Rows,实际返回行数)
含义:操作步骤实际返回的行数。
与 E-Rows 对比:
A-Rows < E-Rows:可能导致索引选择错误(如本应走全表扫描却选择索引)。
A-Rows > E-Rows:可能导致内存溢出(如排序区不足)。
A-Time(Actual Time,实际执行时间)
含义:操作步骤实际消耗的时间(毫秒)。
诊断价值:定位耗时最长的操作(如排序、连接、大表扫描)。
异常示例:若某步骤A-Time
远高于其他步骤,可能存在锁等待、IO 瓶颈或算法低效。
Buffers(缓冲区读取次数)
含义:操作步骤访问数据缓冲区(buffer cache)的次数,反映逻辑读(一致性读)。
诊断价值:
高 Buffers 值:可能存在全表扫描或索引扫描效率低下。
与 A-Rows 结合分析:若Buffers/A-Rows
比值过大,说明每行数据的读取成本高(如回表操作过多)。
优化策略总结:
问题现象 | 可能原因 | 优化方向 |
---|---|---|
E-Rows 与 A-Rows 差异大 | 统计信息陈旧、数据分布不均 | 更新统计信息、使用直方图 |
高 CPU 占比(% CPU) | 函数索引、复杂表达式计算、排序 | 优化表达式、增加索引、调整 PGA |
高 Buffers 值 | 全表扫描、索引设计不合理 | 添加索引、优化查询条件 |
某步骤 A-Time 异常高 | 锁等待、IO 瓶颈、算法低效 | 分析等待事件、优化存储结构、调整执行计划 |
五、MySQL与Oracle执行计划对比
(一)信息展示与侧重点
MySQL 执行计划的信息相对简洁明了,各字段名称通俗易懂,重点突出索引使用、表访问类型、预估行数等信息,便于快速发现常见的性能问题,如索引未命中、全表扫描等。而 Oracle 执行计划展示的信息更为丰富和详细,除了基本的操作类型、对象名称等,还包含成本估算、基数等信息,在复杂查询和大型数据库环境下,这些详细信息有助于更精确地分析和优化执行计划,但也增加了理解和分析的难度。
(二)优化器策略与执行计划生成
MySQL 的优化器相对简单直接,在生成执行计划时,主要依据统计信息和一些固定的规则,对于简单查询能快速生成执行计划,但在处理复杂查询时,可能无法找到最优的执行策略。Oracle 的优化器则更为复杂和智能,它会综合考虑多种因素,如数据分布、索引选择性、系统负载等,通过成本估算等方式,尝试生成最优的执行计划,在复杂业务场景和大型数据处理中具有一定优势,但也可能因为复杂的优化过程和不准确的统计信息,导致生成不理想的执行计划。
(三)连接方式与性能表现
在连接方式上,MySQL 支持嵌套循环连接、哈希连接等,对于简单的连接查询,嵌套循环连接使用较为普遍;在处理大数据量的连接时,哈希连接能发挥较好的性能。Oracle 同样支持多种连接方式,如嵌套循环连接、哈希连接、排序合并连接等,并且在不同的场景下,优化器会根据数据特点和查询条件自动选择合适的连接方式。总体而言,在简单连接场景下,两者性能差异不明显;但在复杂多表连接和大数据量处理时,Oracle 丰富的连接策略和强大的优化器可能使其在性能表现上更具优势,但实际性能还需根据具体的业务需求、数据分布等因素进行评估。
(四)执行计划稳定性
MySQL 的执行计划在数据库版本升级或数据结构、数据量发生变化时,可能出现较大波动,导致原本性能良好的查询突然变慢。这是因为其优化器规则相对固定,对环境变化的适应性较弱。Oracle 的执行计划相对较为稳定,即使数据库环境有所变化,其优化器凭借强大的统计信息收集和分析能力,能够在一定程度上保持执行计划的合理性,但如果统计信息不准确或未及时更新,也可能导致执行计划出现偏差。
六、索引失效情况
七字诀:模型数空运最快
- 模:模糊查询LIKE以%开头
- 型:数据类型错误
- 数:对索引字段使用内部函数
- 空:索引列是NULL
- 运:索引列进行四则运算
- 最:复合索引不按索引列最左开始查找
- 快:全表查找预计比索引更快
🔔🔔本篇博客就到此结束啦!如果你还对数据库开发相关内容感兴趣欢迎进我的主页!
求关注!求点赞!求收藏!
下篇博客将会更新数据库存储过程相关内容,期待与你再见喔!🌈🌈