| |
select ... from
соответствующим образом. Однако по ходу
эксплуатации этого механизма мне не раз приходилось натыкаться на проблемы.
Которые, как выяснилось после детального разбирательства, опять же
упираются в планирование.
Основная ситуация, когда эти проблемы могут проявиться - соединение процедуры и простой таблицы. Где-то в недрах оптимизатора особенности процедур учитываются не до конца.
Так же должен отметить, что в отличие от предыдущей темы, поведение оптимизатора заметно изменилось в процессе развития InterBaseмежду версиями 4 и 6. Правда, с четвёркой давно таких экспериментов не устраивал, но на моей памяти случались совершенно странные ситуации, заканчивавшиеся падением сервера с сообщениями в стиле ``Internal gds consistency check''. Из шестёрки, на которой проводил эксперименты, такого получить не удалось. Более того, заставить её выйти на ``кривой'' план удалось только ручным планированием.
Но обо всём по порядку. Первопричина всех неприятностей - то, что ядро
InterBase до сих пор не умеет делать соединение типа join с хранимыми
процедурами. Здесь имеется в виду не конструкция join
в
select ... from
, а одна из технологий соединения в plan
.
Факт, достойный сожаления, поскольку такое соединение во многих ситуациях
-- одно из самых эффективных.
Так вот, по моим наблюдениям, в 4.2 оптимизатор мог совершенно спокойно
сгенерировать план типа join(sp natural, table index(...))
. После
чего попытка выполнить такой план заканчивалась последствиями различной
тяжести. В 5-ых версиях ситуация стала реже, а последствия перестали
быть катастрофическими. Наконец, как я уже отмечал, в 6 оптимизатор
вроде как научился преодолевать свою склонность к join, видя процедуру.
Конечно, лучше было бы реализовать такой join, нежели предотвращать,
но и на том спасибо. Однако это всего лишь моё предположение, попытки
копать исходный код не привели меня к однозначному выводу о причинах такого
поведения -- вполне возможно, что мне просто повезло.
create procedure sp1(n integer) returns(id integer, x integer) as begin for select id, x from test1 into :id, :x do begin x = :x+:n; suspend; end end
Далее, как обычно, выполняем запросы со включённым отображением плана.
select * from sp1(3); PLAN (TEST1 NATURAL)
Первый сюрприз. Если тело запроса заключено в оператор for select
,
то план выдаётся со ссылкой именно на этот запрос, а не на процедуру.
Можно подумать, что InterBase вникает во внутренние особенности процедуры
и учитывает это при планировании внешнего запроса. Однако это всего
лишь ``оптический обман''.
select * from sp1(3) where id=5 PLAN (TEST1 NATURAL)
Если бы гипотеза была верна, то условие id=5
внеслось бы в
where
внутри процедуры, и план был основан на индексе первичного
ключа. Невозможность такой оптимизации на самом деле проистекает
из архитектуры InterBase. Для каждой процедуры выполняется компиляция в BLR,
и тем самым фактически формируется фиксированный план для
всех запросов и вообще всех операций, описанных внутри. В то время
как план (и BLR) внешнего запроса может всего лишь вызвать процедуру,
обеспечив обмен параметрами.
Но возвращаемся к основной теме.
select * from sp1(3) sp, test2 t where sp.id = t.id PLAN MERGE (SORT (T NATURAL),SORT (TEST1 NATURAL)) select * from test2 t, sp1(3) sp where sp.id = t.id PLAN MERGE (SORT (T NATURAL),SORT (TEST1 NATURAL)) select * from sp1(3) sp inner join test2 t on sp.id = t.id PLAN MERGE (SORT (T NATURAL),SORT (TEST1 NATURAL))
Неизменный результат. Правда, эксперимент проводился на IB6.
Все процедурные особенности изолируются от остальной части
плана через SORT()
. Не самый эффективный, но безопасный
вариант.
select * from sp1(3) sp, test2 t where sp.id = t.id plan join(sp natural, t index(rdb$primary2)) Statement failed, SQLCODE = -104 invalid request BLR at offset 71 -BLR syntax error: expected TABLE at offset 71, encountered 125
BLR 125 -- это (судя по jrd/blr.h
) pid - procedure id. То есть
для осуществления join нужна реальная таблица.
Ещё одна особенность, точнее на этот раз пожалуй даже глупость, которую периодически пытаются сделать новички. Ну например так:
select * from sp1(t.id) sp, test2 t
То есть в качестве параметра процедуры идёт одно из полей другого соединяемого отношения. Никогда не пройдёт. По той простой причине, что при любой технологии сначала выполняется соединение, а уже потом появляются поля.
Если не совсем понятно, то задумайтесь над таким фактом. Если допустить,
что можно в качестве параметров употреблять поля из того же from
,
то почему нельзя употреблять поля той же процедуры. И что получаем?
From sp1(sp.id) sp
. Интересно, можно ли этому придумать ...
ну если не разумную интерпретацию, то хотя бы способ реализации? Мне
что-то на ум ничего не приходит.
Таким образом правильный способ реализации следующий:
for select ... from test2 t into :id, ... do for select ... from sp1(:id) into ... do suspend;
По-длиннее, конечно. Но зато никаких неоднозначностей.
join
, а не sort merge
.
select * from test2 t left join sp1(t.id) on 0=0; PLAN JOIN (T NATURAL,TEST1 NATURAL)Странное условие на конце запроса предназначено единственно для успокоения синтаксического анализатора. Невозможность merge вполне объяснима -- ведь при этом обе таблицы вычисляются независимо и только потом сливаются вместе. Что при связи по параметру невозможно.
При работе же через join и внешнем соединении, InterBase делает
``сплошной'' проход по ``внешней'' стороне соединения (то есть
эта сторона разумеется сама по себе может фильтроваться, в том
числе и по индексам, но только внутри себя), и для каждой
полученной записи вычисляет ``довесок'' из другой таблицы. Если
вычислить не получилось, то присоединяются значения null
.
Подстановка параметров при этом тоже срабатывает.
Заключительная неожиданность этой области -- попробуем вручную
добавить к этому запросу план через join
.
select * from test2 t left join sp1(t.id) sp on 0=0 plan sort merge(t natural, sp natural); Statement failed, SQLCODE = -104 invalid request BLR at offset 77 -BLR syntax error: expected TABLE at offset 77, encountered 125Ничего не замечаете? Сравните с предыдущим примером, где я тоже пытался планировать соединение с процедурой вручную. Как ни парадоксально, ругань совершенно та же самая. InterBase захотел реальную таблицу вместо процедуры. Хотя в прошлый раз он этого хотел с
join
, а с merge
совершенно нормально
работал, а теперь наоборот. Всего-то добавилось слово left
.
Закладки на сайте Проследить за страницей |
Created 1996-2024 by Maxim Chirkov Добавить, Поддержать, Вебмастеру |