2014-10-31 4 views
1

У меня есть четыре - пять таблиц, которые действительно большие по размеру, и они оставлены внешними, соединенными с использованием нижеприведенного запроса. Есть ли способ, чтобы его можно было переписать так, чтобы производительность могла быть улучшена?Тюнинг/переписывание sql-запроса со многими левыми внешними соединениями и большими таблицами

SELECT t1.id, 
    MIN(t5.date) AS first_pri_date, 
    MIN(t3.date) AS first_pub_date, 
    MAX(t3.date) AS last_publ_date, 
    MIN(t2.date) AS first_exp_date 
FROM t1 
    LEFT JOIN t2 ON (t1.id = t2.id) 
    LEFT JOIN t3 ON (t3.id = t1.id) 
    LEFT JOIN t4 ON (t1.id = t4.id) 
    LEFT JOIN t5 ON (t5.p_id =t4.p_id) 
GROUP BY t1.id 
ORDER BY t1.id; 

подсчитывает записи являются:

  • t1: 6434323
  • t2: 6934562
  • t3: 9141420
  • t4: 11515192
  • t5: 3797768

На большинстве столбцов, используемых для объединения, есть индексы. Самой потребляющей частью плана объяснения является внешнее соединение с t4, которое происходит в конце. Я просто хотел узнать, есть ли способ переписать это, чтобы улучшить производительность.

+2

Пожалуйста, используйте ссылку «изменить» под своим вопросом и используйте возможности форматирования кода редактора вопросов, чтобы правильно форматировать ваш запрос как часть кода (это панель инструментов с двумя фигурками - '{}'). Отправьте план выполнения и в свой запрос. – nop77svk

+0

Если это реальный запрос, вы не ссылаетесь на T4 или T5 в предложении select, так почему вы присоединяетесь к ним? – Sparky

+1

Откуда берутся 'p.date' в вашем вопросе? В разделе 'FROM' нет таблицы с именем' p'. –

ответ

1

Предполагая, что id является первичным ключом в t1, ваш запрос может (или не может, в зависимости от настройки ПГК вашей компании Oracle) работают лучше, когда написано следующее:

SELECT --+ leading(t1) use_hash(t2x,t3x,t45x) full(t1) no_push_pred(t2x) no_push_pred(t3x) no_push_pred(t45x) all_rows 
    t1.id, 
    t45x.first_pri_date, 
    t3.first_pub_date, 
    t3.last_publ_date, 
    t2.first_exp_date 
FROM t1 
    LEFT JOIN (
     SELECT t2.id, 
      MIN(t2.date) AS first_exp_date 
     FROM t2 
     GROUP BY t2.id 
    ) t2x 
     ON t2x.id = t1.id 
    LEFT JOIN (
     SELECT t3.id, 
      MIN(t3.date) AS first_pub_date, 
      MAX(t3.date) AS last_publ_date 
     FROM t3 
     GROUP BY t3.id 
    ) t3x 
     ON t3x.id = t1.id 
    LEFT JOIN (
     SELECT --+ leading(t5) use_hash(t4) 
      t4.id, 
      MIN(t5.date) AS first_pri_date 
     FROM t4 
      JOIN t5 ON t5.p_id = t4.p_id 
     GROUP BY t4.id 
    ) t45x 
     ON t45x.id = t1.id 
ORDER BY t1.id; 

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

1

Я бы сказал, что ваша проблема в том, что вы делаете много ЛЕВЫХ ПРИСОЕДИНЕНИЙ, и конечные результаты становятся слишком большими после применения всех этих ОБЪЕДИНЕНИЙ. Также индексы не могут использоваться таким образом, чтобы максимально быстро вычислить MIN или MAX. При хорошем использовании индексов вы сможете очень быстро вычислить MIN или MAX.

Я хотел бы написать запрос, а так:

SELECT t1.id,  
(SELECT MIN(t5.date) FROM t5 JOIN t4 ON t5.p_id = t4.p_id WHERE t4.id = t1.id) AS first_pri_date, 
(SELECT MIN(date) FROM t3 WHERE t3.id = t1.id) AS first_pub_date, 
(SELECT MAX(date) FROM t3 WHERE t3.id = t1.id) AS last_publ_date, 
(SELECT MIN(date) FROM t2 WHERE t2.id = t1.id) AS first_exp_date 
FROM t1 
ORDER BY t1.id; 

Для лучшего Performace создания индексов на (id, date) или (p_id, date). Так что ваши показатели будут выглядеть так:

CREATE INDEX ix2 ON T2 (id,date); 
CREATE INDEX ix3 ON T3 (id,date); 
CREATE INDEX ix5 ON T5 (p_id,date); 
CREATE INDEX ix4 ON T4 (id); 

Но по-прежнему остается проблема с соединением между t4 и t5. В случае, если есть 1: 1 соотношение между t1 и t4, это может быть даже лучше, чтобы написать что-то вроде этого на второй линии:

(SELECT MIN(t5.date) FROM t5 WHERE t5.p_id = (SELECT p_id FROM t4 WHERE t4.id=t1.id)) AS first_pri_date, 

Если 1: N, а также если CROSS ОТНОСИТЬСЯ и OUTER APPLY работать на вашей версии Oracle, вы можете переписать вторую строку:

(SELECT MIN(t5min.PartialMinimum) 
FROM t4 
CROSS APPLY 
(
    SELECT PartialMinimum = MIN(t5.date) 
    FROM t5 
    WHERE t5.p_id = t4.p_id 
) AS t5min 
WHERE t4.id = t1.id) 
AS first_pri_date 

Все это направлено на наилучшее использование индексов при расчете MIN или MAX. Таким образом, весь ВЫБРАТЬ можно переписать так:

SELECT t1.id,  
(SELECT MIN(t5min.PartialMinimum) 
FROM t4 
CROSS APPLY 
(
    SELECT TOP 1 PartialMinimum = date 
    FROM t5 
    WHERE t5.p_id = t4.p_id 
    ORDER BY 1 ASC 
) AS t5min 
WHERE t4.id = t1.id) AS first_pri_date, 
(SELECT TOP 1 date FROM t2 WHERE t2.id = t1.id ORDER BY 1 ASC) AS first_exp_date, 
(SELECT TOP 1 date FROM t3 WHERE t3.id = t1.id ORDER BY 1 ASC) AS first_pub_date, 
(SELECT TOP 1 date FROM t3 WHERE t3.id = t1.id ORDER BY 1 DESC) AS last_publ_date 
FROM t1 
ORDER BY 1; 

Это, как я считаю, наиболее оптимальный способ, как получить MIN или MAX из исторических данных таблицы.

Дело в том, что использование MIN с большим количеством не проиндексированных значений заставляет сервер загружать все данные в память, а затем вычислять MIN или MAX из неиндексированных данных, что занимает много времени, поскольку оно предъявляет высокие требования к Операции ввода-вывода. Плохое использование индексов при использовании MIN или MAX может привести к ситуации, когда у вас есть все ваши данные таблицы истории, хранящиеся в кеше в памяти, не требуя этого ни для чего другого, кроме MIN или MAX.

Без части запроса CROSS APPLY серверу необходимо будет загрузить в память все отдельные даты из t5 и вычислить MAX из всего загруженного набора результатов.

Отметить, что функция MIN на правильно проиндексированной таблице ведет себя как TOP 1 ORDER BY, что очень быстро. Таким образом, вы можете мгновенно получить свои результаты.

CROSS APPLY доступен в Oracle 12C, в противном случае вы можете использовать pipelined functions.

Проверьте это SQL Fiddle, особенно различия в планах исполнения.

Смежные вопросы