Подтвердить что ты не робот

Выбор MIN и MAX Из таблицы происходит медленнее, чем ожидалось

У меня есть таблица MYTABLE с столбцом даты SDATE, который является первичным ключом таблицы и имеет уникальный индекс на нем.

Когда я запускаю этот запрос:

SELECT MIN(SDATE) FROM MYTABLE

он дает ответ мгновенно. То же самое происходит и для:

SELECT MAX(SDATE) FROM MYTABLE

Но если я запрошу оба вместе:

SELECT MIN(SDATE), MAX(SDATE) FROM MYTABLE

для выполнения требуется гораздо больше времени. Я проанализировал планы и обнаружил, что при запросе одного из min или max он использует INDEX FULL SCAN (MIN/MAX), но когда оба запрашиваются одновременно, он выполняет FULL TABLE SCAN.

почему?

Данные теста:

версия 11g

create table MYTABLE
(
  SDATE  DATE not null,
  CELL   VARCHAR2(10),
  data NUMBER
)
tablespace CHIPS
  pctfree 10
  pctused 40
  initrans 1
  maxtrans 255
  storage
  (
    initial 64K
    minextents 1
    maxextents unlimited
  );

alter table MYTABLE
  add constraint PK_SDATE primary key (SDATE)
  using index 
  tablespace SYSTEM
  pctfree 10
  initrans 2
  maxtrans 255
  storage
  (
    initial 64K
    minextents 1
    maxextents unlimited
  );

Таблица загрузки:

declare 
  i integer;
begin
  for i in 0 .. 100000 loop
     insert into MYTABLE(sdate, cell, data)
     values(sysdate - i/24, 'T' || i, i);     
     commit;
  end loop;
end;

Соберите статистику:

begin
  dbms_stats.gather_table_stats(tabname => 'MYTABLE', ownname => 'SYS');
end;

плана1:

enter image description here

Plan2:

enter image description here

4b9b3361

Ответ 1

Полное сканирование индекса может посещать только одну сторону индекса. Когда вы делаете

SELECT MIN(SDATE), MAX(SDATE) FROM MYTABLE

вы просите посетить 2 стороны. Поэтому, если вы хотите как минимальное, так и максимальное значение столбца, полное сканирование индекса не является жизнеспособным.

Более подробный анализ вы можете найти здесь.

Ответ 2

Планы объяснения различны: один MIN или MAX создаст INDEX FULL SCAN (MIN/MAX), тогда как при наличии двух вы получите INDEX FULL SCAN или FAST FULL INDEX SCAN.

Чтобы понять разницу, нам нужно искать описание FULL INDEX SCAN:

При полном сканировании индекса база данных считывает весь индекс в порядке.

Другими словами, если индекс находится в поле VARCHAR2, Oracle будет извлекать первый блок индекса, который будет содержать, например, все записи, начинающиеся с буквы "A", и будет читать блок за блоком всех записей в алфавитном порядке до последней записи (от "А" до "Z" ). Oracle может обрабатывать таким образом, потому что записи сортируются в двоичном индексе дерева.

Когда вы видите INDEX FULL SCAN (MIN/MAX) в плане объяснения, это результат оптимизации, которая использует тот факт, что, поскольку записи сортируются, вы можете остановиться, прочитав первый, если вас интересует только MIN. Если вас интересует только MAX, Oracle может использовать один и тот же путь доступа, но на этот раз, начиная с последней записи и считая назад от "Z" до "A".

В настоящее время a FULL INDEX SCAN имеет только одно направление (вперед или назад) и не может начинаться с обоих концов одновременно, поэтому, когда вы запрашиваете как минимум, так и максимальный, вы получаете менее эффективный доступ метод.

Как было предложено в других ответах, если запрос требует критической эффективности, вы можете запустить свою собственную оптимизацию, выполнив поиск min и max в двух разных запросах.

Ответ 3

Не пытайтесь выбрать оба края индекса в одном запросе, Доступ к запросу по-другому выглядит следующим образом:

select max_date, min_date
from (select max(sdate) max_date from mytable),
       (select min(sdate) min_date from mytable)

приведет к тому, что оптимизатор получит доступ к индексу в INDEX_FULL_SCAN (MIN/MAX) в вложенных циклах (в нашем случае два раза).

enter image description here

Ответ 4

Я должен сказать, что я не вижу такого же поведения в 11.2

Если я настрою тестовый пример следующим образом и обновлен от 10k до 1m строк в ответ на комментарий Vincent

set linesize 130
set pagesize 0
create table mytable ( sdate date );

Table created.

insert into mytable
 select sysdate - level
   from dual
connect by level <= 1000000;
commit;

1000000 rows created.


Commit complete.

alter table mytable add constraint pk_mytable primary key ( sdate ) using index;

Table altered.

begin
dbms_stats.gather_table_stats( user, 'MYTABLE' 
                             , estimate_percent => 100
                             , cascade => true
                               );
end;
/

PL/SQL procedure successfully completed.

Затем, выполняя ваши запросы, я получаю почти одинаковые планы объяснения (обратите внимание на разные типы INDEX FULL SCAN)

explain plan for select min(sdate) from mytable;

Explained.

select * from table(dbms_xplan.display);
Plan hash value: 3877058912

-----------------------------------------------------------------------------------------
| Id  | Operation          | Name   | Rows  | Bytes | Cost (%CPU)| Time |
-----------------------------------------------------------------------------------------
|   0 | SELECT STATEMENT       |        |     1 |     8 |     1   (0)| 00:00:01 |
|   1 |  SORT AGGREGATE        |        |     1 |     8 |        |      |
|   2 |   INDEX FULL SCAN (MIN/MAX)| PK_MYTABLE |     1 |     8 |     1   (0)| 00:00:01 |
-----------------------------------------------------------------------------------------

9 rows selected.

explain plan for select min(sdate), max(sdate) from mytable;

Explained.

select * from table(dbms_xplan.display);
Plan hash value: 3812733167

-------------------------------------------------------------------------------
| Id  | Operation    | Name       | Rows  | Bytes | Cost (%CPU)| Time     |
-------------------------------------------------------------------------------
|   0 | SELECT STATEMENT |        |     1 |     8 |   252   (0)| 00:00:04 |
|   1 |  SORT AGGREGATE  |        |     1 |     8 |        |          |
|   2 |   INDEX FULL SCAN| PK_MYTABLE |  1000K|  7812K|   252   (0)| 00:00:04 |
-------------------------------------------------------------------------------

9 rows selected.

Чтобы процитировать мой предыдущий ответ:

Двумя наиболее распространенными причинами для запроса, не использующего индексы, являются:

  • Быстрее выполнить полное сканирование таблицы.
  • Плохая статистика.

Если у вас нет чего-то, что вы не публикуете в вопросе, мой немедленный ответ будет заключаться в том, что вы не собрали статистику по этой таблице, вы не собрали их с достаточно высоким процентом оценки или используете analyze, что не поможет оптимизатору затрат, в отличие от dbms_stats.gather_table_stats.

Чтобы процитировать документацию на analyze:

Для сбора большинства статистических данных используйте пакет DBMS_STATS, который позволяет собирать статистику параллельно, собирать глобальные статистика для секционированных объектов и точную настройку вашей статистики коллекции другими способами. См. Oracle Database PL/SQL Packages и Типы Ссылки для получения дополнительной информации о пакете DBMS_STATS.

Используйте инструкцию ANALYZE (а не DBMS_STATS) для статистики коллекция не связана с оптимизатором затрат: