一、前言

最近看到一段话,"count(distinct 列名)若列上有索引,且有非空约束或在where子句中使用is not null,则会选择索引快速全扫描。其余情况则选择全表扫描",对其中的原理不理解,因此有了以下的实验。

二、准备工作

1. 准备t1表

SQL> create table t1 as select * from dba_objects;
SQL> insert into t1 select * from t1;
SQL> insert into t1 select * from t1;
SQL> commit;

2. 将object_name列弄出少量的空值

SQL> update t1 set object_name = null where owner = 'SCOTT';

3. 在object_name列上创建普通索引

SQL> create index idx_t1_name on t1(object_name);

4. 收集t1表和t1表上索引的统计信息

SQL> begin
   2  dbms_stats.gather_table_stats(ownname => 'SCOTT',
   3  tabname => 'T1',
   4  estimate_percent => 100,
   5  cascade => true, 
   6  no_invalidate => false,
   7  degree => 4);
   8  end;
   9  /

5. 统计t1表的总行数,object_name的行数

SQL> select count(*), count(object_name), count(distinct object_name) from t1;

  COUNT(*) COUNT(OBJECT_NAME) COUNT(DISTINCTOBJECT_NAME)
---------- ------------------ --------------------------
54068 54060 10472

至此,准备工作已经完成。t1表有54068行,object_name列有54060行,之所以这个值比总行数少,是因为count(列)的时候不统计该列上的空值。

三、查看执行计划

分别执行下面四条sql,观察执行计划
a. select count(object_name) from t1;    
b. select count(object_name) from t1 where object_name is not null;        
c. select count(distinct object_name) from t1 where object_name is not null;        
d. select count(distinct object_name) from t1;

1. 执行sql(a)

SQL> set autot on
SQL> select count(object_name) from t1;

COUNT(OBJECT_NAME)
------------------
54060 -------------------------------------------------------------------------------------
| Id | Operation | Name | Rows | Bytes | Cost (%CPU)| Time |
-------------------------------------------------------------------------------------
| 0 | SELECT STATEMENT | | 1 | 19 | 63 (0)| 00:00:01 |
| 1 | SORT AGGREGATE | | 1 | 19 | | |
| 2 | INDEX FAST FULL SCAN| IDX_T1_NAME | 54068 | 1003K| 63 (0)| 00:00:01 |
-------------------------------------------------------------------------------------

2. 执行sql(b)

SQL> select count(object_name) from t1 where object_name is not null;

COUNT(OBJECT_NAME)
------------------
54060 -------------------------------------------------------------------------------------
| Id | Operation | Name | Rows | Bytes | Cost (%CPU)| Time |
-------------------------------------------------------------------------------------
| 0 | SELECT STATEMENT | | 1 | 19 | 63 (0)| 00:00:01 |
| 1 | SORT AGGREGATE | | 1 | 19 | | |
|* 2 | INDEX FAST FULL SCAN| IDX_T1_NAME | 54060 | 1003K| 63 (0)| 00:00:01 |
-------------------------------------------------------------------------------------

可以看到sql(a)和sql(b)的执行结果和执行计划都一样,执行结果一样很好理解,count(object_name)本来就不会统计object_name为空的行,所以后面有没有where object_name is not null对结果都没有影响。
执行计划一样,也很好理解,都是走的索引快速全扫描,毕竟我只是想得到object_name有多少个值,空值我根本不管,而btree索引刚好也不存储空值,所以只需要统计object_name上的索引有多少行就行了。

3. 执行sql(c)

SQL> select count(distinct object_name) from t1 where object_name is not null;

COUNT(DISTINCTOBJECT_NAME)
--------------------------
10472 -----------------------------------------------------------------------------------------------
| Id | Operation | Name | Rows | Bytes |TempSpc| Cost (%CPU)| Time |
-----------------------------------------------------------------------------------------------
| 0 | SELECT STATEMENT | | 1 | 66 | | 220 (2)| 00:00:03 |
| 1 | SORT AGGREGATE | | 1 | 66 | | | |
| 2 | VIEW | VW_DAG_0 | 10472 | 674K| | 220 (2)| 00:00:03 |
| 3 | HASH GROUP BY | | 10472 | 194K| 1496K| 220 (2)| 00:00:03 |
|* 4 | INDEX FAST FULL SCAN| IDX_T1_NAME | 54060 | 1003K| | 63 (0)| 00:00:01 |
-----------------------------------------------------------------------------------------------

可以看到sql(c)比sql(b)多了一个distinct关键字,执行计划仍然采用的是索引快速全扫描。

4. 执行sql(d)

SQL> select count(distinct object_name) from t1;

COUNT(DISTINCTOBJECT_NAME)
--------------------------
10472 -----------------------------------------------------------------------------------------
| Id | Operation | Name | Rows | Bytes |TempSpc| Cost (%CPU)| Time |
-----------------------------------------------------------------------------------------
| 0 | SELECT STATEMENT | | 1 | 66 | | 349 (1)| 00:00:05 |
| 1 | SORT AGGREGATE | | 1 | 66 | | | |
| 2 | VIEW | VW_DAG_0 | 10472 | 674K| | 349 (1)| 00:00:05 |
| 3 | HASH GROUP BY | | 10472 | 194K| 1496K| 349 (1)| 00:00:05 |
| 4 | TABLE ACCESS FULL| T1 | 54068 | 1003K| | 192 (0)| 00:00:03 |
-----------------------------------------------------------------------------------------

可以看到sql(d)在sql(c)的基础上,删掉了where object_name is not null,执行结果没有变,但是执行计划由索引快速全扫描变成了全表扫描。照道理来讲,sql(d)依然可以使用索引的快速全扫描就可以得出结果,但是却选择了cost更大的全表扫描,这个是为什么呢?

四、问题

a. select count(object_name) from t1;    
b. select count(object_name) from t1 where object_name is not null;        
c. select count(distinct object_name) from t1 where object_name is not null;        
d. select count(distinct object_name) from t1;

sql(a)与sql(b),都走索引INDEX FAST FULL SCAN,在它的上层是SORT AGGREGATE。也就是扫个索引,统计下索引行数就行了。
sql(c),也走索引INDEX FAST FULL SCAN,它的上层是HASH GROUP BY,然后是VIEW,最后才是SORT AGGREGATE。
sql(d),走的是全表扫描,它的上层是HASH GROUP BY,然后是VIEW,最后才是SORT AGGREGATE。

count(object_name),oracle知道空值对结果没有什么影响,所以不管加不加where条件,都能走索引。
count(distinct object_name),oracle估计就懵了,它会在sql中先看看有没有过滤条件。如果将空值踢掉了,开开心心走索引,没踢掉,老老实实全表扫描。
这是为啥?

distinct关键字对执行计划的影响的更多相关文章

  1. Oracle数据库中直方图对执行计划的影响

    在Oracle数据库中,CBO会默认目标列的数据在其最小值low_value和最大值high_value之间均匀分布,并按照均匀分布原则,来计算目标列 施加查询条件后的可选择率以及结果集的cardin ...

  2. 执行计划--在存储过程中使用SET对执行计划的影响

    --如果在存储过程中定义变量,并为变量SET赋值,该变量的值无法为执行计划提供参考(即执行计划不考虑该变量),将会出现预估行数和实际行数相差过大导致执行计划不优的情况--如果在存储过程中使用SET为存 ...

  3. 执行计划--WHERE条件的先后顺序对执行计划的影响

    在编写SQL时,会建议将选择性高(过滤数据多)的条件放到WHERE条件的前面,这是为了让查询优化器优先考虑这些条件,减少生成最优(或相对最优)的执行计划的时间,但最终的执行计划生成过滤顺序还是决定这些 ...

  4. SQL Server 执行计划缓存

    标签:SQL SERVER/MSSQL SERVER/数据库/DBA/内存池/缓冲区 概述 了解执行计划对数据库性能分析很重要,其中涉及到了语句性能分析与存储,这也是写这篇文章的目的,在了解执行计划之 ...

  5. 浅析SQL SERVER执行计划中的各类怪相

    在查看执行计划或调优过程中,执行计划里面有些现象总会让人有些疑惑不解: 1:为什么同一条SQL语句有时候会走索引查找,有时候SQL脚本又不走索引查找,反而走全表扫描? 2:同一条SQL语句,查询条件的 ...

  6. MySQL统计信息以及执行计划预估方式初探

    数据库中的统计信息在不同(精确)程度上描述了表中数据的分布情况,执行计划通过统计信息获取符合查询条件的数据大小(行数),来指导执行计划的生成.在以Oracle和SQLServer为代表的商业数据库,和 ...

  7. Oracle中的执行计划

    使用autotrace sqlplus系统参数:SQL> set autotrace trace onSQL> select * from dual;DUM---XExecution Pl ...

  8. 当执行计划中出现BITMAP CONVERSION TO ROWIDS关键字时,需要注意了。

    前言 前些天优化了一些耗费buffers较多的SQL,但系统CPU降低的效果不明显,于是又拉了awr报告,查看了SQL ordered by Gets排名前列的SQL. 分析 SQL代码: selec ...

  9. SQL Server 执行计划利用统计信息对数据行的预估原理二(为什么复合索引列顺序会影响到执行计划对数据行的预估)

    本文出处:http://www.cnblogs.com/wy123/p/6008477.html 关于统计信息对数据行数做预估,之前写过对非相关列(单独或者单独的索引列)进行预估时候的算法,参考这里. ...

随机推荐

  1. VUEday01

    <!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8&quo ...

  2. Codeforces Round #590 (Div. 3)

    A. Equalize Prices Again 题目链接:https://codeforces.com/contest/1234/problem/A 题意:给你 n 个数 , 你需要改变这些数使得这 ...

  3. 《Java知识应用》Java压缩文件和解压缩

    今天通过Java实现一下:文件的压缩和解压缩. 1. 压缩 准备文件: 准备代码:(压缩) import java.io.BufferedInputStream; import java.io.Buf ...

  4. 剑指offer笔记面试题2----实现Singleton模式

    题目:设计一个类,我们只能生成该类的一个实例. 解法一:单线程解法 //缺点:多线程情况下,每个线程可能创建出不同的的Singleton实例 #include <iostream> usi ...

  5. HtEditor使用总结

    最近在公司学习到ht编辑器的使用,关于使用方法上总结了一下,避免入坑.ht是做大屏数据可视化比较好的一款软件,不过多介绍,官网上有具体使用方法和展示样例,这里我整理一下我用的最多的功能. ##1.如何 ...

  6. IT兄弟连 HTML5教程 DIV+CSS网站首页布局示例

    首页的设计直接影响网站的整体形象,虽然没有一个统一的规范,但最好将其设计为大众化的,只要信息内容能够合理地编排即可,使用户可以方便地找到需要的信息.另外,首页的高度最好不要超过三个屏幕,页面中使用的颜 ...

  7. .net core 获取树莓派的机器码,唯一ID,唯一串号

    今天在实际开发树莓派程序的时候,碰到了一个问题,需要获取到树莓派的唯一串号信息.必须在.net core的环境下: 那么如何实现呢?我们先查找树莓派的基本信息是储存在哪里的? 我们在下面的路径里找到了 ...

  8. opengl画不出直线 线段 坐标轴 却能画出其他图形的坑

    原文作者:aircraft 原文链接:https://www.cnblogs.com/DOMLX/p/12054507.html 好多次都是画坐标轴的三条直线画不出来,虽然最后都解决了  但是还是耽误 ...

  9. python3读取图像并可视化的方法(PIL/Pillow、opencv/cv2)

    原图: 使用TensorFlow做图像处理的时候,会对图像进行一些可视化的操作.下面,就来列举一些我知道的图像读取并可视化的方法. 1. Pillow模块 1.1 Pillow模块的前生 Pillow ...

  10. python3抓取淘宝评论内容

    好久没有写爬虫了,今天研究了下淘宝商品评论的内容. 一开始用最简单的方法,挂代理,加请求头,对网页请求,是抓不到数据的,在网上找了一些相关文章,也基本已经过时了,就是网站逻辑有改动,用旧的方法是抓不到 ...