Oracle 将始终使用最便宜的方法对 SQL 结果集进行排序,如果 CBO 消耗的资源少于排序,则 CBO 将使用索引。
我将使用 Oracle 19c 和默认 NLS_SORT 重现您的案例
SQL> select version from v$instance ;
VERSION
-----------------
19.0.0.0.0
SQL> CREATE TABLE test
(
id NUMBER,
t VARCHAR2(24 CHAR),
n NUMBER
); 2 3 4 5 6
Table created.
SQL> CREATE INDEX ix_test1
ON test(n, id);
Index created.
SQL> CREATE INDEX ix_test2
ON test(t, id); 2
Index created.
SQL> set lines 200 pages 0
SQL> set autotrace traceonly explain
SQL> SELECT *
FROM test
WHERE n = 0
AND id > 100
ORDER BY n, id; 2 3 4 5
Execution Plan
----------------------------------------------------------
Plan hash value: 1505378640
----------------------------------------------------------------------------------------
| Id | Operation | Name | Rows | Bytes | Cost (%CPU)| Time |
----------------------------------------------------------------------------------------
| 0 | SELECT STATEMENT | | 1 | 76 | 0 (0)| 00:00:01 |
| 1 | TABLE ACCESS BY INDEX ROWID| TEST | 1 | 76 | 0 (0)| 00:00:01 |
|* 2 | INDEX RANGE SCAN | IX_TEST1 | 1 | | 0 (0)| 00:00:01 |
----------------------------------------------------------------------------------------
Predicate Information (identified by operation id):
---------------------------------------------------
2 - access("N"=0 AND "ID">100 AND "ID" IS NOT NULL)
Note
-----
- dynamic statistics used: dynamic sampling (level=2)
SQL> SELECT *
FROM test
WHERE t = 'X'
AND id > 100
ORDER BY t, id; 2 3 4 5
Execution Plan
----------------------------------------------------------
Plan hash value: 3173568990
----------------------------------------------------------------------------------------
| Id | Operation | Name | Rows | Bytes | Cost (%CPU)| Time |
----------------------------------------------------------------------------------------
| 0 | SELECT STATEMENT | | 1 | 76 | 0 (0)| 00:00:01 |
| 1 | TABLE ACCESS BY INDEX ROWID| TEST | 1 | 76 | 0 (0)| 00:00:01 |
|* 2 | INDEX RANGE SCAN | IX_TEST2 | 1 | | 0 (0)| 00:00:01 |
----------------------------------------------------------------------------------------
Predicate Information (identified by operation id):
---------------------------------------------------
2 - access("T"='X' AND "ID">100 AND "ID" IS NOT NULL)
Note
-----
- dynamic statistics used: dynamic sampling (level=2)
在这两种情况下,我都没有看到 CBO 执行任何 sort order by 操作。现在让我们尝试添加示例数据
SQL> set autotrace off
SQL>
SQL> insert into test values ( 101 , 'A' , 0 );
1 row created.
SQL> insert into test values ( 102 , 'B' , 1 );
1 row created.
SQL> insert into test values ( 103 , 'X' , 0 ) ;
1 row created.
SQL> insert into test values ( 104 , 'X' , 1 ) ;
1 row created.
SQL> commit ;
Commit complete.
SQL> exec dbms_stats.gather_table_stats( ownname => 'MYSCHEMA' , tabname => 'TEST' ) ;
PL/SQL procedure successfully completed.
SQL> set autotrace traceonly explain
SQL> SELECT *
FROM test
WHERE t = 'X'
AND id > 100
ORDER BY t, id; 2 3 4 5
Execution Plan
----------------------------------------------------------
Plan hash value: 3173568990
----------------------------------------------------------------------------------------
| Id | Operation | Name | Rows | Bytes | Cost (%CPU)| Time |
----------------------------------------------------------------------------------------
| 0 | SELECT STATEMENT | | 2 | 18 | 2 (0)| 00:00:01 |
| 1 | TABLE ACCESS BY INDEX ROWID| TEST | 2 | 18 | 2 (0)| 00:00:01 |
|* 2 | INDEX RANGE SCAN | IX_TEST2 | 2 | | 1 (0)| 00:00:01 |
----------------------------------------------------------------------------------------
Predicate Information (identified by operation id):
---------------------------------------------------
2 - access("T"='X' AND "ID">100 AND "ID" IS NOT NULL)
如您所见,没有这样的sort order by,因为Oracle 不需要对结果进行排序,因为它是由索引继承的。如果您在收到此sort order by 时可以提供您的示例数据,我也许可以改进答案。在正常情况下,它不会发生,因为已经排序。
更新
将NLS_SORT 更改为一种语言(在本例中为GERMAN)将产生排序操作,因为这是语言排序的结果。
SQL> ALTER SESSION SET NLS_SORT='GERMAN' ;
Session altered.
SQL> set autotrace traceonly
SQL> SELECT *
FROM test
WHERE t = 'X'
2 3 4 AND id > 100
5 ORDER BY t, id;
Execution Plan
----------------------------------------------------------
Plan hash value: 3867551970
-------------------------------------------------------------------------------------------------
| Id | Operation | Name | Rows | Bytes | Cost (%CPU)| Time |
-------------------------------------------------------------------------------------------------
| 0 | SELECT STATEMENT | | 2 | 18 | 3 (34)| 00:00:01 |
| 1 | SORT ORDER BY | | 2 | 18 | 3 (34)| 00:00:01 |
| 2 | TABLE ACCESS BY INDEX ROWID BATCHED| TEST | 2 | 18 | 2 (0)| 00:00:01 |
|* 3 | INDEX RANGE SCAN | IX_TEST2 | 2 | | 1 (0)| 00:00:01 |
-------------------------------------------------------------------------------------------------
Predicate Information (identified by operation id):
---------------------------------------------------
3 - access("T"='X' AND "ID">100 AND "ID" IS NOT NULL)
Statistics
----------------------------------------------------------
1 recursive calls
0 db block gets
2 consistent gets
2 physical reads
0 redo size
751 bytes sent via SQL*Net to client
434 bytes received via SQL*Net from client
2 SQL*Net roundtrips to/from client
1 sorts (memory)
0 sorts (disk)
2 rows processed
如图所示,将NLS_SORT 的行为更改为语言值,会在执行计划中引入sort order by。
如果该值是命名语言排序,则比较由该排序定义。语言排序使用各种规则来实现一种或多种自然语言的说话者所期望的排序。这通常与这些语言的字典和电话簿中使用的顺序相同。
这和运行这个查询是一样的
SELECT *
FROM test
WHERE t = 'X'
AND id > 100
ORDER BY nlssort(t,'NLS_SORT=XGerman'), id
/
但如果你想避免sort order by ,那么就这样做
SQL> create index ix_test2 on test ( id , nlssort(t,'NLS_SORT=XGerman') ) ;
Index created.
SQL> SELECT *
FROM test
WHERE t = 'X'
AND id > 100
ORDER BY t, id 2 3 4 5 ;
Execution Plan
----------------------------------------------------------
Plan hash value: 3173568990
----------------------------------------------------------------------------------------
| Id | Operation | Name | Rows | Bytes | Cost (%CPU)| Time |
----------------------------------------------------------------------------------------
| 0 | SELECT STATEMENT | | 2 | 18 | 2 (0)| 00:00:01 |
|* 1 | TABLE ACCESS BY INDEX ROWID| TEST | 2 | 18 | 2 (0)| 00:00:01 |
|* 2 | INDEX RANGE SCAN | IX_TEST2 | 4 | | 1 (0)| 00:00:01 |
----------------------------------------------------------------------------------------
Predicate Information (identified by operation id):
---------------------------------------------------
1 - filter("T"='X')
2 - access("ID">100 AND "ID" IS NOT NULL)