Idag blir det inga komplexa fall eller snĂ„riga algoritmer i SQL. Allt kommer att vara vĂ€ldigt enkelt, pĂ„ nivĂ„ med Kapten Upplysning â vi gör en visning av hĂ€ndelseregistret med sortering efter tid.
Det vill sĂ€ga, hĂ€r har vi en tabell i databasen events, och den har ett fĂ€lt ts â exakt den tid vi vill visa dessa poster ordnat:
CREATE TABLE events(
id
serial
PRIMARY KEY
, ts
timestamp
, data
json
);
CREATE INDEX ON events(ts DESC);SjÀlvklart kommer det att finnas fler Àn ett dussin poster, sÄ vi behöver pÄ nÄgot sÀtt sida navigering.
#0. «Я Ń ĐŒĐ°ĐŒŃ ĐżĐŸĐłŃĐŸĐŒĐŒĐžŃŃ»
cur.execute("SELECT * FROM events;")
rows = cur.fetchall();
rows.sort(key=lambda row: row.ts, reverse=True);
limit = 26
print(rows[offset:offset+limit]);
Det Ă€r nĂ€stan ingen skĂ€mt â det dyker sĂ€llan upp i det vilda. Ibland kan det vara svĂ„rt att stĂ€lla om frĂ„n ORM till "direkt" arbete med SQL.
Men lÄt oss gÄ vidare till mer vanliga och mindre uppenbara problem.
#1. OFFSET
SELECT
...
FROM
events
ORDER BY
ts DESC
LIMIT 26 OFFSET $1; -- 26 - poster pÄ sidan, $1 - början pÄ sidanVar kom siffran 26 ifrÄn? Det Àr den ungefÀrliga mÀngden poster för att fylla en skÀrm. NÀrmare bestÀmt 25 visade poster, plus 1, som signalerar att det fortfarande finns nÄgot mer i urvalet och att det Àr vÀrt att gÄ vidare.
SjĂ€lvklart kan detta vĂ€rde inte "bakas in" i frĂ„gan, utan överföras via en parameter. Men i sĂ„ fall kan PostgreSQL:s planering inte lita pĂ„ att det kommer att finnas fĂ„ poster â och kan lĂ€tt vĂ€lja en ineffektiv plan.
Och sĂ„ lĂ€nge visningen av hĂ€ndelseregistret i applikationen Ă€r implementerad som en vĂ€xling mellan visuella "sidor", lĂ€gger ingen lĂ€nge mĂ€rke till nĂ„got misstĂ€nkt. Fram till dess att man i jakten pĂ„ anvĂ€ndarvĂ€nlighet UI/UX bestĂ€mmer sig för att göra om grĂ€nssnittet till "oĂ€ndlig scroll" â det vill sĂ€ga alla registreringsposter visas som en lista som anvĂ€ndaren kan scrolla upp och ner.
Och nu, vid nÀsta testning fÄngas du pÄ duplicering av poster i registret. Varför, eftersom det finns ett normalt index i tabellen (ts), som din frÄga förlitar sig pÄ?
Exakt dĂ€rför att du inte tog hĂ€nsyn till att ts inte Ă€r en unik nyckel i denna tabell. I sjĂ€lva verket Ă€r vĂ€rdena inte unika, som med all "tid" i verkliga förhĂ„llanden â dĂ€rför kan samma post i tvĂ„ angrĂ€nsande förfrĂ„gningar lĂ€tt "hoppa" frĂ„n sida till sida pĂ„ grund av en annan slutlig ordning i sorteringen av samma nyckelvĂ€rdesresultat.
Faktum Ă€r att hĂ€r döljer sig Ă€ven ett andra problem, som Ă€r mycket svĂ„rare att upptĂ€cka â vissa poster kommer inte att visas överhuvudtaget! Eftersom de "dubbelnoterade" posterna tog upp nĂ„gon annans plats. En detaljerad förklaring med fina bilder kan .
Utöka indexet
Den smarta utvecklaren förstĂ„r â man mĂ„ste göra indexnyckeln unik, och det enklaste sĂ€ttet Ă€r att utöka den med ett medvetet unikt fĂ€lt, dĂ€r primĂ€rnyckel (PK) passar utmĂ€rkt:
SKAPA UNIKT INDEX PĂ
events(ts DESC, id DESC);Och frÄgan muterar:
VĂLJ
...
ORDER BY
ts DESC, id DESC
LIMIT 26 OFFSET $1;#2. ĐĐ”ŃĐ”Ń ĐŸĐŽ ĐœĐ° «ĐșŃŃŃĐŸŃŃ»
Ett tag senare kommer DBA:en till dig och "glÀder" dig med att dina frÄgor , och överlag Àr det dags att övergÄ till navigering frÄn det senaste visade vÀrdet. Din frÄga muterar igen:
VĂLJ
...
DĂR
(ts, id) < ($1, $2) -- senaste mottagna vÀrden frÄn föregÄende steg
ORDER BY
ts DESC, id DESC
LIMIT 26;Du suckade lÀttat, tills det hÀnde...
#3. ЧОŃŃĐșа ĐžĐœĐŽĐ”ĐșŃĐŸĐČ
För en dag lĂ€ste din DBA och insĂ„g att "inte sista" tidsstĂ€mpel â det Ă€r inte bra. Och han kom Ă„ter till dig â den hĂ€r gĂ„ngen med tanken att det indexet verkligen borde förvandlas tillbaka till (ts DESC).
Men vad ska man göra med det ursprungliga problemet med "hoppen" mellan sidorna?.. Jo, allt Ă€r enkelt â man mĂ„ste vĂ€lja block med obestĂ€mt antal poster!
Vem förbjuder oss att lĂ€sa inte "exakt 26", utan "minst 26"? Till exempel sĂ„ att i nĂ€sta block hamnar poster med medvetet andra vĂ€rden ts â dĂ„ kommer det inte att finnas nĂ„got problem med "hoppen" mellan blocken!
SÄ hÀr gör man för att uppnÄ det:
VĂLJ
...
DĂR
ts = coalesce(
VĂLJ
ts
FRĂ
N
events
DĂR
ts < $1
ORDER BY
ts DESC
LIMIT 1 OFFSET 25
), '-oÀndlighet')
ORDER BY
ts DESC;Vad hÀnder hÀr överhuvudtaget?
- Vi kliver ner 25 poster och fÄr det "grÀnsvÀrde"
ts. - Om det inte finns nÄgot dÀr, byter vi NULL-vÀrdet mot
-oÀndlighet. - Vi berÀknar hela segmentet av vÀrden mellan det erhÄllna vÀrdet
tsoch parametern $1 som skickats frĂ„n grĂ€nssnittet (det tidigare "senaste" renderade vĂ€rdet). - Om blocket har fĂ€rre Ă€n 26 poster â Ă€r det det sista.
Eller samma sak med en bild:

Eftersom vi nu har urvalet har inget specifikt "början", hindrar inget oss frĂ„n att "utveckla" denna frĂ„ga bakĂ„t och genomföra dynamisk inlĂ€sning av datablokker frĂ„n "referenspunkt" i bĂ„da riktningar â bĂ„de nedĂ„t och uppĂ„t.
AnmÀrkning
- Ja, i sÄ fall kontaktar vi indexet tvÄ gÄnger, men allt sker "rent enligt index". DÀrför kommer den inbÀddade frÄgan att leda till endast en extra Index Only Scan.
- Det Àr ganska uppenbart att denna metodik bara kan anvÀndas nÀr ni har vÀrden
tskan korsa varandra endast av en slump, och det Àr inte mÄnga. Men om ert typiska fall Àr "en miljon poster pÄ 00:00:00.000", bör ni inte göra sÄ. Med andra ord, ni bör inte tillÄta ett sÄdant fall. Men om det nu har blivit sÄ, anvÀnd alternativet med det utvidgade indexet.
KĂ€lla: habr.com
