Hej alla! Jag Àr backendutvecklare och skriver mikrotjÀnster i Java + Spring. Jag arbetar i ett av utvecklingsteamen för interna produkter inom Tinkoff.

I vĂ„rt team dyker frĂ„gan om optimering av förfrĂ„gningar i databaser ofta upp. Man vill alltid ha det lite snabbare, men man kan inte alltid förlita sig pĂ„ noggrant uppbyggda index â man mĂ„ste leta efter alternativa lösningar. Under ett av dessa nĂ€tövriga sökningar efter rimliga optimeringar nĂ€r man arbetar med databaser fann jag , författare till boken SQL Performance Explained. Det Ă€r den sĂ€llsynta typen av bloggar dĂ€r man kan lĂ€sa alla artiklar i följd.
Jag vill översÀtta en liten artikel av Markus för er. Den kan kallas ett slags manifest som syftar till att dra uppmÀrksamhet till det gamla, men fortfarande aktuella problemet med prestanda för offset-operationen enligt SQL-standarden.
PÄ vissa stÀllen kommer jag att komplettera författaren med förklaringar och anteckningar. Alla sÄdana stÀllen kommer jag att mÀrka som «anm.» för ökad tydlighet
En kort introduktion
Jag tror att mÄnga vet hur problematiskt och trögt det kan vara att arbeta med sidbaserade urval via offset. Men visste ni att det gÄr att ganska enkelt ersÀtta med en mer effektiv konstruktion?
SÄ, nyckelordet offset anger för databasen att hoppa över de första n posterna i förfrÄgan. Men databasen mÄste fortfarande lÀsa dessa första n poster frÄn disken, i angiven ordning (anm.: tillÀmpa sortering om den Àr angiven), och först efter det Àr det möjligt att Äterge posterna frÄn och med n+1 och framÄt. Det mest intressanta Àr att problemet inte ligger i den specifika implementeringen i databasen, utan i den ursprungliga definitionen enligt standarden:
âŠthe rows are first sorted according to the and then limited by dropping the number of rows specified in the from the beginningâŠ
-SQL:2016, Part 2, 4.15.3 Deriverade tabeller (anm.: just nu den mest anvÀnda standarden)
Huvudpunkten hĂ€r Ă€r att offset tar ett enda parameter â antalet poster som ska hoppas över, och det Ă€r allt. Enligt en sĂ„dan definition kan databasen bara hĂ€mta alla poster och sedan kasta bort de onödiga. Det Ă€r uppenbart att en sĂ„dan definition av offset tvingar fram onödigt arbete. Och det spelar ingen roll om det Ă€r SQL eller NoSQL.
Lite mer smÀrta
Offset-problemet slutar inte hÀr, och hÀr Àr varför. Om en annan operation lÀgger till en ny post mellan lÀsningen av tvÄ datorsidor, vad hÀnder i sÄ fall?

NÀr offset anvÀnds för att hoppa över poster frÄn tidigare sidor, finns det en sannolikhet att fÄ dubbletter i situationen dÀr en ny post lÀggs till mellan lÀsningarna av olika sidor (obs: detta kan hÀnda nÀr vi lÀser sida för sida med en order by-konstruktion, sÄ att en ny post kan hamna mitt i vÄr resultatlista).
Diagrammet illustrerar tydligt en sÄdan situation. Databasen lÀser de första 10 posterna, sedan lÀggs en ny post till, vilket skiftar alla tidigare lÀsta poster med 1. DÀrefter tar databasen den nya sidan med 10 följande poster och börjar inte med den 11:e, som den ska, utan med den 10:e, vilket duplicerar denna post. Det finns Àven andra anomalier kopplade till anvÀndningen av detta uttryck, men detta Àr den vanligaste.
Som vi redan har klargjort Àr detta inte ett problem specifikt för nÄgon databas eller dess implementationer. Problemet ligger i definitionen av paginering enligt SQL-standarden. Vi sÀger till databasen vilken sida som behöver hÀmtas eller hur mÄnga poster som ska hoppas över. Men databasen har helt enkelt inte tillrÀckligt med information för att optimera sÄdan begÀran.
Det bör ocksÄ klargöras att detta problem inte beror pÄ nÄgot specifikt nyckelord, utan rÀttare sagt pÄ semantiken i frÄgan. Det finns flera identiska syntaxer med liknande problematik:
- Nyckelordet offset, som nÀmnts tidigare.
- Konstruktionen med tvÄ nyckelord limit [offset] (Àven om limit i sig inte Àr sÄ dÄlig).
- Filtrering baserad pÄ nedre grÀnser, uppbyggd pÄ radnummer (till exempel row_number(), rownum, etc.).
Alla dessa uttryck sÀger bara hur mÄnga rader som ska hoppas över, utan nÄgon ytterligare information eller kontext.
I denna artikel anvÀnds nyckelordet offset som en generalisering av alla dessa varianter.
Liv utan OFFSET
Och nu kan vi förestÀlla oss hur vÄr vÀrld skulle se ut utan alla dessa problem. Det visar sig att livet utan offset inte Àr sÄ svÄrt: med en select-frÄga kan du vÀlja endast de rader som vi Ànnu inte har sett (obs: det vill sÀga de som inte fanns pÄ föregÄende sida), med hjÀlp av ett villkor i where.
I det hÀr fallet utgÄr vi frÄn det faktum att selektering utförs över en sorterad mÀngd (den gamla goda order by). Eftersom vi har en sorterad mÀngd kan vi anvÀnda ett ganska enkelt filter för att bara hÀmta de data som ligger efter den senaste posten pÄ föregÄende sida:
SELECT ...
FROM ...
WHERE ...
AND id < ?last_seen_id
ORDER BY id DESC
FETCH FIRST 10 ROWS ONLYDet Àr hela principen för detta tillvÀgagÄngssÀtt. SjÀlvklart blir det mer komplicerat nÀr man sorterar efter flera kolumner, men grundidén Àr densamma. Det Àr viktigt att notera att denna konstruktion Àr tillÀmplig i mÄnga -lösningar.
Denna metod kallas seek-metod eller keyset-paginering. Den löser problemet med flytande resultat (exempel: situationen med poster mellan sidlÀsningar som beskrevs tidigare) och, som vi alla gillar, fungerar den snabbare och mer stabilt Àn klassisk offset. Stabiliteten ligger i att bearbetningstiden för förfrÄgan inte ökar i proportion till numret pÄ den begÀrda tabellen (exempel: om man vill veta mer om olika metoder för paginering kan man . DÀr kan man ocksÄ hitta jÀmförande benchmark för olika metoder).
En av bilderna Och vad med verktygen?
Paginering baserad pÄ nycklar passar ofta inte pÄ grund av bristande verktygsstöd för denna metod. De flesta utvecklingsverktyg, inklusive olika ramverk, erbjuder ingen möjlighet att vÀlja hur pagineringen ska utföras.
Situationen förvĂ€rras av att den beskrivna metoden krĂ€ver genomgĂ„ende stöd i de anvĂ€nda teknologierna â frĂ„n databashanterare till utförandet av AJAX-förfrĂ„gningar i webblĂ€saren vid oĂ€ndlig skrollning. IstĂ€llet för att bara ange sidnumret mĂ„ste man nu ange en uppsĂ€ttning nycklar för alla sidor samtidigt.
Situationen förvÀrras av det faktum att den beskrivna metoden krÀver genomgripande stöd i de teknologier som anvÀnds - frÄn databashanteringssystemet till utförandet av AJAX-förfrÄgningar i webblÀsaren vid oÀndlig skrollning. IstÀllet för att bara ange sidanumret mÄste man nu ange en uppsÀttning nycklar för alla sidor pÄ en gÄng.
Antalet ramverk som stöder paginering pÄ nycklar ökar gradvis. HÀr Àr vad som finns för tillfÀllet:
- för Java;
- för Ruby;
- och för Django;
- för Python;
- â kriterie-API för implementationer av JPA;
- för Perl;
- , ĐŒĐ°ĐżĐ”Ń ĐŽĐ»Ń Node.js .
(Obs.: vissa lĂ€nkar har tagits bort eftersom nĂ„gra bibliotek inte har uppdaterats sedan 2017â2018 vid tidpunkten för översĂ€ttningen. Om du Ă€r intresserad kan du kolla in kĂ€llan.)
Det Àr just pÄ detta omrÄde som du behövs. Om du utvecklar eller underhÄller ett ramverk som pÄ nÄgot sÀtt anvÀnder paginering, ber jag dig, jag uppmanar dig, jag vÀdjar till dig att göra inhemskt stöd för paginering pÄ nycklar. Om du har frÄgor eller behöver hjÀlp, hjÀlper jag gÀrna till (, , ) (Obs.: utifrÄn min erfarenhet av att kommunicera med Markus kan jag sÀga att han verkligen Àr entusiastisk över att sprida detta Àmne).
Om du dÀremot anvÀnder fÀrdiga lösningar som du anser förtjÀnar stöd för paginering pÄ nycklar, skapa ett förslag eller till och med erbjud en fÀrdig lösning om möjligt. Du kan ocksÄ hÀnvisa till denna artikel i lÀnken.
Slutsats
Anledningen till att en sĂ„ enkel och anvĂ€ndbar metod som paginering pĂ„ nycklar Ă€r sĂ€llsynt beror inte pĂ„ att det Ă€r tekniskt svĂ„rt eller krĂ€ver stora anstrĂ€ngningar. Den frĂ€msta anledningen Ă€r att mĂ„nga Ă€r vana vid att se och arbeta med offset â denna metod dikteras av sjĂ€lva standarden.
Som en följd av detta funderar fÄ pÄ att Àndra sin metod för paginering, och dÀrför utvecklas verktygsstödet frÄn ramverk och bibliotek svagt. SÄ om du Àr för idén och syftet med offsetsfri paginering, hjÀlp till att sprida den!
KĂ€lla:
Författare: Markus Winand
KĂ€lla: habr.com
