深入了解Oracle中的NULL:特性、索引和查询性能问题

我们彻底分析了 Oracle DBMS 中与 NULL 相关的所有微妙之处,以及使用 NULL 索引和查询性能的问题。

关键点

特殊值NULL意味着没有数据,说明该值未知。默认情况下,任何类型的列和变量都可以采用此值,除非它们有约束NOT NULL。此外,还会DBMS自动NOT NULL向表主键中包含的列添加约束。

的主要特征NULL是它不等于任何东西,甚至不等于另一个NULL。您不能使用任何运算符将任何值与其进行比较:=、<、>、 like ... 即使表达式也NULL != NULL不会为真,因为无法唯一地将一个未知数与另一个未知数进行比较。顺便说一句,这个表达式也不会为假,因为Oracle在计算条件时并不局限于andTRUE状态FALSE。由于存在不确定性因素NULL,因此还存在一种状态—— UNKNOWN。 

因此,Oracle不是使用二值逻辑而是使用三值逻辑进行操作。这个特征是祖父Codd在他的关系理论中奠定的,而Oracle作为关系理论DBMS,完全遵循他的戒律。为了不去思考那些奇怪的查询结果,开发人员需要知道三值逻辑的真值表。

为了方便起见,我们将创建一个打印布尔参数状态的过程:

procedure testBool( p_bool in boolean ) is
begin
	if p_bool = true then 
		dbms_output.put_line('TRUE');
	elsif p_bool = false then
		dbms_output.put_line('FALSE');
	else 
		dbms_output.put_line('UNKNOWN');
	end if;     
end;

熟悉的比较运算符屈服于NULL:

exec testBool( null  = null );  -- UNKNOWN
exec testBool( null != null );  -- UNKNOWN
exec testBool( null  = 'a'  );  -- UNKNOWN
exec testBool( null != 'a'  );  -- UNKNOWN

比较NULL

有特殊的运算符 IS NULLIS NOT NULL,可以与 进行比较NULLsIS NULL如果操作数为则返回 trueNULL否则返回 false

select case when null is null then 'YES' else 'NO' end from dual; -- YES
select case when 'a'  is null then 'YES' else 'NO' end from dual; -- NO

相应地,IS NOT NULL执行相反的操作:如果操作数的值为 则返回 true,non-NULL如果为 则返回 false NULL

select case when 'a'  is NOT null then 'YES' else 'NO' end from dual; -- YES
select case when null is NOT null then 'YES' else 'NO' end from dual; -- NO

此外,有关与缺失值进行比较的规则还有一些例外情况。第一个是DECODE函数,它认为两个NULLs彼此等价。其次,它们是复合索引:如果两个键包含空字段,但它们的所有非空字段都相等,则 Oracle 认为这两个键是等效的。

DECODE违背系统:

select decode( null
	, 1, 'ONE'
	, null, 'EMPTY' -- это условие будет истинным 
	, 'DEFAULT'
    )
from dual;

布尔运算和NULL

通常,UNKNOWN状态的处理方式与 相同FALSE。例如,如果您从表中选择行并且子句x = NULL中的条件WHERE计算结果为UNKNOWN,那么您将不会获得任何行。但是,有一个区别:如果表达式NOT(FALSE)返回 true,则NOT(UNKNOWN)返回UNKNOWN。逻辑运算符AND和OR在处理未知状态时也有自己的特点。具体见下面的例子。

在大多数情况下,未知结果被视为FALSE:

select 1 from dual where dummy = null; -- query will not return result

未知数的否定给出未知数:

exec testBool( not(null  = null) ); -- UNKNOWN
exec testBool( not(null != null) ); -- UNKNOWN
exec testBool( not(null  = 'a')  ); -- UNKNOWN
exec testBool( not(null != 'a')  ); -- UNKNOWN

OR操作员:

exec testBool( null or true  );   -- TRUE    <- !!!!!
exec testBool( null or false );   -- UNKNOWN
exec testBool( null or null  );   -- UNKNOWN

AND操作员:

exec testBool( null and true  );  -- UNKNOWN
exec testBool( null and false );  -- FALSE   <- !!!!!
exec testBool( null and null  );  -- UNKNOWN

IN和NOT IN运营商

让我们从一些初步步骤开始。为了进行测试,我们创建一个表 T,其中包含一个数字列 A 和四行:1、2、3 和NULL。

create table t as select column_value a from table(sys.odcinumberlist(1,2,3,null));

启用请求跟踪(您必须具有PLUSTRACE此角色)。

在跟踪的列表中,仅留下过滤器部分来显示请求中指定的条件展开的内容。

set autotrace on

预赛结束了。现在让我们与运营商合作。让我们尝试选择集合 (1, 2, ) 中包含的所有记录NULL:

select * from t where a in ( 1, 2, null ); -- will return [1, 2]

-- Predicate Information: 
--   filter("A"=1 OR "A"=2 OR "A"=TO_NUMBER(NULL))

NULL如您所见,未选择 的行。发生这种情况是因为谓词 " 的计算A"=TO_NUMBER(NULL)返回了 status UNKNOWN。为了包含NULLs在查询结果中,您必须显式指定它:

select * from t where a in ( 1, 2 ) or a is null; -- will return [1, 2, NULL]

-- Predicate Information: 
--    filter("A" IS NULL OR "A"=1 OR "A"=2)

现在让我们尝试一下NOT IN:

select * from t where a not in ( 1, 2, null ); -- no rows selected

-- Predicate Information:
--   filter("A"<>1 AND "A"<>2 AND "A"<>TO_NUMBER(NULL))

根本没有一个结果!让我们看看为什么查询结果中没有包含三元组。DBMS让我们手动计算for case应用的过滤器A=3:

DBMS让我们手动计算for case应用的过滤器A=3

由于三值逻辑的特殊性,一点NOT IN也不友好NULLs:一NULL进入选择条件,就不等待数据。

NULL和空字符串

这里Oracle偏离了标准并声明了和 空字符串ANSI SQL的等价性。NULL这也许是最具争议性的功能之一,它时不时地引发多页讨论,并过渡到个性和其他棘手争议的不可或缺的属性。从文档来看,Oracle本身并不介意改变这种情况(它说即使现在,空字符串也被视为NULL,这可能在未来的版本中改变),但今天已经为这个DBMS编写了如此大量的代码,采取什么措施来改变系统的行为几乎不现实。而且,至少从 DBMS 的第七个版本(1992-1996)开始,他们就开始讨论这个问题,现在第十二个版本已经在路上了。

NULL和空字符串是等价的:

exec testBool( '' is null );  -- TRUE

如果按照经典的戒律追根溯源,那么空字符串 和 等价的原因就可以在 varchar 的存储格式和内部数据块NULL中找到。NULLsOracle 将表行存储在由标题和数据列组成的结构中。每列由两个字段表示:列中数据的长度(1 或 3 个字节)以及数据本身。如果varchar2长度为零,则数据字段中没有任何内容可写入,它不占用单个字节,并且0xFF在长度字段中写入特殊值,表示没有数据。NULL以完全相同的方式表示:没有数据字段,并且0xFF被写入长度字段。当然,Oracle 的开发人员可以将这两种状态分开,但这就是自古以来他们的情况。

就我个人而言,空字符串 和 的等价NULL对我来说似乎非常自然和合乎逻辑。“空线”这个名字本身就意味着没有意义、空虚、一个甜甜圈洞。NULL基本上意思是一样的。但这里有一个令人不快的后果:如果你可以肯定地说一个空字符串的长度等于零,那么 的长度NULL就不会以任何方式定义。因此,表达式将为您length('')返回,而不是零,正如您显然所期望的那样。NULL另一个问题:您无法与空字符串进行比较。该表达式val = ''将返回状态,UNKNOWN因为它本质上等同于val = NULL.

空字符串的长度未定义:

select length('') from dual; -- NULL

无法与空字符串进行比较:

exec test_bool( 'a' != '' ); -- UNKNOWN

Oracle 方法的批评者认为空字符串并不一定意味着未知。例如,销售经理填写客户卡。他可能表明他的联系电话 (555-123456),可能表明他未知 ( NULL),或者可能表明没有联系电话(空字符串)。使用 Oracle 存储空字符串的方法,实现后一个选项将会出现问题。从语义的角度来看,这个说法是正确的,但我一直有一个问题没有得到完整的答案:经理如何在УphoneФ字段中输入空字符串,以及他如何进一步区分它来自NULL?当然,有一些选择,但仍然...

实际上,如果我们谈论 PL/SQL,那么在其引擎深处的某个地方,空字符串  NULL是不同的。看到这一点的一种方法是因为关联集合允许您在索引处存储元素''(空字符串),但不允许您在索引处存储元素NULL:

declare
	procedure empty_or_null( p_val varchar2 ) 
	is
		type tt is table of varchar2(1) index by varchar2(10);
		t tt;
	begin
		if p_val is not null then
			dbms_output.put_line('not null');
		else
			-- trying to create an element with index p_val
			t(p_val) := 'x';
			-- happened!
			dbms_output.put_line('empty string');
		end if;
	exception
		-- it was not possible to create an element with index p_val
		when others then dbms_output.put_line('NULL');
	end;
begin
 empty_or_null( 'qwe' );  -- not null
 empty_or_null( '' );     -- empty string
 empty_or_null( NULL );   -- NULL
end;

为了避免出现问题,最好从文档中学习规则:空字符串和NULLOracle中无法区分。

NULL数学-计算方法

select decode( null + 10,  null, 'UNKNOWN', 'KNOWN') a from dual; -- UNKNOWN
select decode( null * 10,  null, 'UNKNOWN', 'KNOWN') a from dual; -- UNKNOWN
select decode( abs(null),  null, 'UNKNOWN', 'KNOWN') a from dual; -- UNKNOWN
select decode( sign(null), null, 'UNKNOWN', 'KNOWN') a from dual; -- UNKNOWN

连接的情况有所不同:您可以添加NULL到字符串,但不会改变它。这就是双重标准政策。

select null ||'AA'|| null ||'BB'|| null from dual; -- AABB

NULL和聚合函数

几乎所有聚合函数,除了COUNT(即使如此,也不总是),在计算过程中都会忽略空值。如果他们不这样做,那么第一个NULL出现的结果将导致函数结果达到未知值。SUM以需要对序列 (1, 3, null, 2)求和的函数为例。如果考虑空值,那么我们将得到以下操作序列:

1 + 3 = 4; 4 + null = null; null + 2 = null。

在计算聚合时,您不太可能对这样的计算感到满意,因为您可能不想得到它。 

带数据的表。下面多次使用:

create table agg( id int, n int );
insert into agg values( 1, 1 );
insert into agg values( 2, 3 );
insert into agg values( 3, null );
insert into agg values( 4, 2 );
commit;

空值会被聚合忽略:

select sum(n) from agg; -- 6

行COUNT计数函数如果用作COUNT(*)or COUNT(常量),将计算空值。但是,如果将其用作COUNT(表达式),则将忽略 null 值。

有一个常数:

select count(*)    from agg; -- 4
select count(1+1)  from agg; -- 4
select count(user) from agg; -- 4

用表达式:

select count(n)      from agg; -- 3
select count(id)     from agg; -- 4
select count(abs(n)) from agg; -- 3

另外,您应该小心使用诸如AVG. 因为它将忽略空值,所以字段 N 的结果将为(1+3+2)/3, not (1+3+2)/4。也许你不需要这样的平均计算。为了解决此类问题,有一个标准解决方案 - 使用以下NVL函数:

select avg(n)        from agg; -- (1 + 3 + 2) / 3 = 2
select avg(nvl(n,0)) from agg; -- (1 + 3 + 0 + 2) / 4 = 1.5

如果聚合函数UNKNOWN应用于空数据集或仅包含NULLs. 例外的是设计用于计算行数的REGR_COUNTand (表达式)函数。COUNT在上面列出的情况下,它们将返回零。

仅包含 NULL 的数据集:

select sum(n)          from agg where n is null; -- UNKNOWN
select avg(n)          from agg where n is null; -- UNKNOWN
select regr_count(n,n) from agg where n is null; -- 0
select count(n)        from agg where n is null; -- 0

空数据集:

select sum(n)          from agg where 1 = 0; -- UNKNOWN
select avg(n)          from agg where 1 = 0; -- UNKNOWN
select regr_count(n,n) from agg where 1 = 0; -- 0
select count(n)        from agg where 1 = 0; -- 0

NULL在索引中

创建索引时,Oracle 在索引结构中包含包含NULL索引列中的值的所有行的条目。这样的记录称为NULL记录。这使您可以快速识别相应列包含 的行,这在使用或条件NULL执行查询时非常有用。NULLnon-NULL

  • NULL在常规索引中使用值:常规索引包括对表行的引用,指示索引列的值以及ROWIDs这些行的对应值。对于具有值的行NULL,索引会存储一个特殊标记来指示索引列中NULL是否存在。NULL这允许 Oracle 快速查找NULL索引列中的行。

  • 在复合索引中使用NULL值:在对多个列进行索引的复合索引中,每列都有自己的索引结构。因此,对于包含NULL列的复合索引,NULL每个包含 的列都会出现一个标记NULLs。

  • 函数索引 andNULLs:函数索引是基于表列上的表达式或函数构建的。如果函数允许NULL参数,则索引将包含NULL函数参数的条目。这在优化使用可为空函数的查询时非常有用。

不良做法

  • NULL对基数较低的列建立索引:在大多数值所在的列上创建索引NULL可能会导致索引使用率不佳和查询性能不佳。这是因为低NULL基数的索引会占用数据库中的大量空间,并且使用此类索引的查询可能比全表扫描慢。

  • 使用 索引非选择性列NULL:非选择性列是具有很少唯一值或很多重复NULL值的列。在此类列上创建索引可能不切实际,因为此类索引可能无法显着提高查询性能并且需要更多资源来维护。

  • NULL将索引与IS NOT NULL运算符一起使用:如果查询包含带有IS NOT NULL运算符的条件,则NULL查询优化器将不会使用索引。因此,在此类查询中使用NULL索引将毫无用处,并且会浪费资源来创建和维护不必要的索引。

  • 使用以下方式对大型文本列建立索引NULL:在可能包含NULL值的大型文本列上创建索引可能会很不利,因为索引中必须存储大量数据。对此类列建立索引会显着增加索引的大小并降低查询性能。

  • 过度使用函数索引NULL:函数索引对于优化使用允许空参数的函数的查询很有用。然而,过度使用NULL功能索引可能会导致不期望的索引大小和性能下降。

  • 不相关和未使用的索引NULL:陈旧和未使用的NULL索引保留在数据库中,消耗空间,并且在数据发生变化时需要更新。应定期解析和删除此类索引,以减少系统负载并优化性能。

重要的是要记住,NULL在索引中使用可能有用,但并不总是有用。使用 创建索引时NULL,应注意NULL列中值的基数及其在查询中的实际使用情况。这将有助于避免不必要的索引并提高数据库性能。

良好实践

  • 对具有高NULL基数的列进行索引:在具有高基数的列上创建索引NULL可能很有用,因为索引允许您快速识别具有NULL值的行。当查询经常在列中使用空或非空条件时,这尤其有用。

  • 对查询中常用的列建立索引:对查询中常用的列创建索引可以极大地提高查询性能。索引可以帮助加快数据检索速度并减少查询执行时间。

  • 使用函数索引NULL:函数索引对于优化使用允许空参数的函数的查询非常有用。此类索引可以提高使用带参数的函数的查询的性能NULL

  • 当使用运算符NULL IS NULL查找包含值的行时,将索引与:索引结合NULL使用非常有用。此类索引使您可以快速找到相应列中的所有行。IS NULL NULL NULL

NULL使用索引进行性能分析

使用 索引创建索引时NULL,建议您分析查询性能并将其与不使用索引的性能进行比较。这将帮助您确定哪些NULL索引真正提高了查询性能并且在您的特定情况下是合理的。

  • 定期索引维护:NULL索引和普通索引一样,需要定期维护。定期更新索引统计信息将有助于查询优化器正确评估查询执行计划并避免不必要的操作。

  • 删除未使用的NULL索引:NULL应定期解析和删除未使用的索引,以减少系统负载并优化数据库性能。

  • 控制更新和插入:使用NULL索引时,需要控制更新和插入操作。NULL索引会影响此类操作的性能,因此在设计和优化查询时考虑它们非常重要。

遵循这些良好实践将有效地NULLs在Oracle中使用索引,提高查询性能,并减少对数据库的影响。明智地使用NULL索引将帮助您充分利用索引并提高数据库效率。


文章来源地址https://www.toymoban.com/diary/share/375.html

到此这篇关于深入了解Oracle中的NULL:特性、索引和查询性能问题的文章就介绍到这了,更多相关内容可以在右上角搜索或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!

原文地址:https://www.toymoban.com/diary/share/375.html

如若转载,请注明出处: 如若内容造成侵权/违法违规/事实不符,请联系站长进行投诉反馈,一经查实,立即删除!

领支付宝红包 赞助服务器费用
上一篇 2023年10月10日 09:59
下一篇 2023年10月10日 10:31

相关文章

  • 深入了解PostgreSQL:高级查询和性能优化技巧

    在当今数据驱动的世界中,数据库的性能和查询优化变得尤为重要。 POSTGRESQL作为一种开源的关系型数据库管理系统,在处理大规模数据和复杂查询时表现出色。 但随着数据量和查询复杂性的增加,性能问题可能会显现出来。 本文将深入探讨POSTGRESQL的高级查询和性能优化技

    2024年02月10日
    浏览(60)
  • 深入了解5G终端射频标准中的频谱发射与互调特性

    前面的频谱发射我们已经学习了占用带宽、带外发射和杂散发射,今天是频谱发射的最后一部分内容: 互调 。在很多的标准规范中,都有互调测试的相关内容,但测试条件、测试要求和测试方法都不尽相同。我们可以不必纠结互调是否有某种固定的说法,而是关注和了解它

    2024年01月25日
    浏览(51)
  • 解决Oracle SQL语句性能问题——合理使用索引

    ​​​​​​1. 合理使用索引 索引对关系库SQL调优来说,其重要性怎么强调也不会过分。为何这么说呢?因为对SQL调优来讲,最终目的就是通过减少SQL语句对系统资源的消耗来达到优化的目的,而索引又是缩减SQL语句资源消耗的最主要手段。当然,你也可以说,还可以通过

    2024年02月08日
    浏览(40)
  • MySQL 篇-深入了解事务四大特性及原理

    🔥博客主页: 【 小扳_-CSDN博客】 ❤感谢大家点赞👍收藏⭐评论✍     文章目录         1.0 事务的概述         2.0 事务的特性         2.1 原子性         2.2 一致性         2.3 持久性         2.4 隔离性         2.4.1 脏读问题         2.4.2 不可重复读问题  

    2024年03月22日
    浏览(41)
  • oracle19c容器数据库rman备份特性-----性能优化(三)

    目录 冗余备份片 1.备份的时候指定 2.rman配置中设定 归档备份(将备份集保留) 二级备份(将备份文件保留) 1.备份闪回恢复区的恢复文件 2.备份所有恢复文件 recovery catalog database 1.創建recovery catalog 2.创建VPC  data recovery advisor 备份 如果一个数据文件很大,可以设置多通道并

    2024年02月01日
    浏览(56)
  • 【SQL开发实战技巧】系列(四十九):Oracle12C常用新特性☞表分区部分索引(Partial Indexes)

    【SQL开发实战技巧】系列(一):关于SQL不得不说的那些事 【SQL开发实战技巧】系列(二):简单单表查询 【SQL开发实战技巧】系列(三):SQL排序的那些事 【SQL开发实战技巧】系列(四):从执行计划讨论UNION ALL与空字符串UNION与OR的使用注意事项 【SQL开发实战技巧】系列

    2024年02月05日
    浏览(42)
  • 深入了解Webpack:特性、特点和结合JS混淆加密的实例

    1. 模块化管理 Webpack支持将项目拆分为多个模块,允许开发者以模块化的方式组织代码。每个模块都有自己的依赖关系,Webpack能够自动解析这些依赖,从而构建出一个整体的资源文件。 2. 加载器和插件 Webpack使用加载器(Loaders)处理不同类型的文件,如JavaScript、CSS、图片等

    2024年02月11日
    浏览(54)
  • Java 11 新特性与功能:深入了解长期支持版本的亮点

    Java 11,作为一个长期支持版本(LTS),在2018年9月发布。它引入了许多新特性和,为开发者提供了更多的工具和选项。在本文中,我们将探讨 11的主要新特性,以及何在实际项目中应用这些特性。以下是Java 11中值得关注的新特性和新功能: HTTP 客户端 API:Java 11引入了一组标

    2024年02月06日
    浏览(51)
  • 如何查询oracle中一个表的一个字段是否加了索引

    要查询Oracle数据库中一个表的一个字段是否已添加索引,可以使用以下SQL语句: 在上面的SQL语句中,将your_table_name替换为你要查询的表的名称,将your_column_name替换为你要查询的字段的名称。 这个查询语句会返回与指定表和字段关联的所有索引的名称和列名称。如果返回结果

    2024年04月16日
    浏览(51)
  • MySQL查询性能优化——索引分类(二)

    目录 一、索引分类   1.按存储引擎存储形式分类     1.1聚集索引    1.1.1聚集索引结构   1.2 二级索引  1.2.1二级索引结构 2.按数据库分类  2.1 主键索引   2.2 唯一索引  2.3 常规索引  2.4 联合索引  2.5 全文索引 上一期说到索引的原理其实就是B+树,这期我们来聊一下索引的

    2024年01月22日
    浏览(45)

觉得文章有用就打赏一下文章作者

支付宝扫一扫打赏

博客赞助

微信扫一扫打赏

请作者喝杯咖啡吧~博客赞助

支付宝扫一扫领取红包,优惠每天领

二维码1

领取红包

二维码2

领红包