【Oracle篇】SQL执行计划之访问路径(含表级别、B树索引、位图索引、簇表四大类访问路径)(第三篇,总共七篇)

💫《博主介绍》:✨又是一天没白过,我是奈斯,DBA一名✨

💫《擅长领域》:✌️擅长Oracle、MySQL、SQLserver、阿里云AnalyticDB for MySQL(分布式数据仓库)、Linux,也在扩展大数据方向的知识面✌️

💖💖💖大佬们都喜欢静静的看文章,并且也会默默的点赞收藏加关注💖💖💖

    天空一声巨响,靓仔闪亮登场。中秋佳节,月圆人团圆,今天是中秋节,上午抽空把执行计划系列的第三篇撰写一下,因为有些小伙伴在催更啦,感谢小伙伴们对我博客的认可,小伙伴们的支持是我坚持写作的最大动力。Oracle执行计划系列的这七篇文章更多的是偏向理论的学习,所以学习这个系列是需要有一定Oracle基础的,相信各位小伙伴都是高度自律的,并且愿意投入时间和精力去学习这个系列。这份坚持与努力,定将引领各位走向更广阔的数据库技术天地。

    第一篇《统计信息和动态采样的深度剖析》总共3.2万字;第二篇《全面理解优化器和SQL语句的解析步骤(含执行计划的详细分析和四种查看方式)》总共2.6万字;今天第三篇《SQL执行计划之访问路径(含表级别、B树索引、位图索引、簇表四大类访问路径)》总共3.6万字,希望各位小伙伴可以认真学习,因为只有搞明白执行计划的访问路径,才能更好的优化SQL语句,SQL调优的本质就是调整执行计划。

   

    还是老规矩为了让大家更容易消化和逐个理解,我将分成七篇文章来进行介绍,以便大家劳逸结合而不至于感觉到阅读枯燥,七篇的内容分别如下:

  • 第一篇:统计信息和动态采样的深度剖析
  • 第二篇:全面理解优化器和SQL语句的解析步骤(含执行计划的详细分析和四种查看方式)
  • 第三篇:SQL执行计划之访问路径(含表级别、B树索引、位图索引、簇表四大类访问路径)(当前篇)
  • 第四篇:SQL执行计划之多表连接
  • 第五篇:精细化查询优化:如何有效使用Hint干预执行计划
  • 第六篇:掌握SQL Tuning Advisor优化工具:从工具使用到SQL优化的全方位指南
  • 第七篇:SQL性能优化实战案例(从15秒优化到0.08秒)

                                                       

目录

一、表级别的访问路径(Oracle默认表类型是堆表)

堆表介绍:

1、执行计划访问路径之全表扫描(Full Table Scans)

1.1 全表扫描(Full Table Scans)的工作原理

1.2 全表扫描(Full Table Scans)案例讲解

2、执行计划访问路径之按Rowid访问表(Table Access by Rowid)

2.1 按Rowid访问表(Table Access by Rowid)的工作原理

2.2 按Rowid访问表(Table Access by Rowid)案例讲解

3、执行计划访问路径之表取样扫描(Sample Table Scans)

3.1 表取样扫描(Sample Table Scans)案例讲解

二、B树索引的访问路径

1、执行计划访问路径之索引唯一扫描(Index Unique Scans)

1.1 索引唯一扫描(Index Unique Scans)的工作原理

1.2 索引唯一扫描(Index Unique Scans)案例讲解

2、执行计划访问路径之索引范围扫描(Index Range Scans)

2.1 索引范围扫描(Index Range Scans)的工作原理

2.2 索引范围扫描(Index Range Scans)案例讲解

2.3 索引范围扫描(Index Range Scans)降序案例讲解

3、执行计划访问路径之索引全扫描(Index Full Scans)

3.1 索引全扫描(Index Full Scans)的工作原理

3.2 索引全扫描(Index Full Scans)案例讲解

4、执行计划访问路径之索引快速全扫描(Index Fast Full Scans)

4.1 索引快速全扫描(Index Fast Full Scans)的工作原理

4.2 索引快速全扫描(Index Fast Full Scans)案例讲解

5、执行计划访问路径之索引跳跃扫描(Index Skip Scans)

5.1 索引跳跃扫描(Index Skip Scans)的工作原理

5.2 索引跳跃扫描(Index Skip Scans)案例讲解

6、执行计划访问路径之索引连接扫描(Index Join Scans)

6.1 索引连接扫描(Index Join Scans)的工作原理

6.2  索引连接扫描(Index Join Scans)案例讲解

三、位图索引的访问路径

四、簇表的访问路径

簇表介绍:

簇表之簇索引:簇索引是一种特殊的索引,说直白点就是为簇表创建簇索引,使用簇索引来定位簇表的数据,加快数据访问

簇表之hash簇表:hash簇类似于索引簇,只是索引键被散列函数所取代。不存在单独的聚集索引。在hash簇中,数据就是索引。

1、执行计划访问路径之簇扫描(Cluster Scans)

1.1 簇扫描(Cluster Scans)的工作原理

1.2 簇扫描(Cluster Scans)案例讲解

2、执行计划访问路径之哈希扫描(Hash Scans)

2.1 哈希扫描(Hash Scans)的工作原理

2.2 哈希扫描(Hash Scans)案例讲解


                   

什么是访问路径:

    访问路径(access path)是查询用于从行源(row source)检索行的一种技术。“行源(row source)”是什么可以参考上一篇文章哦,直通车👉【Oracle篇】全面理解优化器和SQL语句的解析步骤(含执行计划的详细分析和四种查看方式)(第二篇,总共七篇)-CSDN博客👈

                    

访问路径介绍:

    行源是执行计划中某个步骤返回的一组行。行源可以是表、视图或联接或分组操作的结果。

    Oracle对不同的关系数据结构使用不同的访问路径,下表总结了主要数据结构的常见访问路径。

访问路径

Access Path

堆组织表

Heap-Organized Tables

索引组织表IOT

B-Tree Indexes and IOTs

位图索引

Bitmap Indexes

簇表

Table Clusters

Full Table Scans

Table Access by Rowid

Sample Table Scans

Index Unique Scans

Index Range Scans

Index Full Scans

Index Fast Full Scans

Index Skip Scans

Index Join Scans

Bitmap Conversion to Rowid

Bitmap Index Single Value

Bitmap Index Range Scans

Bitmap Merge

Cluster Scans

Hash Scans

    计划生成器通过尝试不同的访问路径、连接方法和连接顺序来探索查询块的各种计划。许多计划都是可能的,因为数据库可以使用各种组合来产生相同的结果。优化器选择成本最低的计划。 一般来说,索引访问路径对于访问表的小部分行数据时更有效,而在访问表的大部分行数据时,全表扫描更有效。 

                  

官方文档对访问路径的详细介绍(12c版本):

Optimizer Access Paths

             

       

一、表级别的访问路径(Oracle默认表类型是堆表)

    表是Oracle数据库中数据组织的基本单位。 关系表是最常见的表类型,不同的关系表具有以下组织特征:

  • 堆组织的表(Heap-Organized Tables)不按任何特定的顺序存储行数据(Oracle的默认表类型)。
  • 按索引组织的表(B-Tree Indexes and IOTs)根据主键值对行数据进行排序(MySQL的默认表类型)。
  • 外部表(external table)是只读表,其元数据存储在数据库中,但其数据存储在数据库之外。
  • 簇表(Cluster Table)是一种特殊的存储数据方式,它将一组经常一起使用的表中相同的列存储在相同的数据块中。

    关于不同类型的表适合在什么场景下使用,并且他们分别的优缺点都有哪些,博主简单做了一个介绍。

分类

普通堆表

Heap-Organized Tables

全局临时表

分区表

Table partition

索引组织表IOT

Index-organized table

簇表

Cluster Table

介绍

堆组织的表不按任何特定的顺序存储行。oracle默认的表类型,它是一种没有特殊属性或结构的表,数据按照插入的顺序存储,存储快、读取慢。

把数据单独存放,索引上保存数据位置的数据组织形式。

全局临时表是一种特殊类型的表,用于存储临时数据。与普通堆表不同,全局临时表的数据在会话结束后会自动清空。全局临时表分两类:一个是基于会话的全局临时表(on commit preserve rows),一个是基于事务的全局临时表(on commit delete rows)

分区表是一种将表数据分割成多个逻辑部分的表。每个分区可以独立进行管理,可以根据分区键进行数据的快速查询和维护。

按索引组织的表根据主键值对行进行排序。索引组织表是一种特殊类型的表,数据存储在索引结构中,而不是普通堆表的数据块中,存储慢、读取快。

簇表是一种特殊的存储数据方式,它将一组经常一起使用的表中相同的列存储在相同的数据块中。

场景

适合大部分设计场景

1)临时数据(接口表设计):适用于存储临时数据或中间结果,特别是在复杂查询或大数据量的情况下。

2)多用户环境:适用于多用户环境,每个会话可以独立使用全局临时表。

1)大型表:适用于大型表,可以将数据分割成多个分区,提高查询性能。

2)历史数据(日志表):适用于存储历史数据,可以根据日期范围等分区键进行快速查询。

1)频繁查询:适用于需要频繁根据索引进行查询的表,可以提供更高的查询性能。

2)少量更新的表:适合对数据进行较少更新操作的表,如只有定期批量插入数据的情况。

关联查询频繁:如果多个表之间有频繁的关联查询,且DML操作较少,可以考虑使用簇表来提高查询性能。

优点

1)简单易用:普通堆表是最简单的表类型,没有额外的复杂结构或属性,创建和维护普通堆表非常方便。

2)灵活性高:可以存储任意类型的数据,并支持各种查询和操作,适合大多数常规的数据存储需求。

1)提供临时存储:全局临时表可以用于存储临时数据,避免在应用程序中创建临时表或使用其他临时存储方式,并且删除记录非常高效(自动清空)。

2)高效使用空间:全局临时表的数据仅在会话期间存在,不占用永久存储空间,那么就是产生很少的日志。

3)不同的SESSION会话间独立:全局临时表的数据只在当前会话中可见,不同的会话之间独立,不产生锁。

1)提高查询性能:分区表可以根据查询条件仅扫描相关分区,实现有效的分区消除(即可以对某一个分区进行truncate),减少数据的访问量,提高查询性能。

2)管理灵活性:可以独立管理每个分区,包括备份、恢复和维护操作(分区交换),提高管理效率。

3)数据划分:可以根据业务需求将数据按照时间、地理位置等规则进行划分,提高数据的组织和管理效率。

1)查询性能高:索引组织表的数据存储在索引结构中,索引即数据,数据即索引。可以更快地定位和访问数据,避免了回表,提高查询性能。

2)数据紧凑:索引组织表的数据存储在索引结构中,可以减少数据块之间的碎片,提高存储效率。

1)连接查询性能高:不同表中相同的列存储在一起,减少了连接表带来的磁盘I/O,降低了系统开销,从而提高查询性能。

2)节省存储空间:原来需要单独存放在多张表的列,现在作为共享列来存储,减少了磁盘空间的占用。

缺点

1)更新日志开销较大。

2)性能较低:由于数据存储的无序性,随着数据量的增加,查询和更新性能可能会下降,并且delete可能无法释放空间(HWM High Water Mark不下降)。

3)索引效率低:普通堆表需要使用索引来提高查询性能,否则可能出现全表扫描的情况(表记录太大检索太慢),且索引效率相对较低。

1)数据丢失:全局临时表的数据在会话结束后会被清空,如果需要保留数据,必须在会话结束前将数据复制到其他表中。

2)受临时表空间影响:全局临时表使用临时表空间存储数据,如果临时表空间不足,可能会导致表操作失败。

1)配置复杂:分区表的管理和维护相对复杂,创建和管理分区表需要额外的配置和操作。

2)空间利用:分区表可能会导致一些分区不均衡,某些分区可能会占用更多的空间。

3)索引维护复杂度:频繁插入和删除数据时,分区表的索引维护开销较大;对某个分区执行TRUNCATE、DROP操作时会使全局索引失效。

1)空间利用率低:索引组织表的数据存储在索引结构中,相比于普通堆表,会占用更多的存储空间。

2)更新删除开销较大:由于数据存储的特殊结构,向索引组织表更新删除数据时意味着要更新索引。

1)逻辑复杂:创建和维护簇表比普通表更加复杂。

2)DML操作开销大:进行表更新等操作时,需要更多的资源来维护簇表的结构。

3)不适合对单表的全表扫描,因为只能引起对其它表的全表扫描。

                     

堆表介绍:

    默认情况下,表被组织为一个堆,这意味着数据库将行放在它们最适合的位置,而不是按照用户指定的顺序。当用户添加行时,数据库将这些行放在数据段的第一个可用空间中。不能保证按照插入行的顺序检索行。

                

数据块和数据段中的行存储:

    数据库将行存储在数据块中。在表中,数据库可以在块底部的任何地方写入一行。Oracle数据库使用包含行目录和表目录的块开销来管理块本身。

    一个扩展区由逻辑上连续的数据块组成。这些块在磁盘上可能不是物理上连续的。段是一组区,包含表空间中逻辑存储结构的所有数据。例如,Oracle数据库分配一个或多个区来形成表的数据段。数据库还分配一个或多个区来形成表的索引段。

    默认情况下,数据库对永久的本地管理的表空间使用自动段空间管理(ASSM)。当会话首次向表中插入数据时,数据库会格式化位图块。位图跟踪段中的块。数据库使用位图来查找空闲块,然后在写入前格式化每个块。ASSM将插入分散在各个块中,以避免并发问题。

    高水位线(HWM)是数据段中的一个点,超过该点,数据块将被取消格式化,并且从未被使用过。在HWM之下,块可以被格式化和写入,格式化和空,或者未格式化。低高水位线(低HWM)标记这样一个点,在该点之下,所有数据块都被认为已格式化,因为它们包含数据或以前包含的数据。

    在全表扫描过程中,数据库读取低HWM之前的所有块,这些块已知已被格式化,然后读取段位图以确定HWM和低HWM之间的哪些块已被格式化并且可以安全读取。数据库知道不要读取HWM,因为这些块是未格式化的。

                 

ROWID对于行访问的重要性:

    堆组织的表中的每一行都有一个rowid,这个rowid对于这个表是唯一的,它对应于一个行段的物理地址。rowid是一行的10字节物理地址。

    rowid指向特定的文件、块和行号。例如,在rowid AAAPecAAFAAAABSAAA中,最后一个AAA表示行号。行号是行目录条目的索引。行目录条目包含指向块中行位置的指针。

    数据库有时可以在块的底部移动一行。例如,如果启用了行移动,则该行可以因为分区键更新、闪回表操作、收缩表操作等而移动。如果数据库在块中移动一行,则数据库更新行目录条目以修改指针。rowid保持不变。

    Oracle数据库在内部使用rowids来构建索引。例如,B树索引中的每个键都与指向相关行地址的rowid相关联。物理rowids提供了对表行最快的访问,使数据库只需一次I/O就可以检索一行。

              

Direct Path Reads(直接路径读取):

    在直接路径读取中,数据库将缓冲区从磁盘直接读取到PGA中,完全绕过SGA。下图显示了分散读取和顺序读取(在SGA中存储缓冲区)与直接路径读取之间的差异。

    此图描述了从缓冲区读取到SGA缓冲区缓存和进程PGA的数据。读取到SGA缓冲区缓存中的数据是通过db顺序读取或db分散读取获取的。读取到过程PGA中的数据是通过直接路径读取获得的。

    Oracle数据库可能执行直接路径读取的情况包括:

  • 执行CREATE TABLE AS SELECT语句
  • 执行ALTER REBUILD或ALTER MOVE语句
  • 从临时表空间读取
  • 并行查询
  • 从LOB段读取

    虽然上面列了几种表类型,但 下面主要还是介绍下堆组织的表(Heap-Organized Tables)的优化器访问路径,因为Oracle默认的表类型是堆表,所以需要重点了解下。


                                  

1、执行计划访问路径之全表扫描(Full Table Scans)

    全表扫描从表中读取所有行,然后筛选出不符合选择条件的行。一般来说,当优化器无法使用不同的访问路径,或者另一个可用的访问路径成本较高时,它会选择全表扫描。下表显示了选择全表扫描的典型原因。

原因描述
不存在索引如果不存在索引,则优化器使用全表扫描。
列上有索引,但是在索引列上使用了函数除非索引是基于函数的索引,否则数据库会索引列的值,而不是应用函数的列的值。一个典型的应用程序级错误是对字符列(如char_col)进行索引,然后使用WHERE char_col=1等语法查询该列。数据库隐式地将TO_NUMBER函数应用于常数1,这会阻止使用索引。
发出SELECT COUNT(*)查询,并且存在索引,但索引列包含空值优化器无法使用索引来计算表行数,因为索引不能包含空条目。
索引类型为组合索引,但在where条件中没有使用索引的前置列例如,员工上可能存在索引(first_name、last_name)。如果用户使用谓词WHERE last_name='KING'发出查询,则优化器可能不会选择索引,因为前置列列first_name不在where条件中。然而,在这种情况下,优化器可能会选择使用索引跳过扫描。
查询是非选择性的如果优化器确定查询需要表中的大部分块,那么即使索引可用,它也会使用全表扫描。全表扫描可以使用更大的I/O调用。减少大型I/O调用比进行许多小型调用更便宜。
表的统计信息已过时例如,一个表很小,但现在已经变大了。如果表统计信息已经过时,不能反映表的当前大小,那么优化器就不知道索引现在比全表扫描更有效。
表的行数据非常少如果一个表在高水位线下包含的块少于n个,其中n等于DB_FILE_MOLIBLOCK_READ_COUNT初始化参数的设置,那么全表扫描可能比索引范围扫描便宜。无论访问的表或存在的索引的比例如何,扫描都可能更便宜。
该表具有高度的并行性表的高度并行性使优化器倾向于在范围扫描中进行全表扫描。查询ALL_TABLES中的值。DEGREE列用于确定平行度。
查询时使用了Hint干预强制进行全表扫描

使用Hint干预优化器使用全表扫描。全表扫描的Hint干预语法为:/*+ FULL(table alias) */

                        

1.1 全表扫描(Full Table Scans)的工作原理

    在全表扫描中,数据库按顺序读取高水位线(high water mark)下的每个格式化块。数据库只读取每个块一次。下图显示了表段的扫描,显示了扫描如何跳过高水位线以下的未格式化块。

    此图显示了一系列水平块。前6块是灰色的。最后一个灰色块的右边缘标记为“低HWM(Low HWM)”。接下来的2个是空的,接着是一个灰色的块,然后是1个空的块。空块的右边缘标记为“低HWM(Low HWM)”。接下来的4个块是空的。图例表示空块为“从未使用,未格式化(Never Used, Unformatted)”。灰色块为“已使用(Used)”。箭头从左向右移动,在每个已使用的块上向下移动,然后停在高水位标记处。

    由于这些块是相邻的,数据库可以通过使I/O调用大于单个块来加速扫描,这被称为多块读取。读取调用的大小从一个块到DB_FILE_MULTIBLOCK_READ_COUNT初始化参数指定的块数不等。例如,将此参数设置为4指示数据库在一次调用中最多读取4个块。

    在全表扫描期间缓存块的算法很复杂。例如,数据库缓存块的方式因表的大小而异。

                   

1.2 全表扫描(Full Table Scans)案例讲解

    查询employees表,通过salary字段筛选出月薪超过4000美元的人,并且salary字段并没有创建索引。

SELECT salary 
FROM   hr.employees 
WHERE  salary > 4000;

    通过dbms_xplan.DISPLAY_CURSOR方式查看SQL语句的执行计划,由于salary字段上不存在索引,优化器无法使用索引范围扫描,因此使用了全表扫描。关于通过dbms_xplan.DISPLAY_CURSOR方式查看SQL语句的执行计划可以参考上一篇文章哦,直通车👉【Oracle篇】全面理解优化器和SQL语句的解析步骤(含执行计划的详细分析和四种查看方式)(第二篇,总共七篇)-CSDN博客👈

SQL_ID  54c20f3udfnws, child number 0
-------------------------------------
select salary from hr.employees where salary > 4000Plan hash value: 3476115102---------------------------------------------------------------------------
| Id| Operation         | Name      | Rows | Bytes |Cost (%CPU)| Time     |
---------------------------------------------------------------------------
|  0| SELECT STATEMENT  |           |      |       |    3 (100)|          |
|* 1|  TABLE ACCESS FULL| EMPLOYEES |   98 |  6762 |    3   (0)| 00:00:01 |
---------------------------------------------------------------------------Predicate Information (identified by operation id):
---------------------------------------------------1 - filter("SALARY">4000)

                        

2、执行计划访问路径之按Rowid访问表(Table Access by Rowid)

    rowid是数据存储位置的内部表示。行的rowid指定了包含该行的数据文件和数据块以及该行在该块中的位置。通过指定行ID来定位行是检索单行的最快方法,因为它指定了行在数据库中的确切位置。 小提示💥:Rowid可以在不同版本之间更改。不建议根据位置访问数据,因为行可以移动。

    当优化器选择按Rowid访问表时,在大多数情况下,数据库在扫描一个或多个索引后通过rowid访问表。但是,通过rowid访问表不需要遵循每次索引扫描。如果索引包含所有需要的列,则可能无法通过rowid进行访问。

                  

2.1 按Rowid访问表(Table Access by Rowid)的工作原理

要通过rowid访问表,数据库将执行多个步骤。数据库执行以下操作:

  1. 从语句WHERE子句或通过一个或多个索引的索引扫描获取选定行的Rowid,对于索引中不存在的语句中的列,可能需要表访问。
  2. 根据Rowid定位表中的每个选定行。

                         

2.2 按Rowid访问表(Table Access by Rowid)案例讲解

    查询employees表,通过employee_id字段筛选出来id大于190的行数据,employee_id字段作为主键,那么数据库为其创建了emp_emp_id_pk主键索引

SELECT * 
FROM   employees 
WHERE  employee_id > 190;

    以下计划的步骤2显示了对employees表上的emp_emp_id_pk索引的范围扫描。数据库使用索引从中获得Rowid,从employees表中查找相应的行,然后检索它们。步骤1中显示的批量访问意味着数据库从索引中检索一些Rowid,然后尝试访问块中的行,以改善集群并减少数据库必须访问块的次数。

--------------------------------------------------------------------------------
|Id| Operation                           | Name     |Rows|Bytes|Cost(%CPU)|Time|
--------------------------------------------------------------------------------
| 0| SELECT STATEMENT                    |             |  |    |2(100)|        |
| 1|  TABLE ACCESS BY INDEX ROWID BATCHED|EMPLOYEES    |16|1104|2  (0)|00:00:01|
|*2|   INDEX RANGE SCAN                  |EMP_EMP_ID_PK|16|    |1  (0)|00:00:01|
--------------------------------------------------------------------------------Predicate Information (identified by operation id):
---------------------------------------------------2 - access("EMPLOYEE_ID">190)

                                    

3、执行计划访问路径之表取样扫描(Sample Table Scans)

    表取样扫描(Sample Table Scans)从简单的表或复杂的SELECT语句(如涉及联接和视图的语句)中检索随机样本数据。当语句FROM子句包含sample关键字时,数据库使用表取样扫描。FROM子句中的sample关键字具有以下形式:

  1. SAMPLE (sample_percent):数据库读取表中指定百分比的行来执行表取样扫描。
  2. SAMPLE BLOCK (sample_percent):数据库读取指定百分比的表块来执行表取样扫描。

    sample_percent指定要包含在样本中的总行数或块数的百分比。该值必须在. 000001到100的范围内,但不包括100。此百分比表示块抽样中的每一行或每一组行被选为样本的概率。这并不意味着数据库准确地检索sample_percent的行。 小提示💥:只有在全表扫描(full table scans)或索引快速全扫描(index fast full scans)期间才可能进行块采样。如果存在更有效的执行路径,则数据库不会对块进行采样。若要保证对特定表或索引进行块采样,请使用FULL或INDEX_FFS进行Hint干预。

                    

3.1 表取样扫描(Sample Table Scans)案例讲解

    此示例使用表取样扫描(Sample Table Scans)来访问员工表的1%,按块而不是行进行采样。

SELECT * FROM hr.employees SAMPLE BLOCK (1); 

    通过dbms_xplan.DISPLAY_CURSOR方式查看SQL语句的执行计划

-------------------------------------------------------------------------
| Id  | Operation            |  Name       | Rows  | Bytes | Cost (%CPU)|
-------------------------------------------------------------------------
|   0 | SELECT STATEMENT     |             |     1 |    68 |     3  (34)|
|   1 |  TABLE ACCESS SAMPLE | EMPLOYEES   |     1 |    68 |     3  (34)|
-------------------------------------------------------------------------

                 

                

二、B树索引的访问路径

    索引是一种可选结构,与表或表簇相关联,可以加快数据访问。通过在表的一列或多列上创建索引,在某些情况下,可以从表中检索一小组随机分布的行。索引是减少磁盘I/O的许多方法之一。

    B树是balanced trees(平衡树)的缩写,是最常见的数据库索引类型。B树索引是按范围划分的有序值列表。通过将一个键与一行或一系列行相关联,B树为各种查询提供了出色的检索性能,包括精确匹配和范围搜索。

           

 B树索引结构:

    B树索引有两种类型的块:用于搜索的分支块和存储值的叶块。

    下图说明了B树索引的逻辑结构。分支块存储在两个关键字之间进行分支决策所需的最小关键字前缀。叶块包含每个索引数据值和用于定位实际行的相应rowid。每个索引条目按(key,rowid)排序。叶块是双重链接的。

    该图形分为两个带虚线边框的框,一个在另一个的上面。顶部标记为“分支块(Branch Blocks)”,下部标记为“叶块(Leaf Blocks)”。该图形包含一个方框树。顶部是一个包含以下值的框:0-40、41-90等等。三个箭头指向第二排分支块。这一行中的一个块具有值1-10、11-19、20-25等等。相邻块的值为41-48、49-53等等。最后一个黑色的值为200-209、210-220等等。

    最左边和最右边的块各有一对向下的箭头。最左边的箭头指向包含以下值的叶块(Leaf Blocks):0,rowid、0,rowid、10,rowid等等。右边的块包含以下值:11,rowid、11,rowid等等。下一个块包含值:221,rowid、222,rowid等等。最右边的块包含以下值:246,rowid、248,rowid等等。除了最左边和最右边的块之外,底行中的每个叶块都通过双向箭头链接到两侧的块。

                 

 B树索引是如何影响扫描的:

    上图中显示了彼此相邻的叶块。例如,1-10块位于11-19块的旁边和之前。此排序显示了连接索引条目的链表。然而,索引块不需要在索引段内按顺序存储。例如,246-250块可以出现在段中的任何位置,包括直接在1-10块之前。因此,有序索引扫描必须执行单块I/O。数据库必须读取索引块以确定下一步必须读取哪个索引块。

    索引块体将索引条目存储在堆中,就像表行一样。例如,如果值10首先插入到表中,则具有键10的索引条目可能会插入到索引块的底部。如果接下来将0插入表中,则键0的索引条目可能会插入到10的条目的顶部。因此,块体中的索引条目不是按关键字顺序存储的。但是,在索引块中,行标题按键顺序存储记录。例如,标题中的第一条记录指向键为0的索引条目,依此类推,直到指向键为10的索引条目的记录。因此,索引扫描可以读取行标题以确定范围扫描的开始和结束位置,从而避免了读取块中每个条目的必要性。

               

B树索引之唯一和非唯一索引:

    在非唯一索引中,数据库通过将rowid作为额外的列附加到键来存储rowid。该条目添加了一个长度字节,以使密钥唯一。例如,在上图所示的非唯一索引中,第一个索引键是0,rowid,而不仅仅是0。数据库先按索引键值,然后按rowid升序对数据进行排序。例如,条目排序如下:

0,AAAPvCAAFAAAAFaAAa
0,AAAPvCAAFAAAAFaAAg
0,AAAPvCAAFAAAAFaAAl
2,AAAPvCAAFAAAAFaAAm

    在唯一索引中,索引键不包括rowid。数据库只按索引键值对数据进行排序,如0、1、2等。

             

B树索引对于空值的处理:

    B树索引从不存储完全为空的键,这对于优化器如何选择访问路径非常重要。此规则的结果是单列B树索引从不存储空值。

    在下列两个查询中,employees表在employee_id上有主键索引,在department_id上有唯一索引。department_id列可以包含null值,但employee_id列不能存在null值。

SQL> SELECT COUNT(*) FROM employees WHERE department_id IS NULL;COUNT(*)
----------1SQL> SELECT COUNT(*) FROM employees WHERE employee_id IS NULL;COUNT(*)
----------0

    以下示例中显示了优化器为employees中所有部门ID的查询选择全表扫描。优化器无法在employees表中的department_id字段上使用索引,因为不能保证索引包含表中每一行的条目。

SQL> EXPLAIN PLAN FOR SELECT department_id FROM employees;Explained.SQL> SELECT PLAN_TABLE_OUTPUT FROM TABLE(DBMS_XPLAN.DISPLAY());PLAN_TABLE_OUTPUT
---------------------------------------------------------------------------
Plan hash value: 3476115102---------------------------------------------------------------------------
|Id | Operation         | Name      | Rows| Bytes | Cost (%CPU)| Time     |
---------------------------------------------------------------------------
| 0 | SELECT STATEMENT  |           | 107 |   321 |     2   (0)| 00:00:01 |
| 1 |  TABLE ACCESS FULL| EMPLOYEES | 107 |   321 |     2   (0)| 00:00:01 |
---------------------------------------------------------------------------

    以下示例中显示了优化器可以使用department_id上的索引来查询特定的部门id,因为所有非空行都被索引。

SQL> EXPLAIN PLAN FOR SELECT department_id FROM employees WHERE department_id=10;Explained.SQL> SELECT PLAN_TABLE_OUTPUT FROM TABLE(DBMS_XPLAN.DISPLAY());PLAN_TABLE_OUTPUT
---------------------------------------------------------------------------
Plan hash value: 67425611---------------------------------------------------------------------------
|Id| Operation        | Name              |Rows|Bytes|Cost (%CPU)| Time   |
---------------------------------------------------------------------------
| 0| SELECT STATEMENT |                   | 1 |   3 |   1   (0)| 00:0 0:01|
|*1|  INDEX RANGE SCAN| EMP_DEPARTMENT_IX | 1 |   3 |   1   (0)| 00:0 0:01|
---------------------------------------------------------------------------Predicate Information (identified by operation id):1 - access("DEPARTMENT_ID"=10)

    以下示例中显示了当条件中排除空值时,优化器会选择索引扫描:

SQL> EXPLAIN PLAN FOR SELECT department_id FROM employees 
WHERE department_id IS NOT NULL;Explained.SQL> SELECT PLAN_TABLE_OUTPUT FROM TABLE(DBMS_XPLAN.DISPLAY());PLAN_TABLE_OUTPUT
---------------------------------------------------------------------------
Plan hash value: 1590637672---------------------------------------------------------------------------
| Id| Operation        | Name              |Rows|Bytes| Cost (%CPU)| Time |
---------------------------------------------------------------------------
| 0| SELECT STATEMENT |                   |106| 318 |   1   (0)| 00:0 0:01|
|*1|  INDEX FULL SCAN | EMP_DEPARTMENT_IX |106| 318 |   1   (0)| 00:0 0:01|
---------------------------------------------------------------------------Predicate Information (identified by operation id): 1 - filter("DEPARTMENT_ID" IS NOT NULL)

                  

1、执行计划访问路径之索引唯一扫描(Index Unique Scans)

    索引唯一扫描最多返回1个rowid。索引唯一扫描(Index Unique Scans)需要在where条件中使用等于运算符。具体来说,只有当where查询使用相等运算符引用唯一索引键中的所有列时,数据库才会执行唯一扫描,例如,当WHERE prod_id=10时。

    unique或primary key约束本身不足以生成索引唯一扫描(Index Unique Scans),因为该列上可能已经存在非唯一索引。下面示例中创建了一个t_table表,然后在numcol字段上创建非唯一索引:

SQL> CREATE TABLE t_table(numcol INT);
SQL> CREATE INDEX t_table_idx ON t_table(numcol);
SQL> SELECT UNIQUENESS FROM USER_INDEXES WHERE INDEX_NAME = 'T_TABLE_IDX';UNIQUENES
---------
NONUNIQUE

    以下SQL中在具有非唯一索引的列上创建primary key约束,从而导致索引范围扫描,而不是索引唯一扫描:

SQL> ALTER TABLE t_table ADD CONSTRAINT t_table_pk PRIMARY KEY(numcol);
SQL> SET AUTOTRACE TRACEONLY EXPLAIN
SQL> SELECT * FROM t_table WHERE numcol = 1;Execution Plan
----------------------------------------------------------
Plan hash value: 868081059---------------------------------------------------------------------------
| Id | Operation        | Name        |Rows  |Bytes |Cost (%CPU)|Time     |
---------------------------------------------------------------------------
|  0 | SELECT STATEMENT |             |    1 |   13 |    1   (0)|00:00:01 |
|* 1 |  INDEX RANGE SCAN| T_TABLE_IDX |    1 |   13 |    1   (0)|00:00:01 |
---------------------------------------------------------------------------Predicate Information (identified by operation id):
---------------------------------------------------1 - access("NUMCOL"=1)

               

1.1 索引唯一扫描(Index Unique Scans)的工作原理

    扫描按指定键的顺序搜索索引。索引唯一扫描一找到第一条记录就停止处理,因为不可能有第二条记录。数据库从索引条目中获取rowid,然后检索由rowid指定的行。

    下图说明了索引唯一扫描。该语句请求prod_id列中产品ID为19的记录,该列有一个主键索引。

    该图形分为两个带虚线边框的框,一个在另一个的上面。顶部标记为“分支块(Branch Blocks)”,下部标记为“叶块(Leaf Blocks)”该图形包含一个方框树。顶部是一个包含以下值的框:0-40、41-90等等。三个箭头指向第二排分支块。这一行中的一个块具有值:1-10、11-19、20-25等等。相邻块的值为41-48、49-53等等。最后一个黑色的值为200-209、210-220等等。

    最左边和最右边的块各有一对向下的箭头。最左边的箭头指向包含以下值的叶块:0,rowid、1,rowid、10,rowid等等。右边的块包含以下值:11,rowid、12,rowid等等。下一个块包含值:221,rowid、222,rowid等等。最右边的块包含以下值:246,rowid、248,rowid等等。除了最左边和最右边的块之外,底行中的每个叶块都通过双向箭头链接到两侧的块。

                           

1.2 索引唯一扫描(Index Unique Scans)案例讲解

    此示例使用唯一扫描从products表中检索一行,查询products表中prod_id字段为19的记录:

SELECT * 
FROM   sh.products 
WHERE  prod_id = 19;

    因为products表中的prod_id列上存在主键索引,并且WHERE子句使用相等运算符引用所有列,所以优化器选择唯一扫描:

SQL_ID  3ptq5tsd5vb3d, child number 0
-------------------------------------
select * from sh.products where prod_id = 19Plan hash value: 4047888317---------------------------------------------------------------------------
| Id| Operation                   | Name   |Rows|Bytes|Cost (%CPU)|Time   |
---------------------------------------------------------------------------
|  0| SELECT STATEMENT            |             |  |     |1 (100)|        |
|  1|  TABLE ACCESS BY INDEX ROWID| PRODUCTS    |1 | 173 |1   (0)|00:00:01|
|* 2|   INDEX UNIQUE SCAN         | PRODUCTS_PK |1 |     |0   (0)|        |
---------------------------------------------------------------------------Predicate Information (identified by operation id):
---------------------------------------------------2 - access("PROD_ID"=19)

               

2、执行计划访问路径之索引范围扫描(Index Range Scans)

    索引范围扫描是对值的有序扫描。扫描的范围可以在两侧有界,或者在一侧或两侧无界。优化器通常为具有高选择性的查询选择范围扫描。

    默认情况下,数据库以升序存储索引,并以相同的顺序扫描它们。例如,wehere统计中department_id >= 20的查询使用范围扫描返回按索引键20、30、40等排序的行。如果多个索引条目具有相同的键,则数据库按rowid的升序返回它们,因此0,AAAPvCAAFAAAAFaAAa后面跟0,AAAPvCAAFAAAAFaAAg,依此类推。

    降序索引范围扫描与降序索引范围扫描相同,只是数据库按降序返回行。通常,当按降序排列数据时,或者当查找小于指定值的值时,数据库使用降序扫描。

    对于索引范围扫描,索引键必须有多个值。具体来说,优化器会在以下情况下考虑索引范围扫描:

  • 在条件中指定了索引的一个或多个前导列。条件指定一个或多个表达式和逻辑(布尔)运算符的组合,并返回值TRUE、FALSE或UNKNOWN。条件的示例包括:             
department_id = :id
department_id < :id
department_id > :id
以及索引中前导列的前述条件的组合,例如:department_id > :low AND department_id < :hi
  • 一个索引键可能有0、1或多个值。

    当索引可以满足ORDER BY DESCENDING子句时,优化器会将索引范围扫描视为降序。如果优化器选择全表扫描或另一个索引,那么可能需要一个提示来强制使用这个访问路径。索引(tbl_alias ix_name)和索引_DESC(tbl_alias ix_name)提示指示优化器使用特定的索引。

         

2.1 索引范围扫描(Index Range Scans)的工作原理

    在索引范围扫描期间,Oracle数据库从根到分支进行扫描。通常,扫描算法如下:

  1. 读取根块。
  2. 读取分支块。
  3. 交替执行以下步骤,直到检索到所有数据: 
a. 读取叶块以获得rowid
b. 读取表块以检索行

    在某些情况下,索引扫描会读取一组索引块,对rowids进行排序,然后读取一组表块。因此,为了扫描索引,数据库在叶块中向前或向后移动。例如,对介于20和40之间的id的扫描定位具有20或更大的最低键值的第一个叶块。扫描在叶节点的链表中水平进行,直到找到一个大于40的值,然后停止。

    下图说明了使用升序进行索引范围扫描。语句请求department_id列中值为20的employees员工表记录,该列具有非唯一索引。在这个例子中,部门20有2个索引条目。

    该图形分为两个带虚线边框的框,一个在另一个的上面。顶部标记为“分支块(Branch Blocks)”,下部标记为“叶块(Leaf Blocks)”该图形包含一个方框树。顶部是一个包含以下值的框:0-40、41-90等等。三个箭头指向第二排分支块。这一行中的一个块具有值:1-10、11-19、20-25等等。相邻块的值为41-48、49-53等等。最后一个黑色的值为:200-209、210-220等等。

    最左边和最右边的块各有一对向下的箭头。最左边的箭头指向包含以下值的叶块:0,rowid、0,rowid、10,rowid等等。右边的块包含以下值:11,rowid、11,rowid等等。下一个块包含值:221,rowid、222,rowid等等。最右边的块包含以下值:246,rowid、248,rowid等等。除了最左边和最右边的块之外,底行中的每个叶块都通过双向箭头链接到两侧的块。

           

2.2 索引范围扫描(Index Range Scans)案例讲解

    此示例使用索引范围扫描从employees表中检索一组值。以下语句查询部门20中,薪金高于1000的雇员的记录:

SELECT * 
FROM   employees 
WHERE  department_id = 20
AND    salary > 1000;

    前面的查询基数较低(返回的行很少),因此该查询使用department_id列上的索引。数据库扫描索引,从employees表中获取记录,然后对这些获取的记录应用salary > 1000筛选器以生成结果。

SQL_ID  brt5abvbxw9tq, child number 0
-------------------------------------
SELECT * FROM   employees WHERE  department_id = 20 AND    salary > 1000Plan hash value: 2799965532-------------------------------------------------------------------------------------------
|Id | Operation                           | Name             |Rows|Bytes|Cost(%CPU)| Time |
-------------------------------------------------------------------------------------------
| 0 | SELECT STATEMENT                    |                  |    |     | 2 (100)|        |
|*1 |  TABLE ACCESS BY INDEX ROWID BATCHED| EMPLOYEES        |  2 | 138 | 2   (0)|00:00:01|
|*2 |   INDEX RANGE SCAN                  | EMP_DEPARTMENT_IX|  2 |     | 1   (0)|00:00:01|
-------------------------------------------------------------------------------------------Predicate Information (identified by operation id):
---------------------------------------------------1 - filter("SALARY">1000)2 - access("DEPARTMENT_ID"=20)

         

2.3 索引范围扫描(Index Range Scans)降序案例讲解

    此示例使用索引按排序顺序从employees表中检索行。以下语句按降序查询部门20中的雇员记录:

SELECT *
FROM   employees
WHERE  department_id < 20
ORDER BY department_id DESC;

    前面的查询基数较低,因此该查询使用department_id列上的索引。

SQL_ID  8182ndfj1ttj6, child number 0
-------------------------------------
SELECT * FROM employees WHERE department_id<20 ORDER BY department_id DESCPlan hash value: 1681890450
---------------------------------------------------------------------------
|Id| Operation                    | Name      |Rows|Bytes|Cost(%CPU)|Time |
---------------------------------------------------------------------------
| 0| SELECT STATEMENT             |                 | |   |2(100)|        |
| 1|  TABLE ACCESS BY INDEX ROWID |EMPLOYEES        |2|138|2  (0)|00:00:01|
|*2|   INDEX RANGE SCAN DESCENDING|EMP_DEPARTMENT_IX|2|   |1  (0)|00:00:01|
---------------------------------------------------------------------------Predicate Information (identified by operation id):
---------------------------------------------------2 - access("DEPARTMENT_ID"<20)

    数据库定位包含最高键值(等于或小于20)的第一个索引叶块。然后,扫描通过叶节点的链表向左水平进行。数据库从每个索引条目中获取rowid,然后检索由rowid指定的行。

                 

3、执行计划访问路径之索引全扫描(Index Full Scans)

    索引全扫描按顺序读取整个索引。索引全扫描可以消除单独的排序操作,因为索引中的数据是按索引键排序的。优化器在以下情况下都会考虑进行索引全扫描:

  • where条件中引用索引中的列,该列不必是前导列。
  • 未指定where条件,但满足以下所有条件:
a. 表和查询中的所有列都在索引中。
b. 至少有一个索引列不为空。
  • 查询包括对索引的不可为空的列的ORDER BY。

            

3.1 索引全扫描(Index Full Scans)的工作原理

    数据库读取根块,然后向下导航到索引的左侧(如果进行降序全扫描,则向下导航到右侧),直到到达叶块。

    然后数据库到达一个叶块,扫描在索引的底部进行,一次一个块,按排序顺序进行。数据库使用单块I/O,而不是多块I/O。

    下图说明了索引全扫描,一条语句请求按department_id排序的部门表departments记录。

    该图形分为两个带虚线边框的框,一个在另一个的上面。顶部标记为“分支块(Branch Blocks)”,下部标记为“叶块(Leaf Blocks)”该图形包含一个方框树。顶部是一个包含以下值的框:0-40、41-80等等。三个箭头指向第二排分支块。这一行中的一个块具有值:1-10、11-19、20-25等等。相邻块的值为:41-48、49-53等等。最后一个黑色的值为200-209、210-220等等。

    最左边和最右边的块各有一对向下的箭头。最左边的箭头指向包含以下值的叶块:0,rowid、1,rowid、10,rowid等等。右边的块包含以下值:11,rowid、12,rowid等等。下一个块包含值:221,rowid、222,rowid等等。最右边的块包含以下值:246,rowid、247,rowid等等。除了最左边和最右边的块之外,底行中的每个叶块都通过双向箭头链接到两侧的块。

            

3.2 索引全扫描(Index Full Scans)案例讲解

    此示例使用索引全扫描来满足带有ORDER BY子句的查询。以下语句按部门ID的顺序,查询部门的ID和名称:

SELECT department_id, department_name
FROM   departments
ORDER BY department_id;

   以下执行计划显示优化器选择了索引全扫描:

SQL_ID  94t4a20h8what, child number 0
-------------------------------------
select department_id, department_name from departments order by department_idPlan hash value: 4179022242------------------------------------------------------------------------
|Id | Operation                 | Name     |Rows|Bytes|Cost(%CPU)|Time |
------------------------------------------------------------------------
|0| SELECT STATEMENT            |            |   |   |2 (100)|         |
|1|  TABLE ACCESS BY INDEX ROWID|DEPARTMENTS |27 |432|2   (0)|00:00:01 |
|2|   INDEX FULL SCAN           |DEPT_ID_PK  |27 |   |1   (0)|00:00:01 |
------------------------------------------------------------------------

    数据库定位第一个索引叶块,然后水平向右遍历叶节点的链表。对于每个索引条目,数据库从条目中获取rowid,然后检索由rowid指定的表行。因为索引是按department_id排序的,所以数据库避免了对检索到的行进行排序的单独操作。

                    

4、执行计划访问路径之索引快速全扫描(Index Fast Full Scans)

    索引快速全扫描以未排序的顺序读取索引块,因为它们存在于磁盘上。这种扫描不使用索引来探测表,而是读取索引而不是表,本质上是将索引本身用作表。

    当查询只访问索引中的属性时,优化器会考虑索引快速全扫描。 小提示💥:与索引全扫描(Index Full Scans)不同,索引快速全扫描(Index Fast Full Scans)无法消除排序操作,因为它不会按顺序读取索引。

                      

4.1 索引快速全扫描(Index Fast Full Scans)的工作原理

    数据库使用多块I/O来读取根块以及所有叶块和分支块。数据库忽略分支和根块,并读取叶块上的索引条目。

             

4.2 索引快速全扫描(Index Fast Full Scans)案例讲解

    下面例子中使用了Hint干预让执行计划走索引快速全扫描。索引快速全扫描的Hint干预语法为:/*+ INDEX_FFS(table_name index_name) */

SELECT /*+ INDEX_FFS(departments dept_id_pk) */ COUNT(*)
FROM   departments;

以下执行计划中显示优化器选择了索引快速全扫描:

SQL_ID  fu0k5nvx7sftm, child number 0
-------------------------------------
select /*+ index_ffs(departments dept_id_pk) */ count(*) from departmentsPlan hash value: 3940160378
--------------------------------------------------------------------------
| Id | Operation             | Name       | Rows  |Cost (%CPU)| Time     |
--------------------------------------------------------------------------
|  0 | SELECT STATEMENT      |            |       |    2 (100)|          |
|  1 |  SORT AGGREGATE       |            |     1 |           |          |
|  2 |   INDEX FAST FULL SCAN| DEPT_ID_PK |    27 |    2   (0)| 00:00:01 |
--------------------------------------------------------------------------

                   

5、执行计划访问路径之索引跳跃扫描(Index Skip Scans)

    当组合索引的前置列被“跳过(skipped)”或未在查询中指定时,会发生索引跳跃扫描。通常来说,跳过扫描索引块(skip scanning index blocks)比扫描表块(scanning table blocks)要快很多,也比执行完全索引扫描(full index scans)快。

    当满足以下条件时,优化器会考虑进行索引跳跃扫描:

  • where条件中未指定组合索引的前置列:例如,表中创建了一个组合索引,复合索引键是(cust_gender,cust_email),where条件中不引用cust_gender列
  • 在组合索引的前置列中存在很少的非重复值,但是在索引的非前置列中存在许多非重复值:例如,如果组合索引键是(cust_gender,cust_email),那么cust_gender列只有两个不同的值,但是cust_email有数千个值

              

5.1 索引跳跃扫描(Index Skip Scans)的工作原理

    索引跳过扫描在逻辑上将复合索引拆分成更小的子索引。

    索引前导列中不同值的数量决定了逻辑子索引的数量。该数字越小,优化器必须创建的逻辑子索引就越少,扫描效率就越高。扫描分别读取每个逻辑索引,并“跳过(skips)”非前导列上不满足筛选条件的索引块。

                    

5.2 索引跳跃扫描(Index Skip Scans)案例讲解

    下面示例中使用索引跳跃扫描来满足customers表的查询,customers表中的cust_gender字段只包含有M或者F的数据,并且对列(cust_gender,cust_email)创建了一个复合索引:

###cust_gender列和cust_email列上的部分数据:
F,Wolf@company.example.com,rowid
F,Wolsey@company.example.com,rowid
F,Wood@company.example.com,rowid
F,Woodman@company.example.com,rowid
F,Yang@company.example.com,rowid
F,Zimmerman@company.example.com,rowid
M,Abbassi@company.example.com,rowid
M,Abbey@company.example.com,rowid###创建索引:
CREATE INDEX cust_gender_email_ixON sh.customers (cust_gender, cust_email);

    将cust_email字段当做where条件进行查询:

SELECT * 
FROM   sh.customers 
WHERE  cust_email = 'Abbey@company.example.com';

    即使where子句中没有指定cust_ender,数据库也可以使用CUST_GENDER_EMAIL_IX 索引的跳跃扫描。在示例索引中,前置列cust_ender有两个可能的值:F和M。数据库在逻辑上将索引一分为二。一个子索引的键为F,条目格式如下:

F,Wolf@company.example.com,rowid
F,Wolsey@company.example.com,rowid
F,Wood@company.example.com,rowid
F,Woodman@company.example.com,rowid
F,Yang@company.example.com,rowid
F,Zimmerman@company.example.com,rowid

    第二个子索引的键为M,条目的格式如下:

M,Abbassi@company.example.com,rowid
M,Abbey@company.example.com,rowid

    通过cust_email 字段搜索数据为Abbey@company.example.com时,数据库首先搜索以F开头的子索引,然后搜索以M开头的子指数。查询转换器对查询做如下处理,查询转换器觉得这种写法成本更低(查询转换器可以参考之前的文章哦,直通车👉【Oracle篇】全面理解优化器和SQL语句的解析步骤(含执行计划的详细分析和四种查看方式)(第二篇,总共七篇)-CSDN博客👈)

( SELECT * FROM   sh.customers WHERE  cust_gender = 'F' AND    cust_email = 'Abbey@company.example.com' )
UNION ALL
( SELECT * FROM   sh.customers WHERE  cust_gender = 'M'AND    cust_email = 'Abbey@company.example.com' )

    执行计划如下:

SQL_ID  d7a6xurcnx2dj, child number 0
-------------------------------------
SELECT * FROM   sh.customers WHERE  cust_email = 'Abbey@company.example.com'Plan hash value: 797907791-----------------------------------------------------------------------------------------
|Id| Operation                          | Name               |Rows|Bytes|Cost(%CPU)|Time|
-----------------------------------------------------------------------------------------
| 0|SELECT STATEMENT                    |                      |  |    |10(100)|        |
| 1| TABLE ACCESS BY INDEX ROWID BATCHED| CUSTOMERS            |33|6237|  10(0)|00:00:01|
|*2|  INDEX SKIP SCAN                   | CUST_GENDER_EMAIL_IX |33|    |   4(0)|00:00:01|
-----------------------------------------------------------------------------------------Predicate Information (identified by operation id):
---------------------------------------------------2 - access("CUST_EMAIL"='Abbey@company.example.com')filter("CUST_EMAIL"='Abbey@company.example.com')

                       

6、执行计划访问路径之索引连接扫描(Index Join Scans)

    索引连接扫描是多个索引的散列连接,这些索引一起返回查询请求的所有列。数据库不需要访问表,因为所有数据都是从索引中检索的。

    在某些情况下,避免表访问是最具成本效益的选择。在下列情况下,优化器会考虑进行索引连接扫描:

  • 多个索引的散列连接检索查询请求的所有数据,而不需要访问表。
  • 从表中检索行的成本高于读取索引而不从表中检索行的成本。索引连接通常开销很大。例如,当扫描两个索引并连接它们时,选择最具选择性的索引,然后探测表,通常成本更低。

             

6.1 索引连接扫描(Index Join Scans)的工作原理

    索引连接包括扫描多个索引,然后对从这些扫描中获得的rowids使用散列连接来返回行。在索引连接扫描中,总是避免表访问。例如,联接单个表上的两个索引的过程如下:

  1. 扫描第一个索引以检索行Rowid。
  2. 扫描第二个索引以检索行Rowid。
  3. 通过Rowid执行哈希连接以获取行。

              

6.2  索引连接扫描(Index Join Scans)案例讲解

    下面例子中使用了Hint干预让执行计划走索引连接扫描,查询last_name字段中以A开头的雇员的姓氏(last_name)和电子邮件(email)。索引连接扫描的Hint干预语法为:/*+ INDEX_JOIN(table_name) */

SELECT /*+ INDEX_JOIN(employees) */ last_name, email
FROM   employees
WHERE  last_name like 'A%';

    (last_name,first_name)和email列上存在单独的索引。emp_name_ix索引的一部分可能如下:

Banda,Amit,AAAVgdAALAAAABSABD
Bates,Elizabeth,AAAVgdAALAAAABSABI
Bell,Sarah,AAAVgdAALAAAABSABc
Bernstein,David,AAAVgdAALAAAABSAAz
Bissot,Laura,AAAVgdAALAAAABSAAd
Bloom,Harrison,AAAVgdAALAAAABSABF
Bull,Alexis,AAAVgdAALAAAABSABV

    emp_email_uk索引的第一部分可能如下:

ABANDA,AAAVgdAALAAAABSABD
ABULL,AAAVgdAALAAAABSABV
ACABRIO,AAAVgdAALAAAABSABX
AERRAZUR,AAAVgdAALAAAABSAAv
AFRIPP,AAAVgdAALAAAABSAAV
AHUNOLD,AAAVgdAALAAAABSAAD
AHUTTON,AAAVgdAALAAAABSABL

    执行计划中检索emp_email_uk索引中的所有Rowid,然后检索emp_name_ix中以a开头的姓氏的rowid,并且使用哈希联接(HASH JOIN)来搜索这两组rowid以查找匹配项。例如,rowid  AAAVgdAALAAAABSABD出现在两个rowid集合中,因此数据库在employees表中查找与该Rowid对应的记录。

SQL_ID  d2djchyc9hmrz, child number 0
-------------------------------------
SELECT /*+ INDEX_JOIN(employees) */ last_name, email FROM   employees
WHERE  last_name like 'A%'Plan hash value: 3719800892
-------------------------------------------------------------------------------------------
| Id  | Operation              | Name             | Rows  | Bytes | Cost (%CPU)| Time     |
-------------------------------------------------------------------------------------------
|   0 | SELECT STATEMENT       |                  |       |       |     3 (100)|          |
|*  1 |  VIEW                  | index$_join$_001 |     3 |    48 |     3  (34)| 00:00:01 |
|*  2 |   HASH JOIN            |                  |       |       |            |          |
|*  3 |    INDEX RANGE SCAN    | EMP_NAME_IX      |     3 |    48 |     1   (0)| 00:00:01 |
|   4 |    INDEX FAST FULL SCAN| EMP_EMAIL_UK     |     3 |    48 |     1   (0)| 00:00:01 |
-------------------------------------------------------------------------------------------Predicate Information (identified by operation id):
---------------------------------------------------1 - filter("LAST_NAME" LIKE 'A%')2 - access(ROWID=ROWID)3 - access("LAST_NAME" LIKE 'A%')

🚽🏃‍♂️各位看官大人,到这里文章已经突破2万字大关,憋尿的小伙伴可以去放个水,或者转转脖子休息一下啦...................

                                  

                              

三、位图索引的访问路径

    在介绍位图索引的访问路径之前首先让我们了解什么是位图索引,位图索引和B树索引的区别是什么。

位图索引(bitmap)

    我们可以看出,一个叶子节点(用不同颜色标识)代表一个 key , start rowid 和 end rowid规定这种类型的检索范围,一个叶子节点标记一个唯一的 bitmap 值。因为一个数值类型对应一个节点,当是行查询时,位图索引通过不同位图取值直接的位运算(与或),来获取到结果集合向量(计算出的结果)。

如果需要创建索引的列会经常更新,那么是不适合建立位图索引的,位图适用于条目多但取值类别少的列,例如性别、年龄。

    位图索引主要针对大量相同值的列而创建。例如拿全国居民登录表来说,假设有四个字段:姓名、性别、年龄、和身份证号。年龄和性别两个字段会产生许多相同的值,性别只有男女两种值,而年龄是1到120(假设最大年龄120岁)个值。那么不管一张表有几亿条记录,但根据性别字段来区分的话,只有两种取值(男、女)。那么位图索引就是根据字段的这个特性所建立的一种索引。

    总体来说,位图索引适合于数据仓库中,不适合OLTP中。

       

位图索引和B-tree索引适合在那种字段类型上创建:

位图索引:性别、年龄

tree索引:姓名、身份证

           

Bitmap优缺点:

优点:

1)用一个位表示一个索引的键值,节省了存储空间

2)对and,or或者=的查询条件,位图索引查询效率很高,计算机善于处理0,1数据

3)非常适合OR操作符的查询基数比较少的时候才能建位图索引。

      

缺点:

做UPDATE代价非常高

           

应用场景:

1)表的更新操作极少,重复度很高的列

2)位图索引在平时的OLTP系统中比较少见,但是在OLAP系统中就会经常见到,号称数据仓库调优的三个利器之一

           

语法:

create bitmap index index_name  on table_name (column_name) [online] tablespace  tablespace _name  nologging  parallel x;     

             

总结:

    博主对位图索引了解的就这么多,因为位图索引适合于数据仓库中,不适合OLTP中,在Oracle这种关系数据库中使用的非常少(博客目前还没有见过在Oracle项目上有用位图索引的),对于位图索引的访问路径博主就不介绍啦,敬上官网的链接和截图,如果有兴趣的小伙伴可以参考官方文档(官网链接:Optimizer Access Paths)学习哦,可以看到位图索引的访问路径有四个。

      

                  

四、簇表的访问路径

    可能有些小伙伴第一次听到簇表,簇表是oracle中表类型(在oracle中默认表的类型为堆表)。既然要学习簇表的访问路径,那么先来学习一下簇表的结构,然后再开始访问路径的学习。

                  

簇表介绍:

    簇表是一组共享公共列并在相同的块中存储相关数据的表。当表被聚集时,单个数据块可以包含多个表中的行。例如,一个块可以存储employees表和departments表中的行,而不是只存储一个表中的行。

    聚集键(cluster key)是聚集表共有的一列或多列。例如,employees和departments表共享department_id列。在创建簇表和创建添加到簇表中的每个表时,需要指定簇键(也称聚集键,cluster key)。

    簇键值是一组特定行的簇键列的值。包含相同分类键值(如department_id=20)的所有数据在物理上存储在一起。每个簇键值在簇和簇索引中只存储一次,不管不同表中有多少行包含该值。

    打个比方,假设一位人力资源经理有两个书架:一个装有员工文件夹,另一个装有部门文件夹。用户经常要求为特定部门的所有员工提供文件夹。为了便于检索,经理将所有箱子重新排列在一个书架上。她按部门ID划分这些框,因此,部门20的所有员工文件夹和部门20本身的文件夹都在一个框中;部门100中的员工文件夹和部门100的文件夹在另一个框中,依此类推。

    当表主要被查询(但未被修改)并且表中的记录经常被一起查询或连接时,可以考虑对表进行聚类,也就是使用簇表这个表类型。由于表簇将不同表的相关行存储在同一数据块中,因此与非聚集表相比,正确使用簇表具有以下优点:

  • 簇表的联接减少了磁盘I/O。
  • 簇表连接的访问时间缩短。
  • 存储相关表和索引数据所需的存储空间更少,因为不会为每一行重复存储簇键值。

    如果是一下情况那么就不适合使用簇表:

  • 表经常需要更新(update)操作。
  • 表经常需要全表扫描,例如,表经常需要count(*)全表数据,或者需要全表查询数据。
  • 表偶尔需要truncate清空全部数据的操作。

         

簇表之簇索引:簇索引是一种特殊的索引,说直白点就是为簇表创建簇索引,使用簇索引来定位簇表的数据,加快数据访问

    簇索引是使用索引来定位簇表的数据。簇索引是簇键上的B树索引。必须先创建簇索引,然后才能将任何行插入到簇表中。

          

创建簇表和关联的簇索引:

    假设使用聚集键(cluster key)department_id创建了簇表employees_departments_cluster,如下例所示:

CREATE CLUSTER employees_departments_cluster(department_id NUMBER(4))
SIZE 512;CREATE INDEX idx_emp_dept_cluster ON CLUSTER employees_departments_cluster;

    因为没有指定HASHKEYS子句,所以employees_departments_cluster是一个索引簇集。前面的示例在聚集键(cluster key)department_id上创建了一个名为idx_emp_dept_cluster的索引。

       

索引簇中创建表:

    创建employees和departments簇表,将department_id列指定为聚集键(cluster key),如下所示(省略号表示列说明的位置):

CREATE TABLE employees ( ... )CLUSTER employees_departments_cluster (department_id);CREATE TABLE departments ( ... )CLUSTER employees_departments_cluster (department_id);

    假设向employees和departments表中添加了行。数据库将employees和departments表中每个部门的所有行物理存储在同一个数据块中。数据库将行存储在一个堆中,并使用索引来定位它们。

    下图显示了employees_departments_cluster表簇,其中包含雇员和部门。数据库将部门20和部门110的雇员的行存储在一起,依此类推。如果表没有聚集,则数据库不能确保相关的行存储在一起。

    图的左边是一个标有“簇表(Clustered Tables)”的数据库圆柱图标。圆柱体的顶部是一个正方形。正方形指向一个看起来像一张纸的图标。该图标的标签为“聚集关键部门标识(Clustered Key department_id)”图标显示了部门20的一组行和部门110的另一组行。

    在图的右边是一个标有“非集群表(Unclustered Tables)”的数据库圆柱图标。圆柱体的顶部是两个正方形。左边的方块指向一个看起来像一张纸的图标。该图标标记为“雇员(employees)”,包含雇员行。右边的方块标记为“部门(departments)”,显示了部门20的一行和部门110的另一行。

    B树簇索引将簇键值与包含数据的块的数据库块地址(DBA)相关联。例如,键20的索引条目显示了包含部门20中员工数据的块的地址:

20,AADAAAA9d

    簇索引是单独管理的,就像非聚集表上的索引一样,并且可以存在于与表聚集不同的表空间中。

                      

簇表之hash簇表:hash簇类似于索引簇,只是索引键被散列函数所取代。不存在单独的聚集索引。在hash簇中,数据就是索引。

    hash簇类似于索引簇,只是索引键被散列函数所取代。不存在单独的聚集索引。在hash簇中,数据就是索引。

   对于索引表或索引簇,Oracle数据库使用存储在单独索引中的键值来定位表行。要在索引表或表簇中查找或存储一行,数据库必须至少执行两次I/o:

  • 在索引中查找或存储键值的一个或多个I/o
  • 另一个I/O读取或写入表或表簇中的行

    为了在hash簇中查找或存储一行,Oracle数据库将散列函数应用于该行的簇键值。产生的哈希值对应于集群中的一个数据块,数据库代表发出的语句读取或写入该数据块。

   哈希是存储表数据的一种可选方式,可以提高数据检索的性能。当满足以下条件时,hash簇可能是有益的:

  • 表被查询的次数比被修改的次数多得多
  • 哈希键列经常使用相等条件进行查询,例如,其中department_id=20。对于这种查询,会对群集键值进行哈希处理。散列键值直接指向存储行的磁盘区域
  • 可以合理地猜测散列键的数量以及每个键值存储的数据大小

          

创建hash簇表:

    要创建hash簇表,可以使用与索引簇相同的CREATE CLUSTER语句,并添加一个散列键。群集的哈希值的数量取决于哈希键。

    与索引簇的键一样,簇键是由簇中的表共享的单列或组合键。散列键值是插入到簇键列中的实际或可能的值。例如,如果集群键是department_id,那么散列键值可以是10、20、30等等。

    Oracle数据库使用散列函数,该函数接受无限数量的散列键值作为输入,并将它们分类到有限数量的桶中。每个存储桶都有一个唯一的数字ID,称为哈希值。每个哈希值映射到存储与哈希值对应的行的块的数据库块地址(部门10、20、30等)。

    在下面的示例中,可能存在的部门数量是100,因此HASHKEYS被设置为100:

CREATE CLUSTER employees_departments_cluster(department_id NUMBER(4))
SIZE 8192 HASHKEYS 100;

    创建employees_departments_cluster后,可以在集群中创建employees和departments表。然后,您可以像在索引簇中一样将数据加载到散列簇中。

                              

1、执行计划访问路径之簇扫描(Cluster Scans)

   簇索引是使用索引来定位簇表的数据。簇索引是簇键上的B树索引,簇扫描(Cluster Scans)从存储在索引集群中的表中检索具有相同集群键值的所有行。

    当查询访问簇索引中的表时,数据库会考虑簇扫描。

                  

1.1 簇扫描(Cluster Scans)的工作原理

    在索引簇中,数据库将具有相同簇键值的所有行存储在同一数据块中。

    例如,如果employees2和departments2表在emp_dept_cluster簇索引中,并且聚集键是department_id,则数据库将部门10中的所有雇员存储在同一块中,部门20中的所有雇员存储在同一块中,依此类推。

    B树簇索引将簇键值与包含数据的块的数据库块地址(DBA)相关联。例如,键30的索引条目显示了包含部门30中雇员行的块的地址:

30,AADAAAA9d

    当用户请求集群中的行时,数据库扫描索引以获取包含这些行的块的DBA。然后,Oracle数据库根据这些DBA定位行。

              

1.2 簇扫描(Cluster Scans)案例讲解

    此示例在department_id列上对员工表(employees)和部门表(departments)进行聚类,然后在聚类中查询单个部门。

    创建一个簇表、簇索引和簇中的表,如下所示:

CREATE CLUSTER employees_departments_cluster(department_id NUMBER(4)) SIZE 512;CREATE INDEX idx_emp_dept_clusterON CLUSTER employees_departments_cluster;CREATE TABLE employees2CLUSTER employees_departments_cluster (department_id)AS SELECT * FROM employees;CREATE TABLE departments2CLUSTER employees_departments_cluster (department_id)AS SELECT * FROM departments;

    查询部门30中的员工:
 

SELECT * 
FROM   employees2 
WHERE  department_id = 30;

    为了执行扫描,Oracle数据库首先通过扫描簇索引获得描述部门30的行的rowid(步骤2)。然后,Oracle数据库使用这个rowid定位employees2中的行(步骤1)。

SQL_ID  b7xk1jzuwdc6t, child number 0
-------------------------------------
SELECT * FROM employees2 WHERE department_id = 30Plan hash value: 49826199---------------------------------------------------------------------------
|Id| Operation            | Name               |Rows|Bytes|Cost(%CPU)|Time|
---------------------------------------------------------------------------
| 0| SELECT STATEMENT     |                    |   |    | 2 (100)|        |
| 1|  TABLE ACCESS CLUSTER| EMPLOYEES2         | 6 |798 | 2   (0)|00:00:01|
|*2|   INDEX UNIQUE SCAN  |IDX_EMP_DEPT_CLUSTER| 1 |    | 1   (0)|00:00:01|
---------------------------------------------------------------------------Predicate Information (identified by operation id):
---------------------------------------------------2 - access("DEPARTMENT_ID"=30)

                 

2、执行计划访问路径之哈希扫描(Hash Scans)

    散列簇类似于索引簇,只是索引键被散列函数所取代。不存在单独的聚集索引。在散列簇中,数据就是索引。数据库使用哈希扫描来根据哈希值定位哈希簇中的行。

    当查询访问哈希簇中的表时,数据库会考虑哈希扫描。

           

2.1 哈希扫描(Hash Scans)的工作原理

    在散列簇中,具有相同散列值的所有行都存储在同一个数据块中。

    为了对集群执行散列扫描,Oracle数据库首先通过将散列函数应用于由语句指定的集群键值来获得散列值。然后,Oracle数据库扫描包含具有该哈希值的行的数据块。

       

2.2 哈希扫描(Hash Scans)案例讲解

    此示例对department_id列上的employees和departments表进行哈希运算,然后在群集中查询单个部门。

    创建一个散列簇和簇中的表,如下所示:

CREATE CLUSTER employees_departments_cluster(department_id NUMBER(4)) SIZE 8192 HASHKEYS 100;CREATE TABLE employees2CLUSTER employees_departments_cluster (department_id) AS SELECT * FROM employees;CREATE TABLE departments2 CLUSTER employees_departments_cluster (department_id) AS SELECT * FROM departments;

查询部门30中的员工:

SELECT *
FROM   employees2
WHERE  department_id = 30

    为了执行散列扫描,Oracle数据库首先通过对键值30应用散列函数来获得散列值,然后使用该散列值来扫描数据块并检索行(步骤1)。

SQL_ID  919x7hyyxr6p4, child number 0
-------------------------------------
SELECT * FROM employees2 WHERE department_id = 30Plan hash value: 2399378016----------------------------------------------------------------
| Id  | Operation         | Name       | Rows  | Bytes | Cost  |
----------------------------------------------------------------
|   0 | SELECT STATEMENT  |            |       |       |     1 |
|*  1 |  TABLE ACCESS HASH| EMPLOYEES2 |    10 |  1330 |       |
----------------------------------------------------------------Predicate Information (identified by operation id):
---------------------------------------------------1 - access("DEPARTMENT_ID"=30)

    我的发,这篇文章到这里就算结束了,前前后后整理了7天,撰写超过10小时,因为有些访问路径博主之前也不了解,是边看官方文档边学习,然后再整理到博客的,所以看在博主这么用心,这么“拼”的份上,不给个“三连”支持一下,良心真的不会痛吗😁?,并且文章3.6万字了,需要各位小伙伴慢慢学习,细细品,保证你越读越有味,比追剧还上瘾!

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

如若内容造成侵权/违法违规/事实不符,请联系一条长河网进行投诉反馈,一经查实,立即删除!

相关文章

【自动驾驶】决策规划算法(二)参考线模块Ⅰ| 平滑算法与二次规划

写在前面&#xff1a; &#x1f31f; 欢迎光临 清流君 的博客小天地&#xff0c;这里是我分享技术与心得的温馨角落。&#x1f4dd; 个人主页&#xff1a;清流君_CSDN博客&#xff0c;期待与您一同探索 移动机器人 领域的无限可能。 &#x1f50d; 本文系 清流君 原创之作&…

dhtmlxGantt 甘特图 一行展示多条任务类型

效果如图: 后台拿到数据 处理之后如图: 含义: 如上图所示, 如果一行需要展示多个 需要给父数据的那条添加render:split属性, 子数据的parent为父数据的Id即可 切记 父数据的id 别为0 为0 时 会出现错乱 因为有些小伙伴提出分段展示的数据结构还是有点问题,下面展示一个完整…

如何在 Apache 中仅开启 TLS 1.3 / TLS1.2 ?

互联网之所以运行良好&#xff0c;是因为它可以安全地发送数据&#xff0c;这要归功于传输层安全(TLS)等技术。TLS 是安全套接字层(SSL)的新版本&#xff0c;它有助于保持网络流量的安全。本文将讨论 TLS 1.3 和 1.2&#xff0c;它们比旧版本更好、更快。 使用这些协议的一个流…

Java继承教程!(o|o)

Java 继承 Java面向对象设计 - Java继承 子类可以从超类继承。超类也称为基类或父类。子类也称为派生类或子类。 从另一个类继承一个类非常简单。我们在子类的类声明中使用关键字extends&#xff0c;后跟超类名称。 Java不支持多重继承的实现。 Java中的类不能有多个超类。…

SwiftUI 实现关键帧动画

实现一个扫描二维码的动画效果&#xff0c;然而SwiftUI中没有提供CABasicAnimation 动画方法&#xff0c;该如何实现这种效果&#xff1f;先弄清楚什么关键帧动画&#xff0c;简单的说就是指视图从起点至终点的状态变化&#xff0c;可以是形状、位置、透明度等等 本文提供了一…

pytorch学习笔记二:用pytorch神经网络模型做气温预测、分类任务构建

文章目录 一、搭建pytorch神经网络进行气温预测1&#xff09;基础搭建2&#xff09;实际操作标识特征和标签3&#xff09;构建成标准化的预处理数据&#xff08;做标准化收敛速度更快&#xff09; 二、按照建模顺序构建完成网络架构1&#xff09;np.array格式的标签(y)和特征(x…

Spring Boot管理用户数据

目录 学习目标前言Thymeleaf 模板JSON 数据步骤 1: 创建 Spring Boot 项目使用 Spring Initializr 创建项目使用 IDE 创建项目 步骤 2: 添加依赖步骤 3: 创建 Controller步骤 4: 新建index页面步骤 5: 运行应用程序 表单提交步骤 1: 添加 Thymeleaf 依赖在 Maven 中添加依赖 步…

Github 2024-09-22 php开源项目日报 Top10

根据Github Trendings的统计,今日(2024-09-22统计)共有10个项目上榜。根据开发语言中项目的数量,汇总情况如下: 开发语言项目数量PHP项目10Blade项目1Coolify: 开源自助云平台 创建周期:1112 天开发语言:PHP, Blade协议类型:Apache License 2.0Star数量:10527 个Fork数量…

串的存储实现方法(与链表相关)

一、 定义 字符串是由零个&#xff08;空串&#xff09;或多个字符组成的有限序列。 eg:S"Hello World!" 串相等&#xff1a;两个串长度相等并且对应位置的字符都相等时&#xff0c;两个串才相等。 二、串的存储实现 2.1 定长顺序串 2.2 堆串 和定长顺序串的…

nodejs 014: React.FC 与 Evergreen(常青树) React UI 框架的的Dialog组件

React.FC React.FC是React中用于定义函数组件“Function Component”的类型。它代表&#xff0c;可以帮助你在TypeScript中提供类型检查和自动补全。使用React.FC时&#xff0c;可以明确指定组件的props类型&#xff0c;并且它会自动推导children属性。下面是一个使用 React.F…

二、MySQL环境搭建

文章目录 1. MySQL的卸载步骤1&#xff1a;停止MySQL服务步骤2&#xff1a;软件的卸载步骤3&#xff1a;残余文件的清理步骤4&#xff1a;清理注册表&#xff08;选做&#xff09;步骤5&#xff1a;删除环境变量配置 2. MySQL的下载、安装、配置2.1 MySQL的4大版本2.2 软件的下…

Ubuntu以及ROS的一些方便设置及使用

目录 增加环境变量 取消终端sudo密码 关闭开机密码 编写sh文件 虚拟环境的启用与关闭 launch文件小技巧 增加环境变量 1.在home目录下按ctrlh打开隐藏文件&#xff0c;打开.bashrc直接修改即可 2.输入gedit/vim ~/.bashrc修改即可 对于source ~/.bashrc这条指令只是适用…

从零开始讲DDR(5)——读懂Datasheet

对于开发人员来说&#xff0c;需要根据实际场景和使用的需要&#xff0c;使用不同厂家&#xff0c;不同型号的DDR&#xff0c;虽然原理上大同小异&#xff0c;但是还是有一些细节上的需要注意的地方&#xff0c;接触一个新的DDR芯片&#xff0c;首先就是需要找到对应的datashee…

软考高级:系统安全分析与设计- 加密管理:PKI 和 KMI 区别

讲解 PKI&#xff08;公钥基础设施&#xff09;和 KMI&#xff08;密钥管理基础设施&#xff09;都是与加密和密钥管理相关的重要概念&#xff0c;但它们有不同的侧重点。接下来&#xff0c;我将通过一个生活化的例子和概念讲解&#xff0c;帮助你理解它们的区别。 生活化例子…

【redis-02】深入理解redis中RBD和AOF的持久化

redis系列整体栏目 内容链接地址【一】redis基本数据类型和使用场景https://zhenghuisheng.blog.csdn.net/article/details/142406325【二】redis的持久化机制和原理https://zhenghuisheng.blog.csdn.net/article/details/142441756 如需转载&#xff0c;请输入&#xff1a;htt…

[Java EE] 网络原理 ---- UDP协议 序列化 / 反序列化 长短连接

Author&#xff1a;MTingle major:人工智能 Build your hopes like a tower! 文章目录 文章目录 一. UDP 协议 1.UDP协议的特点 2. UDP 的结构 3. md5算法 二. 长短连接 协程 IO多路复用 序列化和反序列化 1.长短连接 2. 协程 3. IO 多路复用 4.序列化 / 反序列化 一…

队列+宽搜专题篇

目录 N叉树的层序遍历 二叉树的锯齿形层序遍历 二叉树最大宽度 在每个树行中找最大值 N叉树的层序遍历 题目 思路 使用队列层序遍历来解决这道题&#xff0c;首先判断根节点是否为空&#xff0c;为空则返回空的二维数组&#xff1b;否则&#xff0c;就进行层序遍历&#x…

论文阅读 | 可证安全隐写(网络空间安全科学学报 2023)

可证安全隐写&#xff1a;理论、应用与展望 一、什么是可证安全隐写&#xff1f; 对于经验安全的隐写算法&#xff0c;即使其算法设计得相当周密&#xff0c;隐写分析者&#xff08;攻击者&#xff09;在观察了足够数量的载密&#xff08;含有隐写信息的数据&#xff09;和载体…

6.数据库-数据库设计

6.数据库-数据库设计 文章目录 6.数据库-数据库设计一、设计数据库的步骤二、绘制E-R图三、关系模式第一范式 (1st NF)第二范式 (2nd NF)第三范式 (3nd NF)规范化和性能的关系 一、设计数据库的步骤 收集信息 与该系统有关人员进行交流、座谈&#xff0c;充分了解用户需求&am…

Vulkan 学习(9)---- vkSuraceKHR 创建

目录 OverView创建窗口表面参考代码 OverView Vulkan 是一个平台无关的图形API&#xff0c;这意味着它不能直接与特定的窗口系统(Windows&#xff0c;linux 和 macOS 的窗口系统)进行交互 为了解决这个问题&#xff0c;Vulkan 引入了窗口系统集成(Window System Intergration …