热门标签 | HotTags
当前位置:  开发笔记 > 数据库 > 正文

关于分页查询和columnisnull能否走索引的分析补充

群里有朋友在谈到关于分页查询的问题,类似下面的sql想让其走索引select*from(select*fromtaorderbyobject_iddesc)whererownum这位朋友在排序列上建立了索引,但是执行计划并不走索引来避免排序,而是全表扫描然后排序后取了前几条数据,这个

群里有朋友在谈到关于分页查询的问题,类似下面的sql想让其走索引 select * from (select * from ta order by object_id desc) where rownum 这位朋友在排序列上建立了索引,但是执行计划并不走索引来避免排序,而是全表扫描然后排序后取了前几条数据,这个

群里有朋友在谈到关于分页查询的问题,类似下面的sql想让其走索引
select * from (select * from ta order by object_id desc) where rownum<10;
这位朋友在排序列上建立了索引,但是执行计划并不走索引来避免排序,而是全表扫描然后排序后取了前几条数据,这个消耗成本是很高的,我们来看看如何让这类分页查询走索引(这里的索引我们都理解为b tree索引,而不是bitmap索引)

SQL> select * from v$version;

BANNER
--------------------------------------------------------------------------------
Oracle Database 11g Enterprise Edition Release 11.2.0.1.0 - 64bit Production
PL/SQL Release 11.2.0.1.0 - Production
CORE 11.2.0.1.0 Production
TNS for Linux: Version 11.2.0.1.0 - Production
NLSRTL Version 11.2.0.1.0 – Production

SQL> create table ta as select * from dba_objects;

Table created.

SQL> create index ind_id_null on ta(object_id);

Index created.

SQL> execute dbms_stats.gather_table_stats(ownname=>'SYS',tabname=>'TA');

PL/SQL procedure successfully completed.

SQL> select * from ta where object_id is null;

no rows selected

Execution Plan
----------------------------------------------------------
Plan hash value: 824468716

--------------------------------------------------------------------------
| Id | Operation | Name | Rows | Bytes | Cost (%CPU)| Time |
--------------------------------------------------------------------------
| 0 | SELECT STATEMENT | | 1 | 101 | 292 (1)| 00:00:04 |
|* 1 | TABLE ACCESS FULL| TA | 1 | 101 | 292 (1)| 00:00:04 |
--------------------------------------------------------------------------

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

1 - filter("OBJECT_ID" IS NULL)

Statistics
----------------------------------------------------------
42 recursive calls
0 db block gets
1078 consistent gets
0 physical reads
0 redo size
1343 bytes sent via SQL*Net to client
509 bytes received via SQL*Net from client
1 SQL*Net roundtrips to/from client
1 sorts (memory)
0 sorts (disk)
0 rows processed

这里看出cbo是不会走object_id列上的索引来避免排序和全表扫描。
SQL> select * from (select * from ta order by object_id desc) where rownum<10;

9 rows selected.

Execution Plan
----------------------------------------------------------
Plan hash value: 2218702745

----------------------------------------------------------------------------------------
| Id | Operation | Name | Rows | Bytes |TempSpc| Cost (%CPU)| Time |
----------------------------------------------------------------------------------------
| 0 | SELECT STATEMENT | | 9 | 1863 | | 2025 (1)| 00:00:25 |
|* 1 | COUNT STOPKEY | | | | | | |
| 2 | VIEW | | 74906 | 14M| | 2025 (1)| 00:00:25 |
|* 3 | SORT ORDER BY STOPKEY| | 74906 | 7388K| 9M| 2025 (1)| 00:00:25 |
| 4 | TABLE ACCESS FULL | TA | 74906 | 7388K| | 293 (1)| 00:00:04 |
----------------------------------------------------------------------------------------

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

1 - filter(ROWNUM<10)
3 - filter(ROWNUM<10)

Statistics
----------------------------------------------------------
164 recursive calls
0 db block gets
1101 consistent gets
0 physical reads
0 redo size
2306 bytes sent via SQL*Net to client
520 bytes received via SQL*Net from client
2 SQL*Net roundtrips to/from client
5 sorts (memory)
0 sorts (disk)
9 rows processed

那么这里有什么问题导致cbo不去考虑索引了,其实b tree索引存储的key是不能全部为null的,由于object_id列上没有not null的约束,而cbo的执行计划不能影响sql的执行结果,索引这里cbo没办法去认为通过索引回表,然后count stopkey取前几条来完成查询

而如果我们添加not null约束,或者在内部的查询结果中添加一个object_id is not null约束的过滤条件,那么此时cbo就知道了能够通过现在有的b tree索引回表的方式来完成查询
SQL> select * from (select * from ta where object_id is not null order by object_id desc) where rownum<10;

9 rows selected.

Execution Plan
----------------------------------------------------------
Plan hash value: 679434780

---------------------------------------------------------------------------------------------
| Id | Operation | Name | Rows | Bytes | Cost (%CPU)| Time |
---------------------------------------------------------------------------------------------
| 0 | SELECT STATEMENT | | 9 | 1863 | 3 (0)| 00:00:01 |
|* 1 | COUNT STOPKEY | | | | | |
| 2 | VIEW | | 9 | 1863 | 3 (0)| 00:00:01 |
| 3 | TABLE ACCESS BY INDEX ROWID| TA | 74906 | 7388K| 3 (0)| 00:00:01 |
|* 4 | INDEX FULL SCAN DESCENDING| IND_ID_NULL | 9 | | 2 (0)| 00:00:01 |
---------------------------------------------------------------------------------------------

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

1 - filter(ROWNUM<10)
4 - filter("OBJECT_ID" IS NOT NULL)

Statistics
----------------------------------------------------------
1 recursive calls
0 db block gets
7 consistent gets
0 physical reads
0 redo size
2306 bytes sent via SQL*Net to client
520 bytes received via SQL*Net from client
2 SQL*Net roundtrips to/from client
0 sorts (memory)
0 sorts (disk)
9 rows processed

那么如果业务中有object_id等于null的值,那么这个查询可能会影响结果,而且oracle对于null值的排序正是认为null是最大值的。

那么这个分页查询如果没有not null约束或者过滤条件,就不能走索引了吗,其实不然,小鱼之前处理过下面的类似的case,是对单个的列进行is null的谓词过滤

SQL> create index ind_id_multi_null on ta(1,object_id);

Index created.

SQL> select /*+index(ta,ind_id_multi_null)*/* from ta where object_id is null;

no rows selected

Execution Plan
----------------------------------------------------------
Plan hash value: 849692407

-------------------------------------------------------------------------------------------------
| Id | Operation | Name | Rows | Bytes | Cost (%CPU)| Time |
-------------------------------------------------------------------------------------------------
| 0 | SELECT STATEMENT | | 1 | 101 | 199 (1)| 00:00:03 |
| 1 | TABLE ACCESS BY INDEX ROWID| TA | 1 | 101 | 199 (1)| 00:00:03 |
|* 2 | INDEX FULL SCAN | IND_ID_MULTI_NULL | 1 | | 199 (1)| 00:00:03 |
-------------------------------------------------------------------------------------------------

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

2 - access("OBJECT_ID" IS NULL)
filter("OBJECT_ID" IS NULL)

Statistics
----------------------------------------------------------
1 recursive calls
0 db block gets
198 consistent gets
197 physical reads
0 redo size
1343 bytes sent via SQL*Net to client
509 bytes received via SQL*Net from client
1 SQL*Net roundtrips to/from client
0 sorts (memory)
0 sorts (disk)
0 rows processed

这个上面走的全索引扫描然后回表的方式来过滤的object_id is null的,这个是因为把索引的前导列弄错了导致的,如果我们建立下面的索引,把过滤列放在索引的前导列上
SQL> create index ind_id_nulti_null_bak on ta(object_id,1);

Index created.

SQL> select * from ta where object_id is null;

no rows selected

Execution Plan
----------------------------------------------------------
Plan hash value: 2610853831

-----------------------------------------------------------------------------------------------------
| Id | Operation | Name | Rows | Bytes | Cost (%CPU)| Time |
-----------------------------------------------------------------------------------------------------
| 0 | SELECT STATEMENT | | 1 | 101 | 1 (0)| 00:00:01 |
| 1 | TABLE ACCESS BY INDEX ROWID| TA | 1 | 101 | 1 (0)| 00:00:01 |
|* 2 | INDEX RANGE SCAN | IND_ID_NULTI_NULL_BAK | 1 | | 1 (0)| 00:00:01 |
-----------------------------------------------------------------------------------------------------

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

2 - access("OBJECT_ID" IS NULL)

Statistics
----------------------------------------------------------
1 recursive calls
0 db block gets
2 consistent gets
0 physical reads
0 redo size
1343 bytes sent via SQL*Net to client
509 bytes received via SQL*Net from client
1 SQL*Net roundtrips to/from client
0 sorts (memory)
0 sorts (disk)
0 rows processed

这个已经可以走这个复合索引的索引范围扫描了,那么最开始那个分页查询同样可以走全索引扫描,这个扫描只会扫描rownum分页数目的key然后回表,这个绝对比大表的全表扫描然后排序的成本要低很多。
SQL> select * from (select * from ta order by object_id desc) where rownum<10;

9 rows selected.

Execution Plan
----------------------------------------------------------
Plan hash value: 2361786208

-------------------------------------------------------------------------------------------------------
| Id | Operation | Name | Rows | Bytes | Cost (%CPU)| Time |
-------------------------------------------------------------------------------------------------------
| 0 | SELECT STATEMENT | | 9 | 1863 | 3 (0)| 00:00:01 |
|* 1 | COUNT STOPKEY | | | | | |
| 2 | VIEW | | 9 | 1863 | 3 (0)| 00:00:01 |
| 3 | TABLE ACCESS BY INDEX ROWID| TA | 74906 | 7388K| 3 (0)| 00:00:01 |
| 4 | INDEX FULL SCAN DESCENDING| IND_ID_NULTI_NULL_BAK | 9 | | 2 (0)| 00:00:01 |
-------------------------------------------------------------------------------------------------------

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

1 - filter(ROWNUM<10)

Statistics
----------------------------------------------------------
1 recursive calls
0 db block gets
7 consistent gets
0 physical reads
0 redo size
2306 bytes sent via SQL*Net to client
520 bytes received via SQL*Net from client
2 SQL*Net roundtrips to/from client
0 sorts (memory)
0 sorts (disk)
9 rows processed

至此最开始那个分页查询我们已经优化完毕了。

这里有两点需要注意的地方:
1对于object_id is null这类过滤条件并不是不能走索引范围扫描的,我们只需要建立该列为前导列的复合索引就有可能让cbo考虑该索引
2还有就是分页查询要利用索引完成索引全扫描rownum分页数据的key然后回表的方式,一定要考虑该列是否有not null的约束或者过滤条件,这个可能造成部分分页查询无法通过索引完成。

推荐阅读
  • PHP 实现实时汇率查询接口
    本文介绍如何使用PHP构建一个实时汇率查询接口,解决网站因数据源限制而无法获取最新汇率的问题。文章将详细讲解从选择合适的数据源到实现接口的具体步骤。 ... [详细]
  • Unix与Linux的起源与发展
    本文详细探讨了Unix与Linux的操作系统起源,从自由软件运动的兴起,到Linux内核的诞生,全面解析了这两款操作系统的发展历程及其对现代计算技术的影响。 ... [详细]
  • 本文探讨了为何在Linux系统上进行项目开发的重要性,并详细介绍了如何在Ubuntu系统上安装PyCharm这一流行的Python集成开发环境(IDE)。通过本文,您将了解在Linux环境下进行Python开发的优势及具体安装步骤。 ... [详细]
  • 成为一名高效的Java架构师不仅需要掌握高级Java编程技巧,还需深入理解JVM的工作原理及其优化方法。此外,对池技术(包括对象池、连接池和线程池)的应用、多线程处理、集合对象的内部机制、以及常用的数据结构和算法的精通也是必不可少的。同时,熟悉Linux操作系统、TCP/IP协议栈、HTTP协议等基础知识,对于构建高效稳定的系统同样重要。 ... [详细]
  • 本文介绍了如何在ARM架构的裸机环境中通过C语言编程点亮LED灯。主要包括初始化栈指针、关闭看门狗以及编写控制LED灯状态的C代码等关键步骤。 ... [详细]
  • 本文介绍了在Linux系统中如何使用不同的命令和工具来查看和检查端口状态,包括有权限和无权限情况下的操作方法。 ... [详细]
  • 本文介绍如何通过配置Linux服务器作为路由器来实现两个不同网段(192.168.1.0/24 和 192.168.2.0/24)之间的互联互通。 ... [详细]
  • Minetest 0.4.9 开源游戏在 Ubuntu 下通过 PPA 安装指南
    本文介绍了如何在 Ubuntu 系统上安装最新版本的 Minetest 0.4.9,包括添加 PPA、更新软件包列表以及安装过程,适合所有 Ubuntu 及其衍生系统的用户。 ... [详细]
  • 四月个人任务:Linux基础操作与网络管理
    本文介绍了两项主要任务:编写一个脚本来检测192.168.1.0/24子网中当前在线的IP地址,以及如何在Linux系统中挂载Windows网络共享目录。通过具体步骤和代码示例,帮助读者理解和掌握相关技能。 ... [详细]
  • Linux中使用Shell脚本获取网络配置信息
    本文提供了一组Shell命令,用于在Linux系统中提取IP地址、子网掩码、MAC地址及默认网关等关键网络配置信息。 ... [详细]
  • VS Code 中 .vscode 文件夹配置详解
    本文介绍了 VS Code 中 .vscode 文件夹下的配置文件及其作用,包括常用的预定义变量和三个关键配置文件:launch.json、tasks.json 和 c_cpp_properties.json。 ... [详细]
  • 去控件化在线文档处理,推动高效信创办公环境
    探讨在线文档处理去控件化的趋势及其对构建高效信创办公生态的影响。 ... [详细]
  • 本文详细介绍了如何在Ubuntu操作系统中安装和配置Maven,包括必要的环境准备和验证步骤。 ... [详细]
  • Mac环境下Java与Ant自动化构建环境搭建指南
    本文详细介绍了如何在Mac操作系统上为测试工程师搭建Java和Ant开发环境,包括环境变量配置等关键步骤。 ... [详细]
  • 本文详细介绍了Linux系统中的三个主要分区:启动分区、交换分区和根分区的功能与特点,以及常见的文件类型、文件搜索方法和常用符号的使用。此外,还深入解析了mkdir和rm命令的具体应用。 ... [详细]
author-avatar
超级a9厑厑
这个家伙很懒,什么也没留下!
PHP1.CN | 中国最专业的PHP中文社区 | DevBox开发工具箱 | json解析格式化 |PHP资讯 | PHP教程 | 数据库技术 | 服务器技术 | 前端开发技术 | PHP框架 | 开发工具 | 在线工具
Copyright © 1998 - 2020 PHP1.CN. All Rights Reserved | 京公网安备 11010802041100号 | 京ICP备19059560号-4 | PHP1.CN 第一PHP社区 版权所有