Страница 13 из 38 Как получаются планы Чтобы лучше понять, как лучше оптимизировать работу interbase, крайне желаетльно понимать, как именно он генерирует планы автоматически, то есть в тех случаях, когда в пользовательском запросе план явно не указан. Во-первых, планирование проводится отдельно для каждого подзапроса любого рода. Сюда входят, в частности, запросы внутри процедур, представления с distinct, части union и тому подобные вещи. То есть interbase делит при необходимости выражение на куски select и оптимизирует каждый из них независимо. Кроме, разве что, "хороших" представлений. Планы вложенных запросов просто вызываются при отработке охватывающих планов столько раз, сколько необходимо. Судя по документации, порядок оптимизации отдельного select'а выглядит следующим образом: - Приведение условия where к нормальной форме. Оно трансформируется с целью поделить его на максимально возможное количество условий, связанных через and, которые, разумеется, сами должны стать как можно проще.
- Распределение условий. Например, если в результате предыдущей нормализации получилось a=b and ... b=c, то добавляется дополнительное условие a=c. Формулировку условия по законам логики это не нарушит, но может выявить новые, более удобные связи в запросе. Скажем, если a, b, и c - поля из разных таблиц, то может оказаться выгоднее соединить сначала a и с, что не следует напрямую из первоначальной формулировки.
Сказанное в некоторой степени распространяется и на неравенства, но здесь возможностей обычно меньше. Между прочим, совсем умные (не interbase) планировщики сюда же добавляют ограничения целостности (check(), следствия unique, ...). Условия получаются ещё жёстче, а значит фильтрация - эффективнее. InterBase, конечно, глуп, но ситуация вполне моделируема руками - не бойтесь подсказать ему очевидное, дописав ещё условия. - Формирование потоков. Полученный набор условий сопоставляется с индексами на полях и выявляются так называемые "потоки" (не путать с потоками в распараллеливании вычислений). То есть поток - это таблица, которую можно перебрать по индексу и это будет соответствовать одному из элементов условия.
- Формирование "рек". Уж любят эти буржуи поэтические названия. В данном случае река - это комбинация потоков с предыдущего шага, связанных непосредственно связанных условием или набором условий. То есть река - это то, что можно реализовать через процедуру слияния, merge. Если есть больше двух потоков, отсортированных по одним и тем же (исходя из равенств) полям, то они объединяются в одну реку.
- Выбор самой широкой реки. Вот здесь у авторов то ли interbase, то ли его документации плоховато. Потому что они обозвали этот этап выбором "самой дллинной реки". Хотя из текста (и из здравого смысла) следует, что выбирать надо именно самую широкую, то есть содержащую максимальное количество потоков, и следовательно, реализующую за раз максимальное количество соединений. А вовсе не самую длинную по количеству записей - это не цель для повышения производительности, скорее наоборот.
В общем, выбирается самая широкая река и исключается из набора доступных потоков. После чего из оставшихся вновь выбирается самая широкая, и т. д. До тех пор, пока не будут исчерпаны все потоки (таблицы с условиями). Если две реки оказываются одинаковыми по ширине, то начинают учитываться параметры индексов и сруктура условий. Лучшими считаются более компактные индексы, отсекающие большее количество данных. В частности, большой приоритет получают уникальные индексы, поскольку для них заранее известно, что для каждого входного значения найдётся не более одного выходного. Хотя оценки индексов, как уже сказано, весьма и весьма относительны, и не всегда имеют отношение к реальности. О чём писалось в соответствующем разделе. - Объединение выбранных рек. Независимо от того, насколько это тяжело, выбранные реки объединяются. То есть делается либо sort merge, либо join, в зависимости от того, что доступнее и удобнее. Вообще-то если бы interbase был немного по-умнее, то он бы учитывал, что "идеальные" реки на предыдущем шаге могут осложнить ситуацию на этом, и наоборот. Но interbase не умеет возвращаться назад, то есть если он принял решения по поводу потоков, а затем рек, то он перейдёт к этому этапу и будет мучиться с тем, что есть.
В целом по поводу изложенного алгоритма можно сказать следующее: - Он приводит к физически выполнимым планам
- Планы с большой вероятностью будут достаточно эффективными
- Существует сравнительно небольшая вероятность зайти "в тупик". То есть план будет получен, но он будет неэффективным.
- Решения принимаются на основе статистики, которая не всегда соответствует действительности, что делает процесс более случайным и увеличивает вероятность неправильного выбора.
- Исходя из предыдущего замечания - существует вероятность, что по ходу эксплуатации базы эффективные решения изменятся на неэффективные, что приведёт к неожиданному и резкому снижению производительности. Хотя возможен и обратный процесс. И то, и другое практически невозможно реально оттестировать.
- Имеются сообщения о глюках в оптимизаторах 4.Х и 5.Х. В частности, они проявляются в том, что в некоторых случаях запросы на одних и тех же данных с одной и той же статистикой, отличающиеся порядком перечисления таблиц и даже полей в select, давали разные планы. Что дополнительно свидетельствует о случайности выбора, делаемого автоматическим планировщиком.
- Вся вышеказанная оптимизация в interbase 4.X касается в основном обычного соединения таблиц путём перечисления их во from через запятую. Что же касается запросов с конструкциями типа t1 xxx join t2, то они обычно принудительно рассматриваются, как отдельная "река" и отрабатываются практически, как записаны. 5.Х стал в этом отношении чуть-чуть умнее, но лишь чуть-чуть.
В общем, вывод один: хочешь иметь гарантированную производительность - пиши план руками. Благо такая возможность имеется. На первый взгляд может показаться, что это громадный недостаток interbase, но поработав с ним по-дольше, я склоняюсь к мысли, что это не совсем так. Разумеется, желательно, чтобы СУБД была по-умнее. Это снимет необходимость ручной оптимизации в простых случаях, то есть для большинства операций. Однако идеального планировщика не бывает. Известно, что даже самые умные оптимизаторы в Oracle и DB2 не всегда поступают правильно. И решения их тоже носят вероятностный характер, поскольку такова статистика параметров БД. Просто вероятность правильных решений в них гораздо выше. Задача написания "хорошего" (не говоря уже об идеальном) планировщика для реляционных запросов упорно исследуется на протяжении последних десятилетий. Многое в этом направлении достигнуто, но многое пока остаётся и нерешённым. Так что ругать разработчиков interbase не совсем правомерно. Хотя и хвалить тоже. По-этому все критичные для приложения операции луше как минимум контролировать, каким образом они делаются. Если окажется, что выбранный оптимизатором план достаточно эффективен, то лучше вписать этот же план в запрос руками, чтобы не дать серверу возможность "неожиданно передумать" в будущем. Если же план поддаётся улучшению, то тем более требуется ручное вмешательство. Только так можно гарантировать отсутствие тормозов. Иначе не спасут никакие CASE'ы, архитектуры, компоненты, и т. д. В конце концов, идеальная оптимизация основана на знании семантики запроса, которая полностью известна только разработчику (а иначе он не разработчик, а ... молчу кто). И здесь я должен сказать спасибо авторам interbase. Дело в том, что в большинстве других СУБД имеются достаточно слабые механизмы для ручного управления планированием. В большинстве случаев лучшее, что можно написать - один индекс для каждой таблицы. Ни в каком порядке их нужно соединять, ни по какой технологии, уже не напишешь. Причём такова ситуация в тех продуктах, которые счиюатся гораздо более "мощными", чем interbase. Может быть они и "мощны", но только до определённого предела, который определяется интеллектом встроенного оптимизатора. Ценность interbase как раз и состоит в возможности свободно работать далеко за этим пределом. |