I den här artikeln vill jag presentera ICU-stödet i PostgreSQL, som jag har arbetat med för PostgreSQL version 10, för att dyka upp senare i år.
Sortering
Sortering är en viktig funktion i ett databassystem. För det första vill användare i allmänhet se data sorterad. Alla frågeresultat som innehåller mer än en rad och är avsedda för slutanvändares konsumtion kommer förmodligen att vilja sorteras, bara för en bättre användarupplevelse. För det andra beror mycket av den interna funktionaliteten i ett databassystem på att sortera data eller ha sorterad data tillgänglig. B-trädsindex är ett uppenbart exempel. BRIN-index har kunskap om ordning och reda. Områdespartitionering måste jämföra värden. Sammanfogningar beror på sorterad indata. Tanken som är gemensam för dessa olika tekniker är att grovt sett, om du har sorterat data och vet vad du letar efter, så gör det det mycket snabbare att hitta platsen där den ska finnas.
Det finns två viktiga aspekter på sortering. En är sorteringsalgoritmen. Detta är ett standardämne inom datavetenskap, och mycket arbete har lagts ner på PostgreSQL genom åren för att förfina de olika sorteringsalgoritmerna och metoderna, men det är inte vad jag kommer att skriva om. Den andra är att bestämma i vilken ordning saker och ting ska vara, vilket är vad vi kallar kollation. I många fall är det valet självklart. 1 kommer före 2. FALSK kommer före TRUE … ja, någon bestämde sig bara godtyckligt för det. A kommer vanligtvis före B. Men när det kommer till naturlig språktext blir det intressant. Det finns många olika sätt att ordna text, och de faktiska metoderna för att sortera textsträngar är mer komplicerade än vad som kan vara uppenbart. Olika språk föredrar olika sorteringsordningar, men även inom ett språk kan det finnas variationer för olika applikationer. Och det finns detaljer att oroa sig för, till exempel vad man ska göra med blanksteg, skiljetecken, skiftlägesskillnader, diakritiska tecken och så vidare. Slå upp Unicode Collation Algorithm för mer insikt i detta.
Innan ICU-funktionen togs i bruk, all denna funktionalitet underlättades av C-biblioteket i operativsystemet. PostgreSQL skickar i princip bara strängar till strcmp()
, strcoll()
, och liknande och fungerar med resultatet. C-biblioteken i de olika operativsystemen implementerar de olika sorteringsvarianterna och nyanserna som nämns ovan till olika nivåer av funktionalitet och kvalitet, så PostgreSQL kan göra vad ditt operativsystem kan göra.
Ändra sorteringar
Problem uppstår om operativsystemet någonsin behöver ändra en sortering som det tillhandahåller. Varför skulle de vilja göra det? Det kan vara så att den tidigare sammanställningen var fel och måste åtgärdas. Kanske har en ny standard för ett språk publicerats och sammanställningen ska uppdateras för det. Kanske har den interna representationen av sorterings- och strängdata ändrats av prestandaskäl eller för att det var nödvändigt att implementera ytterligare funktionalitet. För många program är detta inte ett problem. Du kanske bara ser en något annorlunda ordnad produktion, om du märker någon skillnad alls. För ett databassystem är detta dock ett stort problem. Som beskrivits ovan lagrar PostgreSQL sorterad data i index och andra platser och förlitar sig på att sorteringsordningen är korrekt. Om sorteringsordningen inte är korrekt kanske en indexuppslagning inte hittar data som faktiskt finns där. Eller en skrivning till ett index kommer att skriva till en annan plats. Eller data skrivs till eller läses från fel partition. Detta kan leda till felaktiga dubbletter av data eller uppkomsten av dataförlust eftersom data inte finns där den letas efter. Med andra ord kan det leda till datakorruption och (uppenbar) dataförlust.
Tyvärr har vi inte kunnat göra så mycket åt det än så länge. Operativsystem uppdaterar sina kollationer när de känner för det, kanske som en del av en uppgradering av deras C-bibliotekspaket. Det finns inget sätt att ta reda på detta på ett rimligt sätt, eller än kanske genom att inspektera uppdateringspaketen i detalj. Och även då, kommer du att avvisa en viktig uppdatering av ditt C-bibliotek eftersom du märkte att sorteringen i någon lokal du inte använder ändrades? Det var en mycket obekväm situation.
Öppna ICU
Så var kommer ICU in? ICU, International Components for Unicode, är ett bibliotek som tillhandahåller faciliteter för internationalisering och lokalisering, inklusive sortering. Så i det avseendet är det ett alternativ till att använda faciliteterna i standard C-biblioteket. Det fina är att ICU uttryckligen ger några garantier om stabiliteten för kollationer:
- En sortering kommer inte att ändras på ett inkompatibelt sätt som en del av en mindre versionsuppdatering.
- En sortering har en version som kan inspekteras, och när en sortering ändras på ett inkompatibelt sätt ändras versionen.
För användare av PostgreSQL innebär detta i praktiken:
- Rutinmässiga uppdateringar av operativsystempaketet kommer inte att störa giltigheten av sorterade data. Sedan en
postgres
binär är länkad till en viss huvudversion avlibicu
, rutinmässiga paketuppgraderingar av operativsystem kommer inte att sluta medpostgres
är länkad till en ny huvudversion avlibicu
, så länge som a) du inte uppdaterar PostgreSQL-paketen, eller b) PostgreSQL-paketen fortfarande är länkade till samma huvudversion av ICU som tidigare. Paketörer måste vara noga med att underhålla detta på rätt sätt, men det borde inte vara alltför problematiskt i praktiken. - När större paket- och operativsystemuppgraderingar ändrar versionen av en sortering, har vi ett sätt att upptäcka det och varna användaren. Just nu varnar vi bara och erbjuder några riktlinjer och verktyg för att fixa saker, men i framtiden kan vi förfina och automatisera detta ytterligare.
(För att göra detta mer explicit för paketerare:I en stabil gren av ditt operativsystem bör du inte ändra den stora ICU-versionen som en given PostgreSQL-paketuppsättning är länkad till.)
Använda ICU
För att kunna använda detta måste PostgreSQL byggas explicit med ICU-stöd. När du bygger från källkod, använd ./configure --with-icu
tillsammans med andra önskade alternativ. Vi förväntar oss att de flesta större binära paket också erbjuder detta som standard. När detta är gjort erbjuds ICU-baserade sorteringar vid sidan av de libc-baserade sammanställningar som tidigare utgåvor erbjöd. (Så att bygga med ICU-stöd tar inte bort libc-sorteringsstöd, de två finns tillsammans.) Kontrollera dokumentationen för detaljer om hur man väljer en ICU-baserad sortering kontra en libc-baserad. Till exempel, om du tidigare hade angett
CREATE TABLE ... (... x text COLLATE "en_US" ...)
du kanske nu gör
CREATE TABLE ... (... x text COLLATE "en-x-icu" ...)
Detta bör ge dig ungefär samma användarsynliga beteende som tidigare, förutom att din databas kommer att vara mer framtidssäker när det kommer till uppgradering. (På Linux/glibc bör sorteringsordningen vara i stort sett densamma, men det kan finnas små skillnader i vissa detaljer. Om du däremot använder ett operativsystem vars C-bibliotek inte alls stöder Unicode-kollation, såsom macOS eller äldre versioner av FreeBSD, då kommer detta att vara en stor förändring — till det bättre.)
För närvarande är ICU-stöd endast tillgängligt för explicit specificerade sammanställningar. Standardsorteringen i en databas tillhandahålls fortfarande alltid av C-biblioteket. Att ta itu med detta är ett framtida projekt.
Om du uppgraderar en sådan databas med pg_upgrade
till exempel till en ny PostgreSQL-installation som är länkad till en nyare huvudversion av ICU som har ändrat sorteringsversionen av den sorteringen du använder, då får du en varning och måste fixa till exempel eventuella index som beror på sammanställning. Instruktioner för detta finns också i dokumentationen.
Förkortade nycklar
Så denna förändring kommer att ge några mycket viktiga förbättringar för långsiktig robusthet av ett databassystem. Men ICU är också en förbättring jämfört med system C-biblioteket på andra områden.
Till exempel kan PostgreSQL B-träd lagra vad som kallas förkortade nycklar för att förbättra prestanda och lagring. För textsträngdatatyper, med standard C-bibliotek, skulle vi beräkna dessa förkortade nycklar med strxfrm()
fungera. Vi har dock lärt oss att många C-bibliotek har en mängd olika buggar och felaktiga beteenden som gör att detta tillvägagångssätt inte är tillförlitligt. Så optimeringen av förkortade nycklar är för närvarande inaktiverad för strängdatatyper. Med ICU kan vi använda motsvarande API-anrop och beräkna förkortade nycklar på vad vi tror är ett tillförlitligt och stabilt sätt. Så det finns möjliga prestandaförbättringar från detta drag också.
Fler sorteringar
Förutom dessa interna förbättringar av robusthet och prestanda, finns det också en del ny funktionalitet för användaren.
För vissa språk kan mer än en sorteringsordning vara relevant i praktiken. (Detta kan hjälpa dig att komma igång.) Ett exempel är att för tyska finns det en standardsorteringsordning som används för de flesta ändamål och en "telefonbok"-sorteringsordning som används för namnlistor. Standard C-biblioteket tillhandahåller bara en av dessa varianter (förmodligen den första). Men om du vill skriva en applikation som korrekt sorterar t.ex. både produktnamn och kundnamn, måste du kunna använda båda.
Exempelvis kan exemplet från tyska Wikipedia nu återskapas med PostgreSQL:
CREATE TABLE names (name text); INSERT INTO names VALUES ('Göbel'), ('Goethe'), ('Goldmann'), ('Göthe'), ('Götz'); => SELECT name FROM names ORDER BY name COLLATE "de-u-co-standard-x-icu"; name ---------- Göbel Goethe Goldmann Göthe Götz => SELECT name FROM names ORDER BY name COLLATE "de-u-co-phonebk-x-icu"; name ---------- Göbel Goethe Göthe Götz Goldmann => SELECT name FROM names ORDER BY name COLLATE "de-AT-u-co-phonebk-x-icu"; name ---------- Goethe Goldmann Göbel Göthe Götz
(Med glibc, COLLATE "de_DE"
och COLLATE "de_AT"
returnerar verkligen den första beställningen.)
Ett intressant sätt att kombinera flera funktioner kan vara att använda domäner för att modellera ovan nämnda skillnad mellan produktnamn och kundnamn:
CREATE DOMAIN product_name AS text COLLATE "de-u-co-standard-x-icu"; CREATE DOMAIN person_name AS text COLLATE "de-u-co-phonebk-x-icu";
(Detta är bara ett exempel. Naturligtvis kan du också bifoga dessa COLLATE
satser till kolumndefinitioner direkt eller använd dem i frågor.)
Ännu fler sorteringar
Äntligen, och det är helt klart vad världen hade väntat på, finns det nu ett sätt att sortera emojis ordentligt. Detta är viktigt för att säkerställa att alla dina kattansikten är i rätt ordning. Jämför
=# SELECT chr(x) FROM generate_series(x'1F634'::int, x'1F644'::int) AS _(x) ORDER BY chr(x) COLLATE "und-x-icu"; chr ----- 😴 😵 😶 😷 😸 😹 😺 😻 😼 😽 😾 😿 🙀 🙁 🙂 🙃 🙄
med
=# CREATE COLLATION "und-u-co-emoji-x-icu" (provider = icu, locale = 'und-u-co-emoji'); =# SELECT chr(x) FROM generate_series(x'1F634'::int, x'1F644'::int) AS _(x) ORDER BY chr(x) COLLATE "und-u-co-emoji-x-icu"; chr ----- 🙂 🙃 😶 🙄 😴 😷 😵 🙁 😺 😸 😹 😻 😼 😽 🙀 😿 😾
Ja, det finns faktiskt en standard om detta.
Mer kommer
Detta är bara början. ICU erbjuder mycket funktionalitet inom detta område som vi inte exponerar via PostgreSQL än. Det finns alternativ för skiftlägesokänslig sortering, accentokänslig sortering och helt anpassa en sortering. Leta efter dem i framtida PostgreSQL-utgåvor.