InterBase: тормозология и глюконавтика
Страница 27. Прокрутка датасетов


 

Прокрутка датасетов-таблиц

Допустим, что мы открыли таблицу и листаем её в заданном направлении. Перед BDE встаёт задача: есть текущая запись и нужно найти следующую (предыдущую) в заданном порядке. На уровне SQL-запросов, идущих в сервер, листание превращается в запросы (предполагаем, что для таблицы назначена сортировка по одному полю и листание идёт вперёд):
select имена_всех_полей

       from таблица

       where ключ is null or ключ > ключ_текущей_записи

       order by ключ asc
Особенности:
  • Всегда читаются все поля запрошенной таблицы, независимо от того, нужны они приложению или нет.
  • При обратном направлении прокрутки меняются направления сортировки и сравнения.
  • Ключ всегда проверяется на null, даже если он в метаданных БД прописан, как not null. InterBase при любых order by всегда выдаёт записи с null последними, так что "нормальную" сортировку это не портит, хотя и тормозит.
  • BDE, как правило, вычитывает из этого запроса не все записи, а столько, на сколько пользователь пролистал таблицу в данном направлении.
  • Комбинация where и order by приводит к тому, что записи внутри сервера сортируются физически, причём все, попадающие под условие, несмотря на предыдущий пункт. Так что на больших таблицах такая прокрутка в принципе не может быть эффективной. Об этом и последующих эффектах писалось в планировании запросов.
  • Прокрутка "в начало" или "в конец" приводит к примерно аналогичным запросам с немного другой формулировкой, но не менее тормозным.
  • Сортировка по двум полям (ключ1;ключ2) приводит к условию поиска вида "where (ключ1 > ключ1_текущей_записи) or ((ключ1 = ключ1_текущей_записи or ключ1 is null) and (ключ2 > ключ2_текущей записи or ключ2 is null))". Суть в том, что проверяются все возможные комбинации сравнения ключей и наличия null'ов в них, чтобы сгенерировать все записи которые либо "точно больше" текущей, либо "возможно больше". При трёх полях запрос становится ещё более страшным. Причём страшность обратно пропорциональна эффективности.
  • В случае с наложенным фильтром условие может быть ещё сложнее, но это отдельный разговор.

Прокрутка датасетов-запросов

В стандарте SQL предусмотрено, что одни запросы могут быть произвольно прокручиваемыми, а другие - нет. Условия, когда произвольная прокрутка возможна, примерно в том же духе, что и для обновляемости представлений. Это означает, что большинство полезных запросов на уровне InterBase прокручиваются строго вперёд по одной записи. Тем не менее, BDE эмулирует для них произвольную прокрутку. И делается это варварским способом, который я лично называю маниакальное чтение. Суть в том, что после открытия запрос читается от начала до нужного места и все полученные записи сохраняются в памяти на клиенте. При попытке листать назад приложению предъявляются эти прочитанные записи. К чему это приводит, я расписывать не буду.

В Delphi существует свойство запроса, именуемое UniDirectional. Согласно документации, оно должно определять, как открывается запрос: в однонаправленном режиме или в режиме неограниченной прокрутки. На самом деле если запрос допускает только однонаправленный режим, то он и будет открываться в этом режиме независимо от UniDirectional. Реально эта опция позволяет лишь чуть-чуть сэкономить в очень простых случаях. В остальных маниакальное чтение гарантировано.

Фильтрация

Когда я разобрался, как действует этот механизм, у меня сложилось впечатление, что у Borland правая рука не знает, что делает левая. Дело в том, что существуют два способа реализации фильтра:
  1. Путём формирования соответствующего условия в SQL-запросе.
  2. Путём маниакального вычитывания всех записей на клиента с последующей их проверкой.
В двух случаях фильтрация реализуется гарантированно по второму сценарию: когда фильтруется SQL-запрос (у BDE не хватает умственных способностей, чтобы его осмыслить и модифицировать, хотя существуют реляционные системы, в которых такие фокусы являются нормой) и когда фильтрация делается пользовательским обработчиком (в Delphi по событию OnFilterRecord).

В случае с таблицей теоретически возможно реализовать все фильтрующие конструкции средствами SQL. Ведь InterBase поддерживает приведение строк к верхнему регистру через UPPER() и сравнение с подстрокой с помощью containing, с шаблоном с помощью like. Вот только BDE (или его драйвер InterBase) об этом почему-то не знает. Таким образом, если условие состоит лишь из сравнений по полному точному значению с учётом регистра символов (или сравнений чисел и т. п.), то такой запрос будет сконвертирован в часть where отправлен на сервер. Но стоит это требование хоть в чём-то нарушить - и проверка всего фильтрового выражения перейдёт к клиенту с соответствующей прокачкой всех записей таблицы (разве что с учётом фильтра по MasterSource) через сеть.

 
« Предыдущая статья