Страница 3 из 4 Как оно работает на самом деле Итак, вернёмся к нашим контекстам. В скалярном контексте InterBase действительно принимает во внимание, коррелированный подзапрос, или нет. Если нет, то запрос вызывается единожды, результат (одно значение) запоминается, и используется при отработке внешнего запроса примерно так же, как обычный параметр. В списочном же контексте (чаще всего - в IN (...)), подзапрос всегда вызывается на каждую итерацию внешнего запроса. Точнее тогда, когда для текущей записи проверены прочие условия, чтобы исключить излишние вызовы. Провернуть предыдущую схему InterBase не в состоянии, вероятно по той причине, что запоминать придётся не одно значение, а список, причём потенциально неограниченной длины. Отсюда же следует, что если бы InterBase умел это делать, то мог бы достаточно легко преобразовывать множественные подзапросы в соединения, которые он, как правило, в состоянии реализовать достаточно эффективно. В самом деле, подзапрос внутри IN (...) возвращает таблицу с одним полем, и при дальнейшей обработке внешний запрос фактически соединяется с этой таблицей. Видимо у InterBase сложности с сохранением этой самой промежуточной таблицы, так что он предпочитает другую стратегию - на каждой итерации вычислять те значения, которые ему требуются. И вот здесь мы как раз и натыкаемся на достаточно оригинальную (на мой взгляд) оптимизацию. InterBase действительно вычисляет такие подзапросы помногу раз, но при этом учитывает контекст, так что порой достигается эффективность, не уступающая раскрутке подзапроса в соединение. Хотя, к сожалению, это возможно далеко не во всех случаях. Когда подзапрос вызывается конструкцией типа значение IN (select поле ...), то, если внимательно подумать, нам и не нужны все записи подзапроса. Нужно найти те, у которых поле имеет значение. А это значит, что оптимизатор может со спокойной душой добавить подзапросу в where дополнительное условие ...) and поле=значение. А это, в свою очередь вполне может привести к тому, что по данному полю будет использован индекс, или оно послужит основой для других способов оптимизации. И кстати, данная оптимизация не делается для подзапросов в скалярном контексте. Они отрабатываются совершенно независимо. Хотя в них она могла быть тоже отнюдь не бесполезной. Ещё одна загадка природы. И теперь настало время ещё раз вспомнить про EXISTS(...). По своей природе данная конструкция предназначена для вызова коррелированных подзапросов, и эти подзапросы внутри неё ведут себя в соответствии с вызовом во множественном контексте. Хотя выполнение каждого вызова, естественно, прекращается при получении первой же записи. Именно, исходя из этого, и следует оценивать трудоёмкость EXISTS. Серия примеров Данные create table test1( id integer not null primary key, x integer ); create table test2( id integer not null primary key, y integer); Поскольку эксперимент проводился на свежесозданной базе, индексы первичных ключей получили те же номера, что и таблицы - rdb$primary1 и rdb$primary2. Других индексов нет. Таблицы заполнены записями очень простого вида: insert into test1(id, x) values(1, 10); insert into test1(id, x) values(2, 20); ... insert into test1(id, x) values(10, 100); insert into test2(id, y) values(1, 110); insert into test2(id, y) values(2, 120); ... insert into test2(id, y) values(10, 200); Все дальнейшие запросы приводятся с планами, полученными путём включения set plan в isql. |