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 算法决定了。那么思路就变成了,怎么样能使算法生效。本质也就是理论和实践的相结合,而不是凭空想象。无中生有。

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

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

JVM---垃圾回收

目录 一、C/C的内存管理 二、Java的内存管理 三、垃圾回收的对比 四、Java的内存管理和自动垃圾回收 五、方法区的回收 手动触发回收 六、堆回收 如何判断堆上的对象可以回收&#xff1f; 引用计数法 可达性分析算法 五种对象引用 软引用 软引用的使用场景-缓存 弱引用 虚…

【深入理解MySQL的索引数据结构】

文章目录 &#x1f50a;博主介绍&#x1f964;本文内容&#x1f4d5;索引底层数据结构与算法&#x1f4d9;索引数据结构&#x1f4d8;二叉树&#x1f4d8;红黑树&#x1f4d8;Hash&#x1f4d8;B-Tree&#x1f4d8;BTree &#x1f4d9;表在不同存储引擎的存储结构&#x1f4d8;…

2023.5.12 第43周周报

学习时间&#xff1a;2023.5.5-2023.5.12 学习内容&#xff1a; 1、answer question: img&#xff1a; 看到有论文说应该让图像和文本的潜在嵌入具有相似和合理的数值范围【-2&#xff0c;2】 调试发现模型的文本图像的潜在嵌入虽然符合&#xff0c;但相差较大。 在将文本和…

Python - 深度学习系列33 - ollama_langchain_ppt生成

说明 只是为了速记一下这个实践过程。整体上说&#xff0c;这个结果并不是那么好用&#xff0c;但有一些可以借鉴的地方。 先看结果&#xff1a; 生成的PPT 说的直白点&#xff0c;就是用大模型生成了一堆没太有意义的文字&#xff0c;然后做成ppt。所以实用是不成的&#…

数据猎手:使用Java和Apache HttpComponents库下载Facebook图像

引言 在信息驱动的时代&#xff0c;互联网上的数据成为了无可比拟的宝藏。本文旨在探讨如何通过利用Java和Apache HttpComponents库&#xff0c;从全球最大的社交网络平台Facebook上获取图像数据。 作为全球最大的社交网络平台&#xff0c;Facebook聚集了数以亿计的用户&#…

Rust的协程机制:原理与简单示例

在现代编程中&#xff0c;协程&#xff08;Coroutine&#xff09;已经成为实现高效并发的重要工具。Rust&#xff0c;作为一种内存安全的系统编程语言&#xff0c;也采用了协程作为其并发模型的一部分。本文将深入探讨Rust协程机制的实现原理&#xff0c;并通过一个简单的示例来…

百度GL地图实现某一段路的路况(new BMapGL.DrivingRouteLine)

功能描述&#xff1a; 1.百度地图实现点击地图出现起点&#xff0c;再次点击出现终点&#xff08;起点终点能拖动&#xff09;绘制完终点后获取该路的路况并且起点和终点可以拖动实现实时更新&#xff08;新绘制的路段的&#xff09;路况 2.地点搜索 效果如下&#xff1a; 关键…

从文本日志到图形日志 图形化编程桌面产品的突破

在一个宽敞明亮的会议室里&#xff0c;阳光透过落地窗洒在会议桌上。卧龙和凤雏相对而坐&#xff0c;他们的面前摆放着一些关于图形化编程桌面产品的资料和测试报告。会议室里的气氛紧张而热烈&#xff0c;团队成员们围坐在一起&#xff0c;专注地倾听着卧龙和凤雏的讨论。 卧龙…

Rust编程语言的特点及其适合做什么

Rust编程语言的特点 Rust是一门系统级编程语言&#xff0c;它有如下特点。 1. 类C的语言语法 Rust的具体语法和C/C类似&#xff0c;都是由花括号限定代码块&#xff0c;还有一样的控制流关键字&#xff0c;例如if、else、while和for。然而&#xff0c;也并非所有的C或者C关键…

阿里云和AWS负载均衡服务对比分析

在云计算时代,负载均衡作为一种关键的网络基础设施,承担着在多个服务器之间分发网络流量的重要任务。作为全球两大主要的云服务提供商,阿里云和Amazon Web Services(AWS)都提供了强大的负载均衡解决方案。本文将从性能、功能、可用性和成本等方面对两者进行对比分析。我们九河云…

云南区块链商户平台:抓包技术自制开票工具(三)

前言 上节我们将登录的流程梳理完毕了&#xff0c;来到了本章重点&#xff0c;既然开发票就肯定要有以下参数&#xff1a; 原工具不支持识别历史记录&#xff0c;对于我们的小商店来说&#xff0c;开票的公司基本就是固定的几个&#xff0c;如果提供下拉支持选择将会大大降低…

EPAI手绘建模APP工程图顶部工具栏

7、工程图 图 302 工程图 工程图包括顶部常用工具栏、右侧工程图工具栏、左侧模型列表栏、中间的工程图。 (1) 常用工具栏 ① 删除&#xff0c;选中场景中工程图元素后&#xff0c;删除。可以选择多个工程图元素同时删除。 ② 设置&#xff0c;打开工程图设置页面&#xff0…

FreeRTOS的列表和列表项 list.c文件详解

列表、列表项的定义以及初始化 列表相当于链表&#xff0c;列表项相当于节点&#xff0c;FreeRTOS中的列表相当于一个双向环形链表。 列表使用指针指向列表项。一个列表&#xff08;list&#xff09;下面可能有很多个列表项&#xff08;list item&#xff09;&#xff0c;每个…

JavaEE技术之MySql主从复制及mycat[了解,不讲]

文章目录 1. 主从复制1.1. 主从同步的原理1.2. 检查数据库远程访问权限1.3. 主从配置1.3.1. master配置1.3.2. slave配置1.3.3. 主库创建同步用户1.3.4. 从库配置主从关系1.3.5. 重置主从关系 1.4. 测试主从复制 2. Mycat2.1. Mycat简介2.2. MyCat读写分离原理2.3. 不废话&…

Linux NFS共享目录配置漏洞

Linux NFS共享目录配置漏洞 一、实验目的二、实验原理三、复现准备四、漏洞复现4.1、复现前提4.2、正式复现 一、实验目的 利用 NFS共享目录配置漏洞读取目标主机的 /etc/passwd 文件内容NFS 服务配置漏洞&#xff0c;赋予了根目录远程可写权限&#xff0c;导致 /root/.ssh/au…

编写Ansible角色实现分布式LNMP安装

前言 本文将介绍如何使用 Ansible 编写角色&#xff0c;在分布式环境下完成 LNMP&#xff08;Linux、Nginx、MySQL、PHP&#xff09;的自动化&#xff08;编译&#xff09;安装和配置&#xff0c;并验证 PHP 与 MySQL 数据联通性&#xff0c;实现博客和论坛页面的展示。 常规…

初识C语言——第十八天

循环while/do while while 语法结构 while(表达式) 循环语句; break:在while循环中&#xff0c;break用于永久的终止循环 continue:在while循环中&#xff0c;continue的作用是跳过本次循环continue后面的代码 直接去判断部分&#xff0c;看是否进行下一次循环。 注意事项…

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

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

nacos命名空间的配置

给微服务配置namespace 给微服务配置namespace只能通过修改配置来实现。 例如&#xff0c;修改order-service的application.yml文件&#xff1a; spring:cloud:nacos:server-addr: localhost:8848discovery:cluster-name: HZnamespace: 492a7d5d-237b-46a1-a99a-fa8e98e4b0f…

sscanf字符串解析

ATCIPSNTPTIME? //发生的指令 CIPSNTPTIME:Tue Oct 19 15:17:56 2021 //回复的数据 //接受数据缓存 char* recvStrBuf "CIPSNTPTIME:Tue Oct 19 15:17:56 2021"; char* weekStr; char* monthStr; int day,hour,minute,second,year; sscanf(recvStrBuf,""…