Обработка запроса без оптимизации PREWHERE
① Запрос содержит фильтр по столбцу
town, который входит в первичный ключ таблицы, а значит — и в первичный индекс.
② Чтобы ускорить выполнение запроса, ClickHouse загружает первичный индекс таблицы в память.
③ Затем ClickHouse просматривает записи индекса, чтобы определить, какие гранулы столбца town могут содержать строки, соответствующие условию.
④ Эти потенциально подходящие гранулы загружаются в память вместе с позиционно выровненными гранулами из других столбцов, необходимых для запроса.
⑤ После этого оставшиеся фильтры применяются во время выполнения запроса.
Как видно, без PREWHERE все потенциально нужные столбцы загружаются до фильтрации, даже если условию на самом деле соответствует лишь несколько строк.
Как PREWHERE повышает эффективность запроса
① Запрос включает фильтр по столбцу
town, который входит в первичный ключ таблицы, а значит — и в первичный индекс.
② Как и при выполнении без предложения PREWHERE, для ускорения запроса ClickHouse загружает первичный индекс в память,
③ а затем просматривает записи индекса, чтобы определить, какие гранулы столбца town могут содержать строки, соответствующие условию.
Теперь, благодаря предложению PREWHERE, следующий шаг выглядит иначе: вместо того чтобы сразу читать все нужные столбцы, ClickHouse фильтрует данные по одному столбцу за раз, загружая только то, что действительно необходимо. Это значительно сокращает объём I/O, особенно для широких таблиц.
На каждом шаге загружаются только те гранулы, которые содержат хотя бы одну строку, прошедшую — то есть удовлетворившую — предыдущему фильтру. В результате число гранул, которые нужно загружать и проверять для каждого фильтра, монотонно уменьшается:
Шаг 1: Фильтрация по townClickHouse начинает обработку PREWHERE с ① чтения выбранных гранул столбца
town и проверки, какие из них действительно содержат строки, соответствующие London.
В нашем примере все выбранные гранулы подходят, поэтому ② для обработки выбираются соответствующие им позиционно выровненные гранулы следующего столбца фильтра — date:
Шаг 2: Фильтрация по date
Далее ClickHouse ① читает выбранные гранулы столбца
date, чтобы проверить условие date > '2024-12-31'.
В этом случае две из трёх гранул содержат подходящие строки, поэтому ② для дальнейшей обработки выбираются только их позиционно выровненные гранулы из следующего столбца фильтра — price:
Шаг 3: Фильтрация по price
Наконец, ClickHouse ① читает две выбранные гранулы столбца
price, чтобы проверить последнее условие price > 10_000.
Только одна из двух гранул содержит подходящие строки, поэтому ② для дальнейшей обработки нужно загрузить только её позиционно выровненную гранулу из столбца SELECT — street:
На последнем шаге загружается только минимальный набор гранул столбцов — тех, что содержат подходящие строки. Это снижает использование памяти, уменьшает дисковый I/O и ускоряет выполнение запроса.
PREWHERE уменьшает объём читаемых данных, а не количество обрабатываемых строкОбратите внимание: ClickHouse обрабатывает одинаковое количество строк как в версии запроса с PREWHERE, так и без него. Однако при использовании оптимизации PREWHERE не все значения столбцов нужно загружать для каждой обрабатываемой строки.
Оптимизация PREWHERE применяется автоматически
optimize_move_to_prewhere (по умолчанию true), ClickHouse автоматически переносит условия фильтрации из WHERE в PREWHERE, отдавая приоритет тем, которые сильнее всего сокращают объем чтения.
Идея состоит в том, что столбцы меньшего размера сканируются быстрее, и к моменту обработки более крупных столбцов большинство гранул уже отфильтровано. Поскольку во всех столбцах одинаковое количество строк, размер столбца в первую очередь определяется его типом данных: например, столбец UInt8 обычно значительно меньше, чем столбец String.
Начиная с версии 23.2, ClickHouse по умолчанию использует эту стратегию, сортируя столбцы фильтра PREWHERE для многоэтапной обработки по возрастанию несжатого размера.
Начиная с версии 23.11, необязательная статистика столбцов может дополнительно повысить эффективность, выбирая порядок обработки фильтров на основе фактической селективности данных, а не только размера столбцов.
Как измерить влияние PREWHERE
optimize_move_to_prewhere.
Начнем с выполнения запроса при отключенном параметре optimize_move_to_prewhere:
optimize_move_to_prewhere. (Обратите внимание: этот параметр необязателен, так как он включён по умолчанию):
Ключевые выводы
- PREWHERE позволяет не читать данные столбцов, которые впоследствии будут отфильтрованы, экономя I/O и память.
- Это происходит автоматически, если включен
optimize_move_to_prewhere(по умолчанию). - Порядок фильтрации важен: небольшие и селективные столбцы должны идти первыми.
- Используйте
EXPLAINи журналы, чтобы проверить, применяется ли PREWHERE, и понять его влияние. - PREWHERE дает наибольший эффект на широких таблицах и при сканировании больших объемов данных с селективными фильтрами.