Procesamiento de consultas sin optimización de PREWHERE
① La consulta incluye un filtro en la columna
town, que forma parte de la clave primaria de la tabla y, por lo tanto, también del índice primario.
② Para acelerar la consulta, ClickHouse carga en memoria el índice primario de la tabla.
③ Examina las entradas del índice para identificar qué gránulos de la columna town podrían contener filas que coincidan con el predicado.
④ Estos gránulos potencialmente relevantes se cargan en memoria, junto con los gránulos alineados posicionalmente de cualquier otra columna necesaria para la consulta.
⑤ Luego, los filtros restantes se aplican durante la ejecución de la consulta.
Como puede ver, sin PREWHERE, todas las columnas potencialmente relevantes se cargan antes del filtrado, incluso si solo unas pocas filas coinciden realmente.
Cómo PREWHERE mejora la eficiencia de la consulta
① La consulta incluye un filtro sobre la columna
town, que forma parte de la clave primaria de la tabla y, por lo tanto, también del índice primario.
② De forma similar a la ejecución sin la cláusula PREWHERE, para acelerar la consulta, ClickHouse carga el índice primario en memoria,
③ y luego examina las entradas del índice para identificar qué gránulos de la columna town podrían contener filas que coincidan con el predicado.
Ahora, gracias a la cláusula PREWHERE, el siguiente paso es diferente: en lugar de leer por adelantado todas las columnas relevantes, ClickHouse filtra los datos columna por columna y solo carga lo que realmente necesita. Esto reduce drásticamente la E/S, especialmente en tablas anchas.
En cada paso, solo carga los gránulos que contienen al menos una fila que superó, es decir, coincidió con, el filtro anterior. Como resultado, el número de gránulos que se cargan y evalúan para cada filtro disminuye de forma monótona:
Paso 1: Filtrado por townClickHouse comienza el procesamiento de PREWHERE ① leyendo los gránulos seleccionados de la columna
town y comprobando cuáles contienen realmente filas que coinciden con London.
En nuestro ejemplo, todos los gránulos seleccionados coinciden, por lo que ② los gránulos alineados posicionalmente correspondientes de la siguiente columna de filtro, date, se seleccionan para su procesamiento:
Paso 2: Filtrado por date
A continuación, ClickHouse ① lee los gránulos seleccionados de la columna
date para evaluar el filtro date > '2024-12-31'.
En este caso, dos de los tres gránulos contienen filas coincidentes, por lo que ② solo se seleccionan para su posterior procesamiento sus gránulos alineados posicionalmente de la siguiente columna de filtro, price:
Paso 3: Filtrado por price
Por último, ClickHouse ① lee los dos gránulos seleccionados de la columna
price para evaluar el último filtro price > 10_000.
Solo uno de los dos gránulos contiene filas coincidentes, por lo que ② solo es necesario cargar su gránulo alineado posicionalmente de la columna del SELECT, street, para continuar el procesamiento:
En el paso final, solo se carga el conjunto mínimo de gránulos de columna: los que contienen filas coincidentes. Esto se traduce en un menor uso de memoria, menos E/S de disco y una ejecución más rápida de la consulta.
PREWHERE reduce los datos leídos, no las filas procesadasTen en cuenta que ClickHouse procesa el mismo número de filas tanto en la versión de la consulta con PREWHERE como en la versión sin PREWHERE. Sin embargo, cuando se aplican optimizaciones de PREWHERE, no es necesario cargar todos los valores de las columnas para cada fila procesada.
La optimización de PREWHERE se aplica automáticamente
optimize_move_to_prewhere está habilitada (true de forma predeterminada), ClickHouse mueve automáticamente las condiciones de filtro de WHERE a PREWHERE, priorizando las que más reducen el volumen de lectura.
La idea es que las columnas más pequeñas se escanean más rápido y, para cuando se procesan las columnas más grandes, la mayoría de los gránulos ya se han descartado. Como todas las columnas tienen el mismo número de filas, el tamaño de una columna viene determinado principalmente por su tipo de datos; por ejemplo, una columna UInt8 suele ser mucho más pequeña que una columna String.
De forma predeterminada, ClickHouse sigue esta estrategia desde la versión 23.2, ordenando las columnas de filtro de PREWHERE para el procesamiento en varias etapas en orden ascendente de tamaño sin comprimir.
A partir de la versión 23.11, las estadísticas de columna opcionales pueden mejorar aún más este proceso al elegir el orden de procesamiento de los filtros en función de la selectividad real de los datos, y no solo del tamaño de la columna.
Cómo medir el impacto de PREWHERE
optimize_move_to_prewhere setting habilitada.
Empezamos ejecutando la consulta con la opción optimize_move_to_prewhere deshabilitada:
optimize_move_to_prewhere activado. (Tenga en cuenta que este ajuste es opcional, ya que está activado de forma predeterminada):
Puntos clave
- PREWHERE evita leer datos de columnas que luego se filtrarán, lo que ahorra E/S y memoria.
- Funciona automáticamente cuando
optimize_move_to_prewhereestá habilitado (de forma predeterminada). - El orden de los filtros importa: las columnas pequeñas y selectivas deben ir primero.
- Use
EXPLAINy los logs para comprobar que PREWHERE se aplica y entender su efecto. - PREWHERE ofrece mayor impacto en tablas anchas y en lecturas amplias con filtros selectivos.