0

0

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

php中文网

php中文网

发布时间:2016-06-07 16:38:09

|

1360人浏览过

|

来源于php中文网

原创

群里有朋友在谈到关于分页查询的问题,类似下面的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 这位朋友在排序列上建立了索引,但是执行计划并不走索引来避免排序,而是全表扫描然后排序后取了前几条数据,这个消耗成本是很高的,我们来看看如何让这类分页查询走索引(这里的索引我们都理解为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

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 3 - filter(ROWNUM

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

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 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是最大值的。

北极象沉浸式AI翻译
北极象沉浸式AI翻译

免费的北极象沉浸式AI翻译 - 带您走进沉浸式AI的双语对照体验

下载

那么这个分页查询如果没有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

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

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 WebSocket 实时通信开发
PHP WebSocket 实时通信开发

本专题系统讲解 PHP 在实时通信与长连接场景中的应用实践,涵盖 WebSocket 协议原理、服务端连接管理、消息推送机制、心跳检测、断线重连以及与前端的实时交互实现。通过聊天系统、实时通知等案例,帮助开发者掌握 使用 PHP 构建实时通信与推送服务的完整开发流程,适用于即时消息与高互动性应用场景。

2

2026.01.19

微信聊天记录删除恢复导出教程汇总
微信聊天记录删除恢复导出教程汇总

本专题整合了微信聊天记录相关教程大全,阅读专题下面的文章了解更多详细内容。

39

2026.01.18

高德地图升级方法汇总
高德地图升级方法汇总

本专题整合了高德地图升级相关教程,阅读专题下面的文章了解更多详细内容。

100

2026.01.16

全民K歌得高分教程大全
全民K歌得高分教程大全

本专题整合了全民K歌得高分技巧汇总,阅读专题下面的文章了解更多详细内容。

148

2026.01.16

C++ 单元测试与代码质量保障
C++ 单元测试与代码质量保障

本专题系统讲解 C++ 在单元测试与代码质量保障方面的实战方法,包括测试驱动开发理念、Google Test/Google Mock 的使用、测试用例设计、边界条件验证、持续集成中的自动化测试流程,以及常见代码质量问题的发现与修复。通过工程化示例,帮助开发者建立 可测试、可维护、高质量的 C++ 项目体系。

57

2026.01.16

java数据库连接教程大全
java数据库连接教程大全

本专题整合了java数据库连接相关教程,阅读专题下面的文章了解更多详细内容。

42

2026.01.15

Java音频处理教程汇总
Java音频处理教程汇总

本专题整合了java音频处理教程大全,阅读专题下面的文章了解更多详细内容。

19

2026.01.15

windows查看wifi密码教程大全
windows查看wifi密码教程大全

本专题整合了windows查看wifi密码教程大全,阅读专题下面的文章了解更多详细内容。

107

2026.01.15

浏览器缓存清理方法汇总
浏览器缓存清理方法汇总

本专题整合了浏览器缓存清理教程汇总,阅读专题下面的文章了解更多详细内容。

45

2026.01.15

热门下载

更多
网站特效
/
网站源码
/
网站素材
/
前端模板

精品课程

更多
相关推荐
/
热门推荐
/
最新课程
CSS3 教程
CSS3 教程

共18课时 | 4.7万人学习

PostgreSQL 教程
PostgreSQL 教程

共48课时 | 7.4万人学习

Django 教程
Django 教程

共28课时 | 3.2万人学习

关于我们 免责申明 举报中心 意见反馈 讲师合作 广告合作 最新更新
php中文网:公益在线php培训,帮助PHP学习者快速成长!
关注服务号 技术交流群
PHP中文网订阅号
每天精选资源文章推送

Copyright 2014-2026 https://www.php.cn/ All Rights Reserved | php.cn | 湘ICP备2023035733号