sql >> Database >  >> RDS >> PostgreSQL

Zoekopdracht optimaliseren met OFFSET op grote tafel

Een grote OFFSET gaat altijd langzaam. Postgres moet alle rijen ordenen en de zichtbare . tellen degenen tot uw offset. Alle vorige rijen direct overslaan je zou een geïndexeerd row_number kunnen toevoegen naar de tafel (of maak een MATERIALIZED VIEW inclusief genoemd row_number ) en werk met WHERE row_number > x in plaats van OFFSET x .

Deze benadering is echter alleen zinvol voor alleen-lezen (of meestal) gegevens. Hetzelfde implementeren voor tabelgegevens die gelijktijdig kunnen veranderen is uitdagender. U moet beginnen met het definiëren van gewenst gedrag precies .

Ik stel een andere benadering voor voor paginering :

SELECT *
FROM   big_table
WHERE  (vote, id) > (vote_x, id_x)  -- ROW values
ORDER  BY vote, id  -- needs to be deterministic
LIMIT  n;

Waar vote_x en id_x zijn van de laatste rij van de vorige pagina (voor beide DESC en ASC ). Of vanaf de eerste als u achteruit navigeert .

Het vergelijken van rijwaarden wordt ondersteund door de index die u al heeft - een functie die voldoet aan de ISO SQL-standaard, maar niet elk RDBMS ondersteunt dit.

CREATE INDEX vote_order_asc ON big_table (vote, id);

Of voor aflopende volgorde:

SELECT *
FROM   big_table
WHERE  (vote, id) < (vote_x, id_x)  -- ROW values
ORDER  BY vote DESC, id DESC
LIMIT  n;

Kan dezelfde index gebruiken.
Ik raad u aan uw kolommen NOT NULL te declareren of maak kennis met de NULLS FIRST|LAST constructie:

  • PostgreSQL sorteren op datum/tijd asc, eerst null?

Let op twee dingen in het bijzonder:

  1. De ROW waarden in de WHERE clausule kan niet worden vervangen door gescheiden lidvelden. WHERE (vote, id) > (vote_x, id_x) kan niet worden vervangen door:

    WHERE  vote >= vote_x
    AND    id   > id_x

    Dat zou alles uitsluiten rijen met id <= id_x , terwijl we dat alleen voor dezelfde stemming willen doen en niet voor de volgende. De juiste vertaling zou zijn:

    WHERE (vote = vote_x AND id > id_x) OR vote > vote_x
    

    ... die niet zo goed samengaat met indexen, en steeds ingewikkelder wordt voor meer kolommen.

    Zou eenvoudig zijn voor een single kolom, uiteraard. Dat is het speciale geval dat ik in het begin noemde.

  2. De techniek werkt niet voor gemengde richtingen in ORDER BY zoals:

    ORDER  BY vote ASC, id DESC
    

    Ik kan tenminste geen generiek bedenken manier om dit zo efficiënt mogelijk te implementeren. Als ten minste één van beide kolommen een numeriek type is, kunt u een functionele index gebruiken met een omgekeerde waarde op (vote, (id * -1)) - en gebruik dezelfde uitdrukking in ORDER BY :

    ORDER  BY vote ASC, (id * -1) ASC
    

Gerelateerd:

  • SQL-syntaxisterm voor 'WHERE (col1, col2) <(val1, val2)'
  • Verbeter de prestaties voor volgorde door met kolommen uit veel tabellen

Let in het bijzonder op de presentatie van Markus Winand waarnaar ik heb gelinkt:

  • "Paginering gedaan op de PostgreSQL-manier"


  1. Verschil tussen tijdstempels in milliseconden in Oracle

  2. Somwaarden van multidimensionale array per sleutel zonder lus

  3. Hoe SQL-cursors voor speciale doeleinden te gebruiken

  4. Wat kan ervoor zorgen dat een Oracle ROWID verandert?