sql >> Base de Datos >  >> RDS >> PostgreSQL

Consulta optimizada con OFFSET en una tabla grande

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:

  1. La ROW valores en WHERE 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_x

    Eso 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.

  2. 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 en ORDER 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"