Я был довольно удивлен, обнаружив, что индекс может помочь (протестировано на Oracle 12.2).
Тестовая таблица с 1М строк:
create table demo_sort
( num_indexed integer not null
, num_unindexed integer not null
, str_indexed varchar2(50) not null
, str_unindexed varchar2(50) not null
, date_indexed date not null
, date_unindexed date );
insert /*+ append */ into demo_sort
( num_indexed
, num_unindexed
, str_indexed
, str_unindexed
, date_indexed
, date_unindexed )
select num, num
, str, str
, dt, dt
from ( select round(dbms_random.value() * 1e5) as num
, dbms_random.string('x',50) as str
, date '2010-01-01' + numtodsinterval(dbms_random.value() * 1e5, 'HOUR') as dt
from dual connect by rownum <= 1e6 );
create index demo_sort_num_ix on demo_sort(num_indexed);
create index demo_sort_str_ix on demo_sort(str_indexed);
create index demo_sort_date_ix on demo_sort(date_indexed);
call dbms_stats.gather_table_stats(user, 'demo_sort');
Тестирование производительности с использованием SQL * Plus AUTOTRACE (показывает лучшее время для трех запусков).План выполнения по умолчанию и результирующая производительность были одинаковыми:
SQL> set autotrace trace exp stat
SQL> set timing on
SQL> select * from demo_sort order by str_unindexed;
1000000 rows selected.
Elapsed: 00:00:18.92
Execution Plan
----------------------------------------------------------
Plan hash value: 3213928767
----------------------------------------------------------------------------------------
| Id | Operation | Name | Rows | Bytes |TempSpc| Cost (%CPU)| Time |
----------------------------------------------------------------------------------------
| 0 | SELECT STATEMENT | | 1000K| 122M| | 33771 (1)| 00:00:02 |
| 1 | SORT ORDER BY | | 1000K| 122M| 139M| 33771 (1)| 00:00:02 |
| 2 | TABLE ACCESS FULL| DEMO_SORT | 1000K| 122M| | 5233 (1)| 00:00:01 |
----------------------------------------------------------------------------------------
Statistics
----------------------------------------------------------
135 recursive calls
3 db block gets
18627 consistent gets
17242 physical reads
0 redo size
141711618 bytes sent via SQL*Net to client
733933 bytes received via SQL*Net from client
66668 SQL*Net roundtrips to/from client
0 sorts (memory)
1 sorts (disk)
1000000 rows processed
SQL> select * from demo_sort order by str_indexed;
1000000 rows selected.
Elapsed: 00:00:19.06
Execution Plan
----------------------------------------------------------
Plan hash value: 3213928767
----------------------------------------------------------------------------------------
| Id | Operation | Name | Rows | Bytes |TempSpc| Cost (%CPU)| Time |
----------------------------------------------------------------------------------------
| 0 | SELECT STATEMENT | | 1000K| 122M| | 33771 (1)| 00:00:02 |
| 1 | SORT ORDER BY | | 1000K| 122M| 139M| 33771 (1)| 00:00:02 |
| 2 | TABLE ACCESS FULL| DEMO_SORT | 1000K| 122M| | 5233 (1)| 00:00:01 |
----------------------------------------------------------------------------------------
Statistics
----------------------------------------------------------
136 recursive calls
3 db block gets
18627 consistent gets
17242 physical reads
0 redo size
141711618 bytes sent via SQL*Net to client
733933 bytes received via SQL*Net from client
66668 SQL*Net roundtrips to/from client
0 sorts (memory)
1 sorts (disk)
1000000 rows processed
Однако, если намекнуть на использование индекса, производительность примерно на 40% выше (хотя мы по-прежнему говорим только о 11 секундах вместо 19 для сортировки1M строк - и это на моем ноутбуке, а не на сервере базы данных производственного уровня):
SQL> select /*+ index(d) */ * from demo_sort d order by str_indexed;
1000000 rows selected.
Elapsed: 00:00:11.04
Execution Plan
----------------------------------------------------------
Plan hash value: 2822485249
------------------------------------------------------------------------------------------------
| Id | Operation | Name | Rows | Bytes | Cost (%CPU)| Time |
------------------------------------------------------------------------------------------------
| 0 | SELECT STATEMENT | | 1000K| 122M| 1009K (1)| 00:00:40 |
| 1 | TABLE ACCESS BY INDEX ROWID| DEMO_SORT | 1000K| 122M| 1009K (1)| 00:00:40 |
| 2 | INDEX FULL SCAN | DEMO_SORT_STR_IX | 1000K| | 7770 (1)| 00:00:01 |
------------------------------------------------------------------------------------------------
Statistics
----------------------------------------------------------
0 recursive calls
0 db block gets
1074381 consistent gets
0 physical reads
0 redo size
141711618 bytes sent via SQL*Net to client
733933 bytes received via SQL*Net from client
66668 SQL*Net roundtrips to/from client
0 sorts (memory)
0 sorts (disk)
1000000 rows processed
Таким образом, избегая сортировки, компенсируемой необходимостью делать в 57 раз больше операций ввода-вывода, что приводит к скромному улучшению,(Похоже, что оптимизатор был так же удивлен, как и я, - обратите внимание, что вычисленная стоимость для индексного подхода в 30 раз выше).
Подводя итог, можно сказать, что два подхода:
Считать все строки таблицы за один проход и выполнить сортировку результатов.
Считать все строки из индекса за один проход и для каждогозапись индекса извлекает соответствующую строку из таблицы.
Первый подход использует меньше операций ввода-вывода, поскольку он может использовать многоблочное чтение и, возможно, прямой путь для сканирования таблицы водин выстрел.
Второй подход выполняет поиск по миллиону таблиц в дополнение к сканированию всего индекса один раз.Поскольку данные таблицы физически хранятся и кешируются в блоках, многие из этих блоков будут поражены многократно, поэтому такой способ доступа ко всей таблице гораздо менее эффективен с точки зрения ввода-вывода, даже с учетом кэширования.Так получилось, что в этом случае все еще стоило избежать подобной сортировки.