Un OFFSET
grande siempre va a ser lento. Postgres tiene que ordenar todas las filas y contar las visibles unos hasta su compensación. Para omitir todas las filas anteriores directamente podría agregar un row_number
indexado a la tabla (o crear una MATERIALIZED VIEW
incluyendo dicho row_number
) y trabajar con WHERE row_number > x
en lugar de OFFSET x
.
Sin embargo, este enfoque solo es sensato para datos de solo lectura (o en su mayoría). Implementar lo mismo para los datos de la tabla que pueden cambiar concurrentemente es más desafiante. Debe comenzar definiendo el comportamiento deseado exactamente .
Sugiero un enfoque diferente para paginación :
SELECT *
FROM big_table
WHERE (vote, id) > (vote_x, id_x) -- ROW values
ORDER BY vote, id -- needs to be deterministic
LIMIT n;
Donde vote_x
y id_x
son de la última fila de la página anterior (para ambos DESC
y ASC
). O desde el primero si navega hacia atrás .
La comparación de valores de fila es compatible con el índice que ya tiene, una característica que cumple con el estándar ISO SQL, pero no todos los RDBMS lo admiten.
CREATE INDEX vote_order_asc ON big_table (vote, id);
O por orden descendente:
SELECT *
FROM big_table
WHERE (vote, id) < (vote_x, id_x) -- ROW values
ORDER BY vote DESC, id DESC
LIMIT n;
Puede usar el mismo índice.
Le sugiero que declare sus columnas NOT NULL
o familiarícese con los NULLS FIRST|LAST
construir:
- Ordenar PostgreSQL por fechahora asc, ¿null primero?
Tenga en cuenta dos cosas en particular:
-
La
ROW
valores enWHERE
La cláusula no se puede reemplazar con campos de miembros separados.WHERE (vote, id) > (vote_x, id_x)
no se puede ser reemplazado por:WHERE vote >= vote_x AND id > id_xEso descartaría todos filas con
id <= id_x
, mientras que solo queremos hacer eso para el mismo voto y no para el siguiente. La traducción correcta sería:WHERE (vote = vote_x AND id > id_x) OR vote > vote_x
... que no funciona tan bien con los índices y se vuelve cada vez más complicado para más columnas.
Sería simple para un single columna, obviamente. Ese es el caso especial que mencioné al principio.
-
La técnica no funciona para direcciones mixtas en
ORDER BY
como:ORDER BY vote ASC, id DESC
Al menos no puedo pensar en un genérico manera de implementar esto tan eficientemente. Si al menos una de las dos columnas es de tipo numérico, podría usar un índice funcional con un valor invertido en
(vote, (id * -1))
- y usa la misma expresión enORDER BY
:ORDER BY vote ASC, (id * -1) ASC
Relacionado:
- Término de sintaxis SQL para 'WHERE (col1, col2) <(val1, val2)'
- Mejore el rendimiento para ordenar con columnas de muchas tablas
Tenga en cuenta en particular la presentación de Markus Winand I vinculado a:
- "Paginación realizada al estilo de PostgreSQL"