【问题标题】:Oracle order by not using index when index contains varchar2 column - NLS_SORT索引包含 varchar2 列时不使用索引的 Oracle 排序 - NLS_SORT
【发布时间】:2021-11-08 13:37:03
【问题描述】:

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

例如,如果我有下表:

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 子句,以便在这种情况下也省略显式排序。

编辑: 测试表中没有样本数据,我使用的是Oracle 12.1。

【问题讨论】:

  • 你在那个表中有样本数据吗?如果我在没有数据的情况下运行您的命令,则对于两个索引中的每一个,计划在两种情况下都是相同的,没有任何 sort order by 。您使用的是哪个版本的 Oracle?
  • 您是否确定确实按照所述创建了第二个索引? (在t, id 上,而不是在n, id 上,或者只是t 或只是id 错误?)
  • @MatBailie:索引创建正确。你可以看到它被用在了执行计划的最后一行。
  • @D.Mika 仔细阅读我的评论。我问它是否是按照描述创建的。您的所有计划都表明存在该名称的索引,不是它的定义。
  • @MatBailie:定义是正确的。我不止一次地仔细检查过它。 ;-) 如果它是在n, id 上创建的,它就不能被使用。

标签: oracle sql-order-by sql-execution-plan


【解决方案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

如果该值是命名语言排序,则比较由该排序定义。语言排序使用各种规则来实现一种或多种自然语言的说话者所期望的排序。这通常与这些语言的字典和电话簿中使用的顺序相同。

这和运行这个查询是一样的

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)

【讨论】:

  • 嗯,可能是服务器版本的问题。我目前使用的是相当旧的 12.1。我没有 19 可以测试。顺便说一句:你的测试会话的 NLS_SORT 是什么?
  • @D.Mika , NLS_SORT 是默认值。通常更改它不是一个好主意,除非您需要某种语言整理。在这方面,12c 和 19c 之间应该没有区别。您是否正确计算了表格的统计数据,还是更愿意使用动态抽样?
  • NLS_SORT 设置为 GERMAN 因为我默认需要德语排序。但是将其设置回BINARY 就可以了。
  • 这是关键。如果您使用 BINARY 以外的任何其他值,您将始终得到 SORT。
  • 这会有帮助吗? CREATE INDEX ix_test2 ON test(NLSSORT(t, 'NLS_SORT=German'), id);
猜你喜欢
  • 1970-01-01
  • 2015-10-19
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2012-01-16
  • 1970-01-01
  • 1970-01-01
  • 2018-01-30
相关资源
最近更新 更多