SQL: как я могу ускорить этот запрос - PullRequest
1 голос
/ 15 февраля 2010

Вот ситуация. У меня есть одна таблица, которая содержит записи, основанные на записях во многих разных таблицах (t1 ниже). t2 - это одна из таблиц, из которой извлекается информация в t1.

t1
  table_oid --which table id is a FK to
  id        --fk to other table
  store_num --field

t2
  t2_id

Вот что мне нужно найти: мне нужен самый большой t2_id, где store_num не равно нулю в соответствующей записи t1. Вот запрос, который я написал:

select max(id) from t1
join t2 on t2.t2_id = t1.id
where store_num is not null
and table_oid = 1234;

Однако это занимает довольно много времени. Я думаю, что это должен быть быстрый запрос. все _ids имеют индексы для них. (t1.id/t1.table_oid, t2.t2_id). Подавляющее большинство записей в t1 имеют store_num.

Мысленно, я получал бы t2_ids в порядке убывания, чем один за другим, пробовал бы их против t1, пока не нашел первый, который имел store_num.

select t2_id from t2 order by t2_id desc;

имеет стоимость объяснения 25612

select t1.* from t1 where table_oid = 1234
and id in (select max(t2_id) from t2);

имеет стоимость объяснения 8 *. 1015 *

Так почему же приведенный выше запрос не будет стоить максимум 25612 * 8 = 204896? Когда я это объясняю, это возвращается более чем в 3 раза.

Действительно, мой вопрос: как мне переписать этот запрос, чтобы он выполнялся быстрее?

ПРИМЕЧАНИЕ. Я использую Oracle.

EDIT:

t2 имеет 11 895 731 рядов
t1 имеет 473 235 192 строки

РЕДАКТИРОВАТЬ 2:

Поскольку я пробовал разные вещи, часть запроса, которая занимает больше всего времени, - это полное сканирование t1 в поисках store_num. Есть ли способ не допустить полного сканирования, поскольку мне нужна только самая большая запись?

Ответы [ 4 ]

2 голосов
/ 16 февраля 2010

Вы говорите:

все _ids имеют индексы для них

Но ваш запрос:

...
where store_num is not null
and table_oid = 1234;

Все ваши _id индексы бесполезны для этого запроса, если только store_num и table_oid не проиндексированы и не являются первыми столбцами в указанном индексе.

Так что, конечно, он должен выполнить полное сканирование; он может мгновенно вернуть вам max(id) без каких-либо условий фильтра, но как только вы вставите фильтр, он больше не сможет использовать индекс id, потому что не знает, какая часть индекса соответствует этим store_num is not null записи - не без сканирования.

Чтобы ускорить запрос, вам нужно создать индекс на (store_num, table_oid, id). Применяются стандартные заявления об отказе от ответственности за создание индексов для одного специального запроса; слишком большое количество индексов снизит производительность вставки / обновления.

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

2 голосов
/ 15 февраля 2010

Не уверен, применимы ли они к Oracle. У вас есть индекс по столбцу FK ID для объединения. Также, если вы можете избежать 'NOT IN', это не тип sargable в SQL, который замедляет запрос.

другой вариант, который может быть медленнее, - это выполнить внешнее объединение, а затем проверить наличие нулевого значения в этом столбце (не уверен, относится ли это только к sql)

select max(id) from t1
left outer join t2 on t2.t2_id = t1.id
where t1... IS NULL
and table_oid = 1234;
1 голос
/ 16 февраля 2010

В дальнейшем я предполагаю, что covered_entity_id - это то же самое, что и store_num - это действительно облегчит нам задачу, если вы будете последовательны в своем наименовании.

Подавляющее большинство записей в т1 есть store_num.

Учитывая, что это так, следующий пункт не должен влиять на производительность вашего запроса ...

where covered_entity_id is not null

Однако вы продолжаете говорить

часть запроса, которая принимает самое длинное - полное сканирование на t1 ищу store_num

Это говорит о том, что запрос сначала ищет covered_entity_id is not null, а не, предположительно, гораздо более избирательный table_oid = 1234. Решение может быть так же просто, как переписать запрос, как этот ...

where table_oid = 1234 
and  covered_entity_id is not null;

... хотя я подозреваю, что нет. Вы можете попробовать подсказку, чтобы запрос использовал индекс на table_oid.

Другое дело, насколько свежа статистика? Когда оптимизатор выбирает радикально неверный план выполнения, это часто происходит потому, что статистика устарела.

Кстати, почему вы вообще присоединяетесь к T2? Ваши требования могут быть удовлетворены путем выбора max(id) из T1 (если только у вас нет внешнего ключа, обеспечивающего T1.ID ссылки T2.T2_ID, и, следовательно, необходимо быть уверенным).

редактировать

Чтобы проверить статистику, запустите этот запрос:

select table_name
       , num_rows
       , last_analyzed
from user_tables
where table_name in ('T1', 'T2')
/

Если результаты показывают, что num_rows сильно отличается от значений, которые вы указали при первом редактировании, вам следует заново собрать статистику. Если last_anlayzed - это что-то вроде того дня, когда вы начали жить, то вам определенно стоит пересобраться. Вы можете сначала экспортировать свою статистику; обновление статистики может повлиять на планы выполнения (что является целью упражнения), как правило, навсегда, но иногда дела могут ухудшиться. Узнать больше .

1 голос
/ 16 февраля 2010

Лучший способ, которым я могу придумать, чтобы запустить этот бег быстро, это:

  1. Создайте индекс для (TABLE_OID, ID DESC, COVERED_ENTITY_ID) в этом порядке. Почему?

table_oid - это ваше основное условие доступа id - так что вам не нужно обращаться к блоку данных, чтобы прочитать его, - и вы получите более высокие значения ID в первую очередь closed_entity_id - вы фильтруете данные на основе этого, ноль против ноль

Это должно исключить необходимость доступа к 473-метровым строкам в T1.

  1. Убедитесь, что есть индекс для T2_ID.

Если все, что есть, запрос вроде:

select max(id) 
  from t1     
       inner join t2 
          on t2.t2_id = t1.id     
 where covered_entity_id is not null     
   and table_oid = 1234;   

должен быть (оптимизатор - привередливая зверька), способным выполнять полусоединение, основанное на быстром полном сканировании по индексу T1, никогда не сканируя блоки данных. Также рассмотрите возможность написать это как:

select max(id) 
  from t1     
 where covered_entity_id is not null     
   and table_oid = 1234
   and exists (select null 
                 from t2
                where t1.id = t2.t2_id);   

select max(id)
  from t1
 where covered_entity_id is not null
   and table_oid = 1234
   and id in (select t2_id from t2);

Поскольку оптимизатор может написать эти планы немного по-другому.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...