Skip to content
Deven Home
Go back

sql优化

9 min read
—Edit page

sql优化

1.基数

某个列唯一键(Distinct_Keys)的数量叫作基数。比如性别列,该列只有男女之分,所以这一列基数是2。主键列的基数等于表的总行数。基数的高低影响列的数据分布。

暂定:

返回表中5%以内的数据走索引,返回超过5%的数据就使用全表扫描,*这个结论太绝对了*

2.选择性

基数与总行数的比值再乘以100%就是某个列的选择性。

当一个列选择性大于20%,说明该列的数据分布就比较均衡了

3.直方图

当某个列基数很低,该列数据分布就会不均衡。数据分布不均衡会导致在查询该列的时候,要么走全表扫描,要么走索引扫描,这个时候很容易走错执行计划。

如果没有对基数低的列收集直方图统计信息,基于成本的优化器(CBO)会认为该列数据分布是均衡的。

当列没有收集直方图统计信息的时候,CBO会认为该列数据分布是均衡的。

执行计划里面的Rows是假的。执行计划中的Rows是根据统计信息以及一些数学公式计算出来的

如果CBO每次都能计算得到精确的Rows,那么相信我们这个时候只需要关心业务逻辑、表设计、SQL写法以及如何建立索引了,再也不用担心SQL会走错执行计划了。

直方图是用来帮助CBO在对基数很低、数据分布不均衡的列进行Rows估算的时候,可以得到更精确的Rows就够了。

什么样的列需要收集直方图呢?

当列出现在where条件中,列的选择性小于1%并且该列没有收集过直方图,这样的列就应该收集直方图。

绑定变量

4.回表

当对一个列创建索引之后,索引会包含该列的键值以及键值对应行所在的rowid。通过索引中记录的rowid访问表中的数据就叫回表。回表一般是单块读,回表次数太多会严重影响SQL性能,如果回表次数太多,就不应该走索引扫描了,应该直接走全表扫描。

在进行SQL优化的时候,往往会建立合适的组合索引消除回表,或者建立组合索引尽量减少回表次数。

5.集群因子

集群因子用于判断索引回表需要消耗的物理I/O次数。

集群因子介于表的块数和表行数之间

如果集群因子与块数接近,**说明表的数据基本上是有序的,而且其顺序基本与索引顺序一样。这样在进行索引范围或者索引全扫描的时候,回表只需要读取少量的数据块就能完成。**如果集群因子与表记录数接近,说明表的数据和索引顺序差异很大,在进行索引范围扫描或者索引全扫描的时候,回表会读取更多的数据块。

集群因子只会影响索引范围扫描(INDEX RANGE SCAN)以及索引全扫描(INDEX FULL SCAN),因为只有这两种索引扫描方式会有大量数据回表。

集群因子究竟影响的是什么性能呢?集群因子影响的是索引回表的物理I/O次数。我们假设索引范围扫描返回了1 000行数据,如果buffer cache中没有缓存表的数据块,假设这1000行数据都在同一个数据块中,那么回表需要耗费的物理I/O就只需要一个;假设这1000行数据都在不同的数据块中,那么回表就需要耗费1 000个物理I/O。因此,集群因子影响索引回表的物理I/O次数。

6.表与表之间的关系

select count(*) from a left join b on a.id=b.id;

案例中a与b是1∶1关系,a与b都是上千万数据量。因为a与b是使用外连接进行关联,不管a与b是否关联上,始终都会返回a的数据,SQL语句中求的是两表关联后的总行数,因为两表是1∶1关系,关联之后数据不会翻番,那么该SQL等价于如下文本。

统计信息

访问路径

TABLE ACCESS FULL表示全表扫描,一般情况下是多块读

全表扫描究竟是怎么扫描数据的呢?回忆一下Oracle的逻辑存储结构,Oracle最小的存储单位是块(block),物理上连续的块组成了区(extent),区又组成了段(segment)。对于非分区表,如果表中没有clob/blob字段,那么一个表就是一个段。全表扫描,其实就是扫描表中所有格式化过的区。因为区里面的数据块在物理上是连续的,所以全表扫描可以多块读。全表扫描不能跨区扫描,因为区与区之间的块物理上不一定是连续的。

对于分区表,如果表中没有clob/blob字段,一个分区就是一个段,分区表扫描方式与非分区表扫描方式是一样的。

对一个非分区表进行并行扫描,其实就是同时扫描表中多个不同区,因为区与区之间的块物理上不连续,所以我们不需要担心扫描到相同数据块。

对一个分区表进行并行扫描,有两种方式。如果需要扫描多个分区,那么是以分区为粒度进行并行扫描的,这时如果分区数据不均衡,会严重影响并行扫描速度;如果只需要扫描单个分区,这时是以区为粒度进行并行扫描的。

一般的操作系统,一次I/O最多只支持读取或者写入1MB数据。数据块为8KB的时候,一次I/O最多能读取128个块。数据块为16KB的时候,一次I/O最多能读取64个块,数据块为32KB的时候,一次I/O最多能读取32个块。 如果表中有部分块已经缓存在buffer cache中,在进行全表扫描的时候,扫描到已经被缓存的块所在区时,就会引起I/O中断。如果一个表不同的区有大量块缓存在buffer cache中,这个时候,全表扫描性能会严重下降,因为有大量的I/O中断,导致每次I/O不能扫描1MB数据。

TABLE ACCESS BY USER ROWID表示直接用ROWID获取数据,单块读。

该访问路径在Oracle所有的访问路径中性能是最好的。

TABLE ACCESS BY ROWID RANGE表示ROWID范围扫描,多块读。因为同一个块里面的ROWID是连续的,同一个EXTENT里面的ROWID也是连续的,所以可以多块读。

TABLE ACCESS BY ROWID RANGE表示ROWID范围扫描,多块读。因为同一个块里面的ROWID是连续的,同一个EXTENT里面的ROWID也是连续的,所以可以多块读。

INDEX UNIQUE SCAN表示索引唯一扫描,单块读。 对唯一索引或者对主键列进行等值查找,就会走INDEX UNIQUE SCAN。因为对唯一索引或者对主键列进行等值查找,CBO能确保最多只返回1行数据,所以这时可以走索引唯一扫描。

对唯一索引或者对主键列进行等值查找,就会走INDEX UNIQUE SCAN。

INDEX RANGE SCAN表示索引范围扫描,单块读,返回的数据是有序的(默认升序)。

对唯一索引或者主键进行范围查找,对非唯一索引进行等值查找,范围查找,就会发生INDEX RANGE SCAN。

索引范围扫描默认是从索引中最左边的叶子块开始,然后往右边的叶子块扫描(从小到大),当检查到不匹配数据的时候,就停止扫描。

INDEX SKIP SCAN表示索引跳跃扫描,单块读。返回的数据是有序的(默认升序)。

当组合索引的引导列(第一个列)没有在where条件中,并且组合索引的引导列/前几个列的基数很低,where过滤条件对组合索引中非引导列进行过滤的时候就会发生索引跳跃扫描

当执行计划中出现了INDEX SKIP SCAN,我们可以直接在过滤列上面建立索引,使用INDEX RANGE SCAN代替INDEX SKIP SCAN。

INDEX FULL SCAN表示索引全扫描,单块读,返回的数据是有序的(默认升序)。


Edit page
Share this post:

Previous Post
java lamda
Next Post
大型开源项目参与经验分享:从 Good First Issue 到核心贡献者