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

news2024/11/25 11:41:37

网友发来一个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.coloradmin.cn/o/1666434.html

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

相关文章

集成平台建设方案(大数据中台技术方案)—Word原件

基础支撑平台主要承担系统总体架构与各个应用子系统的交互&#xff0c;第三方系统与总体架构的交互。需要满足内部业务在该平台的基础上&#xff0c;实现平台对于子系统的可扩展性。基于以上分析对基础支撑平台&#xff0c;提出了以下要求&#xff1a; 基于平台的基础架构&…

arm架构下安装conda

一、参考文章&#xff1a;感谢这位网友的分享&#xff0c;搬过来以备过几天使用&#xff0c;这种小众系统真的有些麻烦解决方案&#xff1a;ARM架构下安装Miniconda 离线配置Conda环境的全流程及踩坑避坑指南 - 技术栈 二、步骤 2.1 确认系统架构 uname -m 2.2 根据架构下载…

云贝教育 |【直播课】5月19日Oracle 19c OCM认证大师课 即将上课了!(附课件预览)

贝教育独家认证课OCM全网价格最低&#xff0c;性价比最高&#xff01;&#xff01;&#xff01; Oracle 19c OCM认证大师培训 - 课程体系 - 云贝教育 (yunbee.net) OCM部分课件预览 Oracle Database 19c Certified Master Exam (OCM) 认证大师 25 天 / 150课时 什么是Oracle 1…

浅析vue3自定义指令

vue3中可以像下面这样使用自定义指令。 这里我们只是定义了一个vFoucs变量&#xff0c;vue怎么知道这是一个指令呢&#xff1f; 这是因为约定大于配置&#xff0c;vue3中有这样一个约定&#xff08;截图来自官方文档&#xff09;&#xff1a; 注意这里说的是驼峰命令&#x…

插入法(直接/二分/希尔)

//稳定耗时&#xff1a; 双向冒泡&#xff0c;可指定最大最小值个数MaxMinNum<nsizeof(Arr)/sizeof(Arr[0]), void BiBubbleSort(int Arr[],int n&#xff0c;int MaxMinNum){int left0,rightn-1;int i;bool notDone true;int temp;int minPos;while(left<right&&am…

《Linux运维总结:ARM64架构CPU基于docker-compose一离线部署rabbitmq 3.10.25容器版镜像模式集群》

总结&#xff1a;整理不易&#xff0c;如果对你有帮助&#xff0c;可否点赞关注一下&#xff1f; 更多详细内容请参考&#xff1a;《Linux运维篇&#xff1a;Linux系统运维指南》 一、部署背景 由于业务系统的特殊性&#xff0c;我们需要面向不通的客户安装我们的业务系统&…

情感感知OCR:整合深度学习技术提升文字识别系统的情感理解能力

摘要&#xff1a;随着深度学习技术的发展&#xff0c;文字识别&#xff08;OCR&#xff09;系统在识别准确率和速度上取得了长足的进步。然而&#xff0c;在处理文本时&#xff0c;仅仅依靠字符和词语的识别并不足以满足用户对信息的全面理解需求。本文提出了一种新颖的方法&am…

Navicat 17:先睹为快

官方声明&#xff1a;Navicat 17&#xff08;英文版&#xff09;目前处于测试阶段中&#xff0c;并计划 5 月 13 日发布&#xff01; 如果你觉得 Navicat 16 已经推出很多令人兴奋的新功能&#xff0c;那么这次你可能要好好看看 Navicat 17&#xff0c;本次升级涵盖了更多的内容…

vscode切换分支及合并分支操作教程

工具&#xff1a;gitee、git 、vscode、Git Graph 点击可以看到分支管理明细。 一、前提 1、首先要有两个分支 &#xff08;1&#xff09;分支说明&#xff1a; test&#xff1a; 测试分支 feature/luo-20240508&#xff1a;自己的开发分支 &#xff08;2&#xff09;分支说…

【启明智显分享】国产自主HMI核心板Model3

Model3是一款高性能的工业级HMI&#xff08;人机界面&#xff09;核心板&#xff0c;也是一款纯国产HMI方案&#xff0c;工业级标准&#xff0c;稳定、可靠&#xff1b; 工业级HMI芯片–Model3 纯国产HMI方案 Model3核心板&#xff0c;具有2D加速&#xff0c;PNG解码&…

生产性服务业与生活性服务业如何区分

服务业的兴旺发达是现代经济的显著特征&#xff0c;是经济社会发展的必然趋势&#xff0c;是衡量经济发展现代化、国际化、高端化的重要标志。生产性服务业和生活性服务业是服务业的重要组成部分&#xff0c;是当前中国经济最具活力的产业&#xff0c;也是未来经济发展最具潜力…

【linux-IMX6ULL-定时器-GPT-串口配置流程-思路】

目录 1. 定时器配置流程1.1 EPIT定时器简介1.2 定时器1(epit1)的配置流程1.3 配置代码(寄存器版本)1.4 定时器-配合按键消抖1.4.1 实现原理1.4.2 代码实现&#xff08;寄存器版&#xff09; 2. GPT定时器实现高精度延时2.1 延时原理分析2.2 代码实现 3. UART串口配置流程3.1 UA…

SpringCloudAlibaba:4.2云原生网关higress的基本使用

概述 简介 Higress是基于阿里内部的Envoy Gateway实践沉淀、以开源Istio Envoy为核心构建的下一代云原生网关&#xff0c; 实现了流量网关 微服务网关 安全网关三合一的高集成能力&#xff0c;深度集成Dubbo、Nacos、Sentinel等微服务技术栈 定位 在虚拟化时期的微服务架构…

文本检测模型 DBNet 一种基于分割算法的模型 对每个像素点进行自适应二值化,并将二值化过程与网络训练相结合 可微分二值化模块 概率图

文本检测模型 DBNet DBNet文本检测模型是一种基于分割算法的模型,其优化之处在于对每个像素点进行自适应二值化,并将二值化过程与网络训练相结合。 传统的文本检测方法通常将二值化作为一个后处理步骤,与网络训练分开进行。而DBNet则提出了一种可微分的二值化方法,即将文…

类加载机制(双亲委派机制)

文章目录 JVM的作用是什么双亲委派机制加载流程 JVM的作用是什么 我们运行Java程序时&#xff0c;要安装JDK&#xff0c;JDK包含JVM&#xff0c;不同环境的JDK都是不同的。 Java 代码在编译后会形成 class 的字节码文件&#xff0c;该字节码文件通过 JVM 解释器&#xff0c;生…

【Linux】基于 Jenkins+shell 实现更新服务所需文件 -->两种方式:ssh/Ansible

&#x1f468;‍&#x1f393;博主简介 &#x1f3c5;云计算领域优质创作者   &#x1f3c5;华为云开发者社区专家博主   &#x1f3c5;阿里云开发者社区专家博主 &#x1f48a;交流社区&#xff1a;运维交流社区 欢迎大家的加入&#xff01; &#x1f40b; 希望大家多多支…

基于 LlaMA 3 + LangGraph 在windows本地部署大模型 (四)

基于 LlaMA 3 LangGraph 在windows本地部署大模型 &#xff08;四&#xff09; 大家继续看 https://lilianweng.github.io/posts/2023-06-23-agent/的文档内容 第三部分&#xff1a;工具使用 工具的使用是人类的一个显着而显着的特征。我们创造、修改和利用外部物体来完成超…

gocator导出图片

想用3D扫描后的图片&#xff0c;但是系统自带的导出方法很麻烦&#xff0c;所以考虑通过sdk导出 首先需要设置点云亮度 这里是导出图片的关键代码 case GoDataMessageType.SurfaceIntensity: { Debug.WriteLine("SurfaceIntensity "); GoSu…

C++ 中的 lambda 表达式

1.概念 lambda表达式实际上是一个匿名类的成员函数&#xff0c;该类由编译器为lambda创建&#xff0c;该函数被隐式地定义为内联。因此&#xff0c;调用lambda表达式相当于直接调用匿名类的operator()函数&#xff0c;这个函数可以被编译器内联优化&#xff08;建议&#xff0…

BGP第二篇(bgp邻居状态及影响邻居建立的因素)

1、bgp邻居状态 BGP对等体的交互过程中存在6种状态机&#xff1a; 空闲&#xff08;Idle&#xff09; 连接&#xff08;Connect&#xff09; 活跃 &#xff08;Active&#xff09; Open报文已发送&#xff08;OpenSent&#xff09; Open报文已确认&#xff08;OpenConfirm&…