关于索引和空值的讨论

简介: 在日常的工作中,空值总是有特殊的身份,对于它的处理有时候也是比较纠结。 有时候创建索引的时候会因为空值出现一些奇怪的结果。 有时候一个简单的查询因为空值却走不了索引。

在日常的工作中,空值总是有特殊的身份,对于它的处理有时候也是比较纠结。
有时候创建索引的时候会因为空值出现一些奇怪的结果。
有时候一个简单的查询因为空值却走不了索引。
有时候却因为空值而能走索引。

我们来简单的模拟一下这些问题。
首先创建一个空表,注意对于id列我们是加了not null的约束的。

SQL> create table index_test(id number not null,name varchar2(30) )  ;
Table created.
我们创建一个唯一性索引,包含了id和name列。
SQL> create unique index inx_test on index_test(id,name);
Index created.
这个时候我们对表index_test插入数据。因为name列没有非空约束,所以可以为空。注意第一条insert语句,如果插入空串也会作为null来处理。
SQL> insert into index_test values(2,'');
1 row created.
SQL> insert into index_test values(1,'a');
1 row created.
SQL> insert into index_test values(3,null);
1 row created.
收集一下统计信息。

exec dbms_stats.gather_table_stats(user,'INDEX_TEST');
这个时候我们来看一下下面这个查询额执行计划。

SQL> select *from index_test where id is not null;
Execution Plan
----------------------------------------------------------
Plan hash value: 4273605835
-----------------------------------------------------------------------------
| Id  | Operation        | Name     | Rows  | Bytes | Cost (%CPU)| Time     |
-----------------------------------------------------------------------------
|   0 | SELECT STATEMENT |          |     3 |    12 |     1   (0)| 00:00:01 |
|   1 |  INDEX FULL SCAN | INX_TEST |     3 |    12 |     1   (0)| 00:00:01 |
-----------------------------------------------------------------------------

对于这个查询可能没什么感觉,走了全索引扫描。我们在查询条件中添加了id is not null的条件,其实id列已经存在非空约束了。所以这个过滤条件可有可无。
我们来看看不加过滤条件的情况。还是走了全索引扫描。
SQL> select *from index_test;
Execution Plan
----------------------------------------------------------
Plan hash value: 4273605835
-----------------------------------------------------------------------------
| Id  | Operation        | Name     | Rows  | Bytes | Cost (%CPU)| Time     |
-----------------------------------------------------------------------------
|   0 | SELECT STATEMENT |          |     3 |    12 |     1   (0)| 00:00:01 |
|   1 |  INDEX FULL SCAN | INX_TEST |     3 |    12 |     1   (0)| 00:00:01 |
-----------------------------------------------------------------------------

这个时候我们来取消id列的非空约束。
SQL> alter table index_test modify(id number null);
Table altered.
然后再次查询执行计划

SQL> select *from index_test;
Execution Plan
----------------------------------------------------------
Plan hash value: 356488860
--------------------------------------------------------------------------------
| Id  | Operation         | Name       | Rows  | Bytes | Cost (%CPU)| Time     |
--------------------------------------------------------------------------------
|   0 | SELECT STATEMENT  |            |     3 |    12 |     3   (0)| 00:00:01 |
|   1 |  TABLE ACCESS FULL| INDEX_TEST |     3 |    12 |     3   (0)| 00:00:01 |
--------------------------------------------------------------------------------
来看看添加非空的过滤条件,又可以走索引了。

SQL>  select *from index_test where id is not null;

Execution Plan
----------------------------------------------------------
Plan hash value: 4273605835

-----------------------------------------------------------------------------
| Id  | Operation        | Name     | Rows  | Bytes | Cost (%CPU)| Time     |
-----------------------------------------------------------------------------
|   0 | SELECT STATEMENT |          |     3 |    12 |     1   (0)| 00:00:01 |
|*  1 |  INDEX FULL SCAN | INX_TEST |     3 |    12 |     1   (0)| 00:00:01 |
-----------------------------------------------------------------------------

Predicate Information (identified by operation id):
---------------------------------------------------

   1 - filter("ID" IS NOT NULL)

好了问题来了,索引对于空值好像总是有些特殊,我们来看看空值在索引中的一些细节。

SQL> set autot off
这个时候表index_test的索引还是唯一性索引,我们尝试插入一些值来对比一下。
如果插入name列为null,可以正常插入。
SQL> insert into index_test values(1,null);
1 row created.
如果插入id列为null,也可以正常插入。因为我们取消了id列的非空约束。

SQL> insert into index_test values(null,1);
1 row created.
插入id,name列为null,这个时候竟然可以正常插入。
SQL> insert into index_test values(null,null);
1 row created.
再尝试一条,竟然还可以正常插入。
SQL> insert into index_test values(null,null);
1 row created.
我们再来试试空串的情况,发现结果和Null是一致的,都可以插入。
SQL> insert into index_test values('','');
1 row created.
SQL> insert into index_test values('','');
1 row created.
简单分析一下索引,我们来看看空值在索引中的存储情况。
SQL> analyze index inx_test validate structure;
Index analyzed.
先来看看index_test中的数据情况,因为有些行存在空值,就把rownum也给打印出来方便查看。
SQL> select rownum,id,name from index_test;
    ROWNUM         ID NAME
---------- ---------- ------------------------------
         1          2
         2          1 a
         3          3
         4          1
         5            1
         6
         7
         8
         9
9 rows selected.
 我们来看看一共有9行数据,索引只有5行,最后4行都不在索引中。这也就基本能够说明为什么上面的查询条件中id is not null的时候有时候走索引,有时候又不走索引了。
至于为什么可以成功插入id,name列为空的行,是因为对于oracle来说,(null,null)和(null,null)是不同的,null值总是介于一种很模糊的状态。
SQL> select name,lf_rows from index_stats;
NAME                              LF_ROWS
------------------------------ ----------
INX_TEST                                5
对表Index_test中的数据进行统计,null值的统计结果是包含了Null和空串。

SQL> select id,name,count(*)from index_test group by id,name ;
        ID NAME                             COUNT(*)
---------- ------------------------------ ----------
           1                                       1
                                                   4
         1 a                                       1
         3                                         1
         2                                         1
         1                                         1
6 rows selected.
此外,在平时的工作中,如果需要对某个表创建索引,就需要考虑null值的情况,为了使得索引能够正常启用,我们需要索引列中至少有一列存在非空约束。

就如下面的情况,我们已经存在唯一性索引,但是因为b树索引不会存储null的条目,所以对表中已有的空值就需要使用全表扫描了。
SQL> set autot trace exp
SQL> select id,name from index_test;
Execution Plan
----------------------------------------------------------
Plan hash value: 356488860
--------------------------------------------------------------------------------
| Id  | Operation         | Name       | Rows  | Bytes | Cost (%CPU)| Time     |
--------------------------------------------------------------------------------
|   0 | SELECT STATEMENT  |            |     3 |    12 |     3   (0)| 00:00:01 |
|   1 |  TABLE ACCESS FULL| INDEX_TEST |     3 |    12 |     3   (0)| 00:00:01 |
--------------------------------------------------------------------------------

我们如果需要输出非空的数据,加入is not null的过滤条件,索引就能够正常启用了。
select id,name from index_test where id is not null
Execution Plan
----------------------------------------------------------
Plan hash value: 4273605835
-----------------------------------------------------------------------------
| Id  | Operation        | Name     | Rows  | Bytes | Cost (%CPU)| Time     |
-----------------------------------------------------------------------------
|   0 | SELECT STATEMENT |          |     3 |    12 |     1   (0)| 00:00:01 |
|*  1 |  INDEX FULL SCAN | INX_TEST |     3 |    12 |     1   (0)| 00:00:01 |
-----------------------------------------------------------------------------
Predicate Information (identified by operation id):
---------------------------------------------------
   1 - filter("ID" IS NOT NULL)

目录
相关文章
|
18天前
|
存储 关系型数据库 MySQL
MySQL索引简介(包含索引优化,索引失效,最左前缀简洁版)
MySQL索引简介(包含索引优化,索引失效,最左前缀简洁版)
28 0
|
3月前
|
SQL 存储 关系型数据库
MySQL索引原理(索引、约束、索引实现、索引失效、索引原则)以及SQL优化
MySQL索引原理(索引、约束、索引实现、索引失效、索引原则)以及SQL优化
135 1
|
9月前
|
SQL 存储 大数据
案例12-数据类型不一致导致索引失效
数据类型不一致导致索引失效
|
4月前
|
SQL 前端开发 关系型数据库
MYSQL基础知识之【LIKE子句的使用 ,NULL值的处理,空值的处理】
MYSQL基础知识之【LIKE子句的使用 ,NULL值的处理,空值的处理】
59 0
|
5月前
|
存储 SQL 关系型数据库
第23章(上)_索引原理之索引与约束
第23章(上)_索引原理之索引与约束
49 7
|
8月前
|
SQL 关系型数据库 MySQL
MySQL索引补充
MySQL索引补充
55 0
|
SQL JSON 数据格式
ES中如何实现空值和非空值的查询
ES中如何实现空值和非空值的查询
4145 0
|
11月前
|
存储 消息中间件 SQL
|
11月前
|
存储 关系型数据库 MySQL
|
Oracle 关系型数据库 索引
唯一约束和唯一索引区别
唯一约束和唯一索引区别
759 0

相关实验场景

更多