Порядок соединения – Часть 1
Несколько дней назад я вынес на обсуждение следующий запрос и попросил вас рассмотреть возможные варианты его выполнения. Когда я задавал этот вопрос, меня не интересовали технические подробности — я хотел подтолкнуть людей задуматься о том что значит этот запрос, и как цель этого запроса может повлиять на выбор варианта его выполнения.
select /*+ qb_name(main) */ t1.v1 from t1, t3 where t1.n2 = 100 and t3.n1 = t1.n1 and t3.n2 = 100 and exists ( select /*+ qb_name(sub2) */ t2.id from t2 where t2.n1 = 15 and t2.id = t1.id ) and exists ( select /*+ qb_name(sub4) */ t4.id from t4 where t4.n1 = 15 and t4.id = t3.id ) ;
Имейте ввиду, что этот запрос представлен в качестве иллюстрации, и дальнейшие возможности, которые я буду описывать, зависят от версии оптимизатора, от данных, от ограничений (constraints) и от используемых предикатов.
В частности, способность Oracle устранять уровни вложенности (UNNEST) может значительно отличаться в разных версиях, а также может серьезно зависеть от вида общего запроса. Далее я буду изменять EXISTS на NOT EXISTS, или AND на OR как того будут требовать нить повествования.
Количество возможных вариантов выполнения и комментарии, которые идут за ними, настолько великолепны что я собираюсь опубликовать серию серьезных «глав», посвященных этому запросу. Сделать их самостоятельными страницами и разместить ссылки в главном меню. Главу которую вы сейчас читаете — будет первой.
Простые объединения и подзапросы (Simple Joins and Subqueries)
В самом простом случае, игнорируя механизмы Oracle, способные устранять уровни вложенности (unnesting), оптимизатор сначала будет соединять таблицы во внешнем предложении FROM, а подзапросы отодвинет в самый конец выполнения основного запроса.
Может быть это хорошо, а может и нет. Одним из важнейших пунктов оптимизации является «раннее исключение» (eliminate early). Он означает, что когда вы находитесь в середине объединения нескольких таблиц, то «следующую» таблицу нужно выбирать находя компромисс между эффективным путем доступа и его влиянием на уменьшение размера результирующего множества.
Для примера, если t1 — это таблица содержимого заказов, то t3 может быть таблицей самих заказов, а t2 (в первом подзапросе) может быть таблицей накладных, которая логически соотносится с таблицей содержимого заказов, а не с таблицей заказов.
Возникает вопрос. Если вы идентифицировали список содержимого заказа, должны ли вы объединить его с таблице заказов, только потому что эта таблица содержит дату, которая исключит 90% всех записей списка содержимого, который, кстати, вы только что сформировали? Или вы должны выполнить подзапрос к таблице накладных, потому что он исключит 99% из списка содержимого заказов?
Ясно, что разумнее всего было бы исключить как можно больше данных как можно раньше, выполнив подзапрос первым. Но соединение с таблицей заказов может быть намного более эффективным и позволит сократить количество (более дорогих) подзапросов до безобидного количества. Чтобы принять правильное решение, вы должны разбираться в данных и понимать назначение запроса.
Конечно вы можете решить, что устранение уровней вложенности (unnesting), путем получения накладных и объединения их с содержимым заказов, может быть более эффективным. Но в этом случае может вообще не существовать эффективного способа получить минимальный набор накладных без получения дополнительной информации из таблицы содержимого заказов.
Хинтование (Hinting)
В случаях, похожих на рассматриваемый, Oracle дает нам возможность некоторого стратегического контроля. Подсказки push_subq, no_push_subq, unnest и nounnest могут использоваться для указания основной стратегии выполнения запроса.
В 10g, подсказки push_subq должны размещаться внутри подзапросов, которые вы хотите обработать раньше. В предыдущих версиях Oracle они размещались во внешнем запросе. Обратите внимание на этот факт. Если вы уже используете push_subq в 8i или 9i — после обновления до 10g может показаться, что Oracle игнорирует вашу подсказку.
Оригинал: Join Ordering — Part 1