Oracle SQL优化案例-查询Null值走索引

网友发来一个SQL,说他们公司的一个SQL要优化帮忙看一下,执行计划如下:

-------------------------------------SELECT * FROM (SELECT * FROM TXS C WHERE C.A ISNULL OR C.A = '' ORDER BY ID_TXS DESC) WHERE ROWNUM<=100---------------------------------------------------------------------------------------------------| Id | Operatistartupon     | Name | E-Rows |E-Bytes| Cost (%CPU)|   E-Time | OMem | 1Mem | O/1/M |---------------------------------------------------------------------------------------------------|  0 | SELECT STATEMENT     |      |        |       | 584K(100)  |          |      |      |       ||* 1 | COUNT STOPKEY        |      |        |       |            |          |      |      |       ||  2 | VIEW                 |      |      2 |  1292 | 584K (1)   | 01:56:49 |      |      |       ||* 3 | SORT ORDER BY STOPKEY|      |      2 |   334 | 584K (1)   | 01:56:49 | 2048 | 2048 | 13/0/0||* 4 | TABLE ACCESS FULL    |  TXS |      2 |   334 | 584K (1)   | 01:56:49 |      |      |       |---------------------------------------------------------------------------------------------------

SQL 比较简单,就是一个单表查询。对于oracle 的单表查询,执行计划无外乎走全表扫描和走索引两种大的方向。这是一个使用全表扫描操作( ID =4,执行计划中的 TABLE ACCESS FULL)。即使 A列上存在简单索引,也不可能走索引。原因单列字段的索引不会存储NULL值,NULL 值会被忽略。表大小TXS有10多G,执行超过 150 秒。

先说结论解决办法是:创建包含 NULL 值的索引。也就是创建一个复合索引,其中一个值是带有 NULL 的列,另一个值只是一个常量的复合索引 。 

create index idx_01 t on TXS(A,0) ONLINE;------------------------------------------------------------------------------| Id | Operation                  | Name   | Rows | Bytes |Cost (%CPU)| Time |------------------------------------------------------------------------------|  0 | SELECT STATEMENT           |        |    2 |  1292 | 4 (25)| 00:00:01 ||* 1 | COUNT STOPKEY              |        |      |       |       |          ||  2 | VIEW                       |        |    2 |  1292 | 4 (25)| 00:00:01 ||* 3 | SORT ORDER BY STOPKEY      |        |    2 |   334 | 4 (25)| 00:00:01 ||  4 | TABLE ACCESS BY INDEX ROWID| TXS    |    2 |   334 | 3 (0) | 00:00:01 ||* 5 | INDEX RANGE SCAN           | idx_01 |    2 |       | 2 (0) | 00:00:01 |------------------------------------------------------------------------------

创建完索引后,执行计划变成索引访问扫描,该sql 只要0.25s。这相当于创建索引来查找具有空值的记录(一般返回少量记录才会走索引),可是Oracle 不会为具有空值的列建立索引。于是通过向索引添加额外的字符 (1或者0),Oracle 就可为空的值建立索引。

如果一张表里面假设A 字段Null值很多并且A上创建了索引,如果查询 A is NULL的结果,这样子走全表扫描也是更高效的方式。如果A is NULL 的的结果很少,走全表扫描就非常糟糕。

案例是这么个简单的案例。下面详细把这里面的原理说清楚。假设一个组合索引有(A,B)两列组成。则有下面三种情况。

情况1-》(A非空,NULL)  --》索引中有此行记录

情况2-》(NULL,B非空) --》索引中有此行记录

情况3-》(NULL,NULL)  --》索引中无此行记录

组合索引,所有索引列的值都为NULL时,表中的该行将不在索引中存储。所以通过复合索引和至少一个非空列属性,Oracle 可以保证所有其他列的每个空值都包含在索引中,如果是IS NULL的查询可能用到此索引。

下面看例子:查询 PCT_FREE IS NULL是全面扫描

SQL> CREATE TABLE test_nulls AS SELECT * FROM dba_tables;Table created.SQL> CREATE INDEX idx_PCT_FREE  ON test_nulls(pct_free);Index created.SQL>  exec DBMS_STATS.GATHER_TABLE_STATS(ownname=>'TEST', tabname=>'TEST_NULLS')PL/SQL procedure successfully completed.SQL>  SELECT * FROM test_nulls WHERE pct_free IS NULL;72 rows selected.Execution Plan----------------------------------------------------------Plan hash value: 4225836326--------------------------------------------------------------------------------| Id  | Operation    | Name       | Rows  | Bytes | Cost (%CPU)| Time     |--------------------------------------------------------------------------------|   0 | SELECT STATEMENT  |         |    72 | 17280 |    31   (0)| 00:00:01 ||*  1 |  TABLE ACCESS FULL| TEST_NULLS |    72 | 17280 |    31   (0)| 00:00:01 |--------------------------------------------------------------------------------

甚至用hint都无法强制走索引

SQL> SELECT /*+ INDEX(tn, IDX_PCT_FREE) */ * FROM test_nulls tn WHERE pct_free IS NULL;72 rows selected.Execution Plan----------------------------------------------------------Plan hash value: 4225836326--------------------------------------------------------------------------------| Id  | Operation    | Name       | Rows  | Bytes | Cost (%CPU)| Time     |--------------------------------------------------------------------------------|   0 | SELECT STATEMENT  |         |    72 | 17280 |    31   (0)| 00:00:01 ||*  1 |  TABLE ACCESS FULL| TEST_NULLS |    72 | 17280 |    31   (0)| 00:00:01 |--------------------------------------------------------------------------------

创建组合索引 (pct_free, owner) ,执行计划开始走索引

SQL> CREATE INDEX IDX_PCT_FREE2 ON test_nulls(pct_free, owner) ;Index created.SQL>  SELECT * FROM test_nulls WHERE pct_free IS NULL;72 rows selected.Execution Plan----------------------------------------------------------Plan hash value: 2881765546---------------------------------------------------------------------------------------------| Id  | Operation        | Name      | Rows  | Bytes | Cost (%CPU)| Time     |---------------------------------------------------------------------------------------------|   0 | SELECT STATEMENT      |        |   72 | 17280 |    8   (0)| 00:00:01 ||   1 |  TABLE ACCESS BY INDEX ROWID| TEST_NULLS    |   72 | 17280 |    8   (0)| 00:00:01 ||*  2 |   INDEX RANGE SCAN      | IDX_PCT_FREE2 |   72 |      |    2   (0)| 00:00:01 |---------------------------------------------------------------------------------------------

创建另外一个组合索引(pct_free, ' ')

SQL> CREATE INDEX IDX_PCT_FREE3 ON test_nulls(pct_free, ' ');Index created.SQL>  SELECT * FROM test_nulls WHERE pct_free IS NULL;no rows selectedExecution Plan----------------------------------------------------------Plan hash value: 3683813840---------------------------------------------------------------------------------------------| Id  | Operation        | Name      | Rows  | Bytes | Cost (%CPU)| Time     |---------------------------------------------------------------------------------------------|   0 | SELECT STATEMENT      |        |   72 | 17280 |    6   (0)| 00:00:01 ||   1 |  TABLE ACCESS BY INDEX ROWID| TEST_NULLS    |   72 | 17280 |    6   (0)| 00:00:01 ||*  2 |   INDEX RANGE SCAN      | IDX_PCT_FREE3 |   72 |      |    2   (0)| 00:00:01 |---------------------------------------------------------------------------------------------

可以看到走了代价更低的IDX_PCT_FREE3。这是因为IDX_PCT_FREE3索引比IDX_PCT_FREE2索引更加小。(pct_free, ' ')的空格字符占用一个字节,索引中的列长度占用一个额外字节,每个索引条目总共需要 2 个字节的开销。开销越小,CBO就越可能选

现在表的索引情况:

IDX_PCT_FREE3 --》(pct_free, ' ')  --》7  --》明显要比用owner的组合索引要小

IDX_PCT_FREE2  --》(pct_free, owner)--》9

IDX_PCT_FREE --》(pct_free)--》6

dump索引块看看

SQL> select object_id from dba_objects where object_name=upper('IDX_PCT_FREE3'); OBJECT_ID----------     88896SQL> alter session set events  'immediate trace name treedump level &object_id_index';Enter value for object_id_index: 88896old   1: alter session set events  'immediate trace name treedump level &object_id_index'new   1: alter session set events  'immediate trace name treedump level 88896'Session altered.SQL>

----- begin tree dump

branch: 0x1083593 17315219 (0: nrow: 7, level: 1)  ---root节点

   leaf: 0x1083594 17315220 (-1: nrow: 462 rrow: 462) --7个叶子块

   leaf: 0x1083595 17315221 (0: nrow: 448 rrow: 448)--每个叶子块448行记录

   leaf: 0x1083596 17315222 (1: nrow: 448 rrow: 448)

   leaf: 0x1083597 17315223 (2: nrow: 448 rrow: 448)

   leaf: 0x1083598 17315224 (3: nrow: 448 rrow: 448)

   leaf: 0x1083599 17315225 (4: nrow: 448 rrow: 448)

   leaf: 0x108359a 17315226 (5: nrow: 169 rrow: 169)

----- end tree dump

dump一个索引叶子块

SQL> @get_dba.sqlEnter value for rdbanex: 0x1083596old   1: SELECT dbms_utility.DATA_BLoCK_ADDRESS_FILE(to_number(REPLACE('&rdbanex',new   1: SELECT dbms_utility.DATA_BLoCK_ADDRESS_FILE(to_number(REPLACE('0x1083596',Enter value for rdba_hex: 0x1083596old   5:  dbms_utility.DATA_BLoCK_ADDRESS_BLocK(to_number(REPLACE('&rdba_hex',new   5:  dbms_utility.DATA_BLoCK_ADDRESS_BLocK(to_number(REPLACE('0x1083596',   FI1E_NO   B1OCK_NO---------- ----------   4     538006SQL> alter system dump datafile &file_id block &block;Enter value for file_id: 4Enter value for block: 538006old   1: alter system dump datafile &file_id block &blocknew   1: alter system dump datafile 4 block 538006System altered.SQL>

--叶子节点保存的是索引的值和rowid的值

KDXCOLEV Flags = - - -

kdxcolok 0

kdxcoopc 0x80: opcode=0: iot flags=--- is converted=Y

kdxconco 3

kdxcosdc 0

kdxconro 448

kdxcofbo 932=0x3a4

kdxcofeo 1760=0x6e0

kdxcoavs 828

kdxlespl 0

kdxlende 0

kdxlenxt 17315223=0x1083597

kdxleprv 17315221=0x1083595

kdxledsz 0

kdxlebksz 8032

row#0[8018] flag: ------, lock: 0, len=14

col 0; len 2; (2):  c1 0b

col 1; len 1; (1):  20  --》可以看到空格存储到数据块里面就是16进制的20 

col 2; len 6; (6):  01 08 35 1d 00 15  --》rowid信息

row#1[8004] flag: ------, lock: 0, len=14

col 0; len 2; (2):  c1 0b

col 1; len 1; (1):  20

col 2; len 6; (6):  01 08 35 1d 00 16

row#2[7990] flag: ------, lock: 0, len=14

col 0; len 2; (2):  c1 0b

col 1; len 1; (1):  20

col 2; len 6; (6):  01 08 35 1d 00 17

row#3[7976] flag: ------, lock: 0, len=14

col 0; len 2; (2):  c1 0b

col 1; len 1; (1):  20

col 2; len 6; (6):  01 08 35 1d 00 18

row#4[7962] flag: ------, lock: 0, len=14

col 0; len 2; (2):  c1 0b

col 1; len 1; (1):  20

col 2; len 6; (6):  01 08 35 1d 00 19

SQL>  SELECT dump(0,16), dump(' ',16), dump(1,16) FROM dual;

DUMP(0,16) DUMP(' ',16) DUMP(1,16)

--------------- ---------------- -----------------

Typ=2 Len=1: 80 Typ=96 Len=1: 20 Typ=2 Len=2: c1,2

值0的空格的长度和空格的长度都是1,索引更加小。但是数字的话长度是2,查看dba_ind_expressions视图可以显示索引中的表达式.

​总结:Oracle 索引默认情况下不能包含空值。这是因为在 B 树索引结构中,空值无法唯一标识索引的位置,这会导致索引的不确定性和性能问题。因此,Oracle 索引在设计上排除了空值。但是可以通过创建函数索引或者在列上创建复合索引的方式绕过这一限制。也就是说SQL优化很多时候先从原理出发,理解为什么不保存空值。因为这是Oracle 算法决定了。那么思路就变成了,怎么样能使算法生效。本质也就是理论和实践的相结合,而不是凭空想象。无中生有。

欢迎关注和转发本公众号,你的支持是我继续写作的动力

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

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

相关文章

【全开源】Java知识付费教育付费资源付费平台公众号小程序源码

特色功能&#xff1a; 多样化的内容呈现&#xff1a;资源付费平台小程序支持图文、音视频、直播等多种形式的内容呈现&#xff0c;为用户提供了丰富的学习体验。直播课程&#xff1a;专家或讲师可以通过小程序进行在线授课&#xff0c;与用户实时互动&#xff0c;增强了学习的…

设计模式-结构型-桥接模式-Bridge

矩阵类 public class Matrix {private String fileName;public Matrix(String fileName) {this.fileName fileName;}public String getFileName() {return fileName;} } 图片抽象类 public abstract class Image {protected ImageImp imp;public void setImp(ImageImp imp)…

如何使用Tushare+ Backtrader进行股票量化策略回测

数量技术宅团队在CSDN学院推出了量化投资系列课程 欢迎有兴趣系统学习量化投资的同学&#xff0c;点击下方链接报名&#xff1a; 量化投资速成营&#xff08;入门课程&#xff09; Python股票量化投资 Python期货量化投资 Python数字货币量化投资 C语言CTP期货交易系统开…

IT服务台的优势

我们谈谈IT服务台的一些好处&#xff0c;以更好地了解其重要性。IT 服务台为所有利益相关者&#xff08;技术人员和最终用户&#xff09;提供服务带来了效率。例如&#xff0c;三层 IT 服务台可以在第 0 层拥有自助服务门户&#xff0c;在第 1、2 和 3 层拥有技术人员&#xff…

SpringAMQP Work Queue 工作队列

消息模型: 代码模拟: 相较于之前的基础队列&#xff0c;该队列新增了消费者 不再是一个&#xff0c;所以我们通过代码模拟出两个consumer消费者。在原来的消费者类里写两个方法 其中消费者1效率高 消费者2效率低 RabbitListener(queues "simple.queue")public voi…

二维数组 和 变长数组

在上一期的内容中&#xff0c;为诸君讲解到了一维数组&#xff0c;在一维数组的基础上&#xff0c;C语言中还有着多维数组&#xff0c;其中&#xff0c;比较典型且运用较为广泛的就是我们今天的主角——二维数组 一 . 二维数组的概念 我们把单个或者多个元素组成的数组定义为一…

React 第三十一章 前端框架的分类

现代前端框架&#xff0c;有一个非常重要的特点&#xff0c;那就是基于状态的声明式渲染。如果要概括的话&#xff0c;可以使用一个公式&#xff1a; UI f&#xff08;state&#xff09; state&#xff1a;当前视图的一个状态f&#xff1a;框架内部的一个运行机制UI&#xff1…

LNMP环境部署WordPress——使用源码包安装方式部署环境

目录 一.前提准备 二.源码安装Mysql 1.MySQL类型 2.MySQL 版本说明 3.MySQL 安装方式 3.1 yum 安装 3.2 编译安装 3.3 二进制安装 3.4 rpm 安装 4. 编译安装MySQL5.7 4.1 清理安装环境 4.2 创建mysql用户 4.3 从官网下载tar包 4.4 安装编译工具 4.5 解压 4.6 …

专业130+总分400+哈尔滨工程大学810信号与系统考研哈工程水声电子信息通信工程,真题,大纲,参考书。

毕业设计刚搞完&#xff0c;总结一下去年考研的复习经历&#xff0c;希望对大家复习有帮助&#xff0c;考研专业课810信号与系统130总分400&#xff0c;如愿上岸哈工程水声。专业课&#xff1a;130 哈工程水声院810专业课信号与系统难度适中&#xff0c;目前数一难度很高&…

【全开源】Java洗衣清洁服务同城清洗服务小程序源码

特色功能&#xff1a; 在线预约与支付&#xff1a;用户可以直接通过小程序预约洗衣时间和地点&#xff0c;同时还可以在线支付洗衣费用&#xff0c;从而避免了排队等待的时间&#xff0c;提高了效率。订单查询与服务评价&#xff1a;用户可以通过小程序实时查询洗衣订单的状态…

算法详解——回溯法

一、回溯法概述——问题背景 回溯法是一种解决约束满足问题的方法&#xff0c;特别适用于解决组合问题、搜索优化问题等。它通过逐步构建候选解决方案并且在这个解决方案不再可能满足约束或条件时进行剪枝和回溯。具体来说&#xff0c;回溯法可以应用于以下类型的问题&#xff…

物联网到底物联了个啥?——青创智通

工业物联网解决方案-工业IOT-青创智通 物联网&#xff0c;这个听起来似乎颇具科技感和未来感的词汇&#xff0c;其实早已悄然渗透到我们生活的方方面面。从智能家居到智慧城市&#xff0c;从工业自动化到医疗健康&#xff0c;物联网技术正在以其独特的魅力改变着我们的生活方式…