当索引包含 varchar2 列时,Oracle 通过不使用索引来排序 - NLS_SORT

Oracle order by not using index when index contains varchar2 column - NLS_SORT

我正在寻找一种方法让 Oracle 使用索引进行排序,即使它包含类型为 VARCHAR2 的列。

例如,如果我有以下 table:

CREATE TABLE test
(
   id    NUMBER,
   t     VARCHAR2(24 CHAR),
   n     NUMBER
);

具有以下索引:

CREATE INDEX ix_test1
   ON test(n, id);

CREATE INDEX ix_test2
   ON test(t, id);

然后是下面的SELECT语句

  SELECT *
    FROM test
   WHERE     n = 0
         AND id > 100
ORDER BY n, id;

有以下执行计划:

------------------------------------------------
| Id  | Operation                   | Name     |
------------------------------------------------
|   0 | SELECT STATEMENT            |          |
|   1 |  TABLE ACCESS BY INDEX ROWID| TEST     |
|*  2 |   INDEX RANGE SCAN          | IX_TEST1 |
------------------------------------------------

由于索引 IX_TEST1 中的列对应于 ORDER BY 子句中的列,因此没有排序操作。

但是,下面的语句

  SELECT *
    FROM test
   WHERE     t = 'X'
         AND id > 100
ORDER BY t, id;

有这个执行计划

-------------------------------------------------
| Id  | Operation                    | Name     |
-------------------------------------------------
|   0 | SELECT STATEMENT             |          |
|   1 |  SORT ORDER BY               |          |
|   2 |   TABLE ACCESS BY INDEX ROWID| TEST     |
|*  3 |    INDEX RANGE SCAN          | IX_TEST2 |
-------------------------------------------------

如你所见,这里有一个明确的排序。 这是合乎逻辑的,因为索引中列 T 的值的存储是二进制的(AFAIK),而排序取决于 NLS_SORT.

当前设置的值

是否有可能以不同方式定义索引或以不同方式制定 ORDER BY 子句,以便在这种情况下也省略显式排序。

编辑: 测试中没有示例数据 table 我使用的是 Oracle 12.1.

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

If the value is a named linguistic sort, then comparison is defined by this sort. A linguistic sort uses various rules to achieve ordering expected by speakers of one or more natural languages. This is usually the same ordering that is used in dictionaries and telephone directories in those languages.

也就是运行这个查询

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)