В плане появляется bitmap-сканирование вместо обычного индексного поиска. Какой компромисс оптимизатор пытается выиграть?
Bitmap-сканирование выбирают, когда нужно найти достаточно много строк: сначала индекс собирает ссылки на подходящие строки, затем СУБД группирует их по страницам таблицы и читает страницы более последовательно. Это уменьшает количество случайных обращений к таблице, но требует дополнительной памяти и не сохраняет порядок, заданный индексом.
Обычный индексный поиск особенно эффективен, когда найдено мало строк. Для каждой строки он может отдельно обращаться к таблице, поэтому при большом результате чтение превращается в множество случайных операций.
Bitmap-подход появился как компромисс между таким точечным доступом и полным сканированием таблицы. Он сохраняет преимущество индекса при отборе строк, но организует чтение уже не по порядку ссылок из индекса, а по страницам таблицы.
Наличие подходящего индекса само по себе не означает, что оптимален обычный индексный скан. Если условие возвращает много строк, последовательное обращение к каждой строке может оказаться дороже, чем обработка списка страниц, содержащих результаты.
Неверный выбор доступа приводит либо к большому числу случайных операций ввода-вывода, либо к лишней обработке всей таблицы. Дополнительная проблема состоит в том, что bitmap-план обычно теряет порядок строк, поэтому для результата с сортировкой может потребоваться отдельная операция сортировки.
План обычно состоит из двух логических этапов. Узел Bitmap Index Scan проходит индекс и строит bitmap — структуру с информацией о страницах таблицы и позициях подходящих строк. Затем Bitmap Heap Scan читает страницы таблицы группами, извлекая из них найденные строки.
Главный выигрыш возникает потому, что несколько строк на одной странице обслуживаются одним чтением. Кроме того, доступ к страницам можно упорядочить по физическому расположению, уменьшив случайность ввода-вывода.
Bitmap может быть точным: в нём хранятся конкретные позиции строк. При нехватке памяти он может стать неточным для отдельных страниц: тогда хранится только факт, что страница может содержать подходящие строки. Такие страницы приходится перечитывать и проверять условие уже на уровне таблицы.
Для условий с несколькими предикатами СУБД иногда объединяет результаты нескольких индексов операциями, аналогичными BitmapAnd или BitmapOr. Это позволяет использовать несколько отдельных индексов, но создание и обработка bitmap тоже имеют стоимость; составной индекс может оказаться выгоднее.
Обычный индексный скан часто лучше, когда результат очень мал и важен порядок индекса. Bitmap-сканирование выгоднее при промежуточной селективности, когда строк уже много для точечных обращений, но ещё недостаточно для полного сканирования. Если подходит большая часть таблицы, оптимизатор может выбрать последовательное сканирование.
Решение зависит от стоимости чтения, размера таблицы, расположения данных, доступной памяти, кластеризации таблицы и оценки количества строк. Поэтому сам факт появления bitmap-узла не означает ошибку: его нужно оценивать вместе с фактическими количеством строк, временем и объёмом чтения.
В PostgreSQL отчёт фильтрует большую таблицу событий по периоду времени и типу события. Отдельный индекс по дате и отдельный индекс по типу позволяют быстро получить кандидатов, но обычный индексный доступ приводит к множеству обращений к страницам таблицы.
Рассматривались три варианта. Полное сканирование имело простой план, но читало слишком много страниц. Составной индекс мог ускорить конкретный отчёт, однако увеличивал размер индекса и стоимость изменений данных. Bitmap-план объединял результаты существующих индексов и читал страницы таблицы группами.
В выбранном варианте оставили bitmap-доступ, поскольку запрос возвращал заметную долю строк, но не всю таблицу. При этом отдельно проверили, что сортировка выполняется после чтения, и не ожидали от bitmap-плана сохранения порядка индекса. Если требования к сортировке стали бы доминирующими, состав индекса пришлось бы пересмотреть.
Нет. Для небольшого результата обычный индексный скан обычно дешевле: bitmap не нужно строить, хранить и затем обходить. Bitmap оправдан, когда экономия на обращениях к страницам превышает стоимость его построения.
При точном bitmap известны конкретные позиции строк. При неточном bitmap СУБД знает только, что страница потенциально содержит результат, поэтому читает страницу и повторно проверяет предикат для её строк. Это увеличивает CPU-нагрузку и может сделать план невыгодным при недостатке памяти.
Индексный скан может выдавать строки в порядке ключа индекса. Bitmap-план обычно перестраивает доступ в порядок страниц таблицы, чтобы сократить случайное чтение. Поэтому порядок результата не считается сохранённым, и при наличии ORDER BY оптимизатор может добавить отдельную сортировку.