Nykyään SQL:ssä ei ole monimutkaisia tapauksia ja kehittyneitä algoritmeja. Kaikki on hyvin yksinkertaista kapteeni Ilmeisen tasolla - tehdään se tapahtumarekisterin katselu ajan mukaan lajiteltuna.
Eli tietokannassa on merkki events
, ja hänellä on kenttä ts
- tarkalleen milloin haluamme näyttää nämä tietueet järjestyksessä:
CREATE TABLE events(
id
serial
PRIMARY KEY
, ts
timestamp
, data
json
);
CREATE INDEX ON events(ts DESC);
On selvää, että meillä ei ole siellä tusinaa levyä, joten tarvitsemme jonkinlaisen sivun navigointi.
#0. "Olen äitini pogromisti"
cur.execute("SELECT * FROM events;")
rows = cur.fetchall();
rows.sort(key=lambda row: row.ts, reverse=True);
limit = 26
print(rows[offset:offset+limit]);
Se ei ole läheskään vitsi - se on harvinainen, mutta luonnosta löytyy. Joskus ORM:n kanssa työskentelyn jälkeen voi olla vaikeaa siirtyä "suoraan" työhön SQL:n kanssa.
Mutta siirrytään yleisempiin ja vähemmän ilmeisiin ongelmiin.
#1. OFFSET
SELECT
...
FROM
events
ORDER BY
ts DESC
LIMIT 26 OFFSET $1; -- 26 - записей на странице, $1 - начало страницы
Mistä numero 26 tuli? Tämä on likimääräinen merkintöjen määrä yhden näytön täyttämiseen. Tarkemmin sanottuna 25 näytettävää tietuetta plus 1 merkkinä siitä, että näytteessä on ainakin jotain muuta ja on järkevää jatkaa eteenpäin.
Tätä arvoa ei tietenkään voida "ommella" pyynnön runkoon, vaan se voidaan siirtää parametrin kautta. Mutta tässä tapauksessa PostgreSQL-aikataulu ei voi luottaa siihen, että tietueita pitäisi olla suhteellisen vähän - ja valitsee helposti tehottoman suunnitelman.
Ja vaikka sovellusliittymässä rekisterin katselu toteutetaan visuaalisten "sivujen" välillä vaihtamisena, kukaan ei huomaa mitään epäilyttävää pitkään aikaan. Täsmälleen siihen hetkeen asti, jolloin UI/UX päättää mukavuustaistelussa muuttaa käyttöliittymän "loputtomaan vieritykseen" - eli kaikki rekisterimerkinnät piirretään yhteen luetteloon, jota käyttäjä voi vierittää ylös ja alas.
Ja niin seuraavan testin aikana jäät kiinni tietueiden kopiointi rekisterissä. Miksi, koska taulukossa on normaali indeksi (ts)
, johon kyselysi perustuu?
Juuri siksi, että et ottanut sitä huomioon ts
ei ole ainutlaatuinen avain tässä taulukossa. Itse asiassa ja sen arvot eivät ole ainutlaatuisia, kuten mikä tahansa "aika" todellisissa olosuhteissa - siksi sama tietue kahdessa vierekkäisessä kyselyssä "hyppää" helposti sivulta sivulle erilaisen lopullisen järjestyksen vuoksi saman avainarvon lajittelun puitteissa.
Itse asiassa tässä on piilotettu myös toinen ongelma, jota on paljon vaikeampi havaita - joitain merkintöjä ei näytetä ollenkaan! Loppujen lopuksi "kaksoiskappaleet" ottivat jonkun toisen paikan. Yksityiskohtainen selitys kauniilla kuvilla löytyy
Indeksin laajentaminen
Ovela kehittäjä ymmärtää, että indeksiavaimesta on tehtävä ainutlaatuinen, ja helpoin tapa on laajentaa sitä ilmeisen ainutlaatuisella kentällä, johon PK sopii täydellisesti:
CREATE UNIQUE INDEX ON events(ts DESC, id DESC);
Ja pyyntö muuttuu:
SELECT
...
ORDER BY
ts DESC, id DESC
LIMIT 26 OFFSET $1;
#2. Vaihda "kursoriin"
Jonkin ajan kuluttua DBA tulee luoksesi ja on "tyytyväisenä" pyyntöösi
SELECT
...
WHERE
(ts, id) < ($1, $2) -- последние полученные на предыдущем шаге значения
ORDER BY
ts DESC, id DESC
LIMIT 26;
Hengitit helpotuksesta, kunnes se tuli...
#3. Puhdistusindeksit
Koska eräänä päivänä DBA-lukemasi (ts DESC)
.
Mutta mitä tehdä alkuperäisen ongelman kanssa "hyppäämällä" tietueita sivujen välillä?.. Ja kaikki on yksinkertaista - sinun on valittava lohkot, joissa on kiinteä määrä tietueita!
Yleisesti, kuka kieltää meitä lukemasta "täsmälleen 26", vaan "vähintään 26"? Esimerkiksi niin, että seuraavassa lohkossa on tietueita, joilla on selvästi eri merkitys ts
- silloin ei tule olemaan ongelmia tietueiden "hyppäämisessä" lohkojen välillä!
Näin saavutat tämän:
SELECT
...
WHERE
ts < $1 AND
ts >= coalesce((
SELECT
ts
FROM
events
WHERE
ts < $1
ORDER BY
ts DESC
LIMIT 1 OFFSET 25
), '-infinity')
ORDER BY
ts DESC;
Mitä täällä tapahtuu?
- Askelemme 25 tietuetta "alas" ja saamme "raja-arvon".
ts
. - Jos siellä ei ole jo mitään, korvaa NULL-arvo arvolla
-infinity
. - Vähennämme koko arvojen segmentin vastaanotetun arvon väliltä
ts
ja käyttöliittymästä välitetty parametri $1 (edellinen "viimeinen" renderöity arvo). - Jos lohko palautetaan alle 26 tietueella, se on viimeinen.
Tai sama kuva:
Koska nyt meillä on näytteellä ei ole mitään erityistä "alkua", niin mikään ei estä meitä "laajentamasta" tätä pyyntöä vastakkaiseen suuntaan ja toteuttamasta tietolohkojen dynaamista lataamista "viitepisteestä" molempiin suuntiin - sekä alas että ylös.
huomautus
- Kyllä, tässä tapauksessa käytämme hakemistoa kahdesti, mutta kaikki tapahtuu "puhtaasti indeksillä". Siksi alikysely johtaa vain yhteen ylimääräiseen vain hakemistoskannaukseen.
- On aivan selvää, että tätä tekniikkaa voidaan käyttää vain, kun sinulla on arvot
ts
voi ylittää vain sattumalta, ja niitä ei ole montaa. Jos tyypillinen tapauksesi on "miljoona tietuetta klo 00:00:00.000", sinun ei pitäisi tehdä tätä. Tarkoitan, sinun ei pitäisi antaa tällaisen tapauksen tapahtua. Mutta jos näin tapahtuu, käytä vaihtoehtoa laajennetulla indeksillä.
Lähde: will.com