tsvector-kolommen en GIN-indexen ontwerpen
Bereken zoekdocumenten vooraf en indexeer ze, zodat full-textqueries op schaal onder een milliseconde blijven.
tsvector-kolommen en GIN-indexen ontwerpen is een gratis Prestaties en queryoptimalisatie in PostgreSQL-les op CoddyKit. Dit is les 1 van 4. Je kunt 3 lessen uit dit leerpad gratis volledig lezen — daarna ontgrendelt CoddyKit PRO alle lessen, plus praktische oefeningen met een ingebouwde code-editor en een AI-tutor die 24/7 beschikbaar is. Deze les maakt deel uit van het leertraject Prestaties en queryoptimalisatie in PostgreSQL. Je voortgang wordt gesynchroniseerd op het web en in de CoddyKit-app. De cursus Prestaties en queryoptimalisatie in PostgreSQL bevat in totaal 4 lessen.
Waarom een vooraf berekende tsvector
PostgreSQL vergelijkt bij zoeken in volledige tekst een tsvector (het doorzoekbare document) met een tsquery (de zoektermen). De eenvoudige aanpak roept tijdens de query to_tsvector() aan op een kolom met onbewerkte tekst.
Dat werkt, maar brengt op schaal twee kosten met zich mee:
- CPU per rij: tekst bij elke scan ontleden en terugbrengen tot stammen is duur.
- Geen bruikbare index, tenzij de indexexpressie exact overeenkomt met de queryexpressie.
De oplossing is het document één keer vooraf te berekenen, op te slaan en vervolgens te indexeren. In deze les zie je hoe je die kolom en de GIN-index ontwerpt, zodat zoekopdrachten in volledige tekst ook bij miljoenen rijen minder dan een milliseconde duren.
De eenvoudige query (en de valkuil)
Dit is het patroon waarmee de meeste mensen beginnen: gewone tekst opslaan en de tsvector tijdens de query samenstellen.
De onderstaande query werkt correct, maar veroorzaakt op een grote tabel een sequentiële scan en ontleedt body opnieuw voor elke rij. Elke aanroep van to_tsvector reduceert de volledige documenttekst tot stammen en normaliseert die.
Het doel van de les is om dit werk per rij volledig te elimineren.
SELECT id, title
FROM articles
WHERE to_tsvector('english', body) @@ to_tsquery('english', 'index & scan');Optie A: een opgeslagen gegenereerde kolom
Het duidelijkste moderne ontwerp (PostgreSQL 12+) is een opgeslagen gegenereerde kolom. PostgreSQL berekent de tsvector automatisch wanneer de rij verandert, zodat het document altijd overeenkomt met de brontekst.
Houd rekening met twee regels:
- De generatie-expressie moet IMMUTABLE zijn. Daarom geef je de regconfig als letterlijke waarde door (
'english') in plaats van te vertrouwen op een sessie-instelling. - Gebruik
coalesce(), zodat een NULL-veld niet het hele document NULL maakt.
ALTER TABLE articles
ADD COLUMN search_doc tsvector
GENERATED ALWAYS AS (
to_tsvector('english', coalesce(title, '') || ' ' || coalesce(body, ''))
) STORED;Velden wegen met setweight
Niet elk veld is even belangrijk. Een overeenkomst in de titel is meestal belangrijker dan een overeenkomst diep in de hoofdtekst. setweight() voorziet lexemen van het label A, B, C of D (A is het hoogste).
Met deze labels kan ts_rank later overeenkomsten in de titel hoger waarderen dan overeenkomsten in de hoofdtekst. Verwerk de weging in de gegenereerde kolom, zodat die één keer en niet tijdens elke query wordt berekend.
ALTER TABLE articles
ADD COLUMN search_doc tsvector
GENERATED ALWAYS AS (
setweight(to_tsvector('english', coalesce(title, '')), 'A') ||
setweight(to_tsvector('english', coalesce(body, '')), 'B')
) STORED;De GIN-index maken
Een opgeslagen tsvector is zonder index nog steeds nutteloos. Het juiste indextype voor zoeken in volledige tekst is GIN (Generalized Inverted Index). GIN slaat één vermelding per verschillend lexeem op, met verwijzingen naar de rijen waarin het voorkomt. Dat is precies wat overeenkomsten met @@ nodig hebben.
Omdat de kolom al een tsvector bevat, is de index een gewone kolomindex en is er geen expressie nodig:
CREATE INDEX idx_articles_search_doc
ON articles
USING GIN (search_doc);De geïndexeerde kolom bevragen
De query verwijst nu rechtstreeks naar de opgeslagen kolom. De planner kan de GIN-index gebruiken omdat de expressie in de WHERE-clausule (search_doc) exact overeenkomt met de geïndexeerde expressie.
Geen to_tsvector() per rij en geen sequentiële scan. Voer EXPLAIN ANALYZE uit; je zou een Bitmap Index Scan op idx_articles_search_doc moeten zien.
SELECT id, title
FROM articles
WHERE search_doc @@ to_tsquery('english', 'index & scan')
ORDER BY ts_rank(search_doc, to_tsquery('english', 'index & scan')) DESC
LIMIT 20;GIN versus GiST: de juiste kiezen
PostgreSQL ondersteunt twee indextypen voor tsvector. Kies bewust:
- GIN: sneller zoeken, de standaardkeuze voor zoekopdrachten. Iets groter en trager op te bouwen en bij te werken. Het beste wanneer lezen overheerst.
- GiST: kleiner en goedkoper bijwerken, maar verliesgevend, waardoor kandidaatrijen opnieuw worden gecontroleerd en zoekopdrachten trager zijn. Handig voor gegevens met heel veel schrijfbewerkingen of voortdurende wijzigingen.
Voor de meeste zoekbelastingen, waarbij je veel vaker queryt dan schrijft, wint GIN. Kies GiST alleen wanneer de kosten voor het bijwerken van de index je knelpunt zijn.
GIN afstemmen: fastupdate en gin_pending_list_limit
GIN-indexen gebruiken een wachtlijst om invoegingen te bundelen (fastupdate = on staat standaard aan). Dit versnelt schrijfbewerkingen, maar een grote wachtlijst vertraagt leesbewerkingen omdat queries deze naast de hoofdindex moeten scannen.
Voor zoektabellen met veel leesbewerkingen kun je dit gedrag afstemmen of uitschakelen. Als je fastupdate uitschakelt, kost elke invoeging meer werk, maar blijven queries consequent snel.
ALTER INDEX idx_articles_search_doc
SET (fastupdate = off);
-- Or cap the pending list size instead of disabling it:
ALTER INDEX idx_articles_search_doc
SET (gin_pending_list_limit = 4096);Het patroon van vóór versie 12: door een trigger onderhouden kolom
Gegenereerde kolommen zijn in PostgreSQL 12 geïntroduceerd. In oudere versies, of wanneer je logica nodig hebt die niet IMMUTABLE is, onderhoud je de tsvector met een trigger.
De klassieke hulpfunctie is tsvector_update_trigger, die een doelkolom vult vanuit benoemde bronkolommen. Let op de beperking: de functie gebruikt één vaste weging en één vaste configuratie. Voor weging per veld schrijf je daarom zelf een aangepaste BEFORE-triggerfunctie.
ALTER TABLE articles ADD COLUMN search_doc tsvector;
CREATE TRIGGER trg_articles_search_doc
BEFORE INSERT OR UPDATE ON articles
FOR EACH ROW
EXECUTE FUNCTION
tsvector_update_trigger(search_doc, 'pg_catalog.english', title, body);Bestaande rijen aanvullen
Een trigger wordt alleen uitgevoerd bij toekomstige invoegingen en updates. Bestaande rijen houden een NULL-waarde in search_doc totdat je ze aanvult.
Bij een opgeslagen gegenereerde kolom vult PostgreSQL de waarden automatisch aan wanneer je de kolom toevoegt. Bij het triggerpatroon voer je eenmalig een UPDATE uit. Doe dit bij zeer grote tabellen in batches op basis van bereiken van de primaire sleutel, zodat je niet de hele tabel vergrendelt of één enorme transactie opblaast.
UPDATE articles
SET search_doc =
setweight(to_tsvector('english', coalesce(title, '')), 'A') ||
setweight(to_tsvector('english', coalesce(body, '')), 'B')
WHERE id BETWEEN 1 AND 100000;Controleren of de index daadwerkelijk wordt gebruikt
Controleer altijd of de planner je GIN-index gebruikt in plaats van terug te vallen op een sequentiële scan. Veelvoorkomende redenen waarom dat niet gebeurt: de query-expressie komt niet overeen met de geïndexeerde expressie, de tabel is klein of de statistieken zijn verouderd.
Voer EXPLAIN (ANALYZE, BUFFERS) uit en zoek naar een Bitmap Index Scan op de naam van je index. Zie je een Seq Scan met een Filter, dan wordt de index niet gebruikt. Zorg dan dat de expressies overeenkomen of voer ANALYZE uit.
EXPLAIN (ANALYZE, BUFFERS)
SELECT id
FROM articles
WHERE search_doc @@ to_tsquery('english', 'gin & index');Snelle controle
Je hebt een grote articles-tabel waarin veel wordt gelezen. Je wilt full-textqueries die titels zwaarder laten meetellen dan hoofdtekst, binnen minder dan een milliseconde blijven en tekst nooit tijdens de query opnieuw parseren. Welk ontwerp voldoet het beste aan alle drie de doelen?
Samenvatting
Je hebt van begin tot eind een zeer snelle kolom voor full-textzoeken ontworpen:
- Vooraf berekenen van het document in een STORED gegenereerde
tsvector-kolom, zodat tekst één keer wordt geparseerd en niet per query. - Wegen van velden met
setweight()(A voor titel, B voor hoofdtekst), zodatts_rankovereenkomsten betekenisvol kan scoren. - Indexeren van de kolom met GIN, de voor lezen geoptimaliseerde omgekeerde index voor overeenkomsten met
@@; gebruik GiST alleen bij zeer veel schrijfbewerkingen en voortdurende wijzigingen. - Afstemmen van schrijfbewerkingen via
fastupdateengin_pending_list_limitwanneer de wachtlijst het lezen vertraagt. - Onderhouden van tabellen van vóór versie 12 met een trigger en aanvullen van bestaande rijen in batches.
- Controleren met
EXPLAIN (ANALYZE, BUFFERS)of je een Bitmap Index Scan krijgt en geen Seq Scan.
Leer SQL met een AI-tutor — gratis
Schrijf echte code en voer die uit in je browser, krijg direct hulp van een AI-tutor die 24/7 beschikbaar is en ga verder waar je gebleven bent op het web of in de app.
- Cursussen
- 22
- Lessen
- 88
Veelgestelde vragen
Is de les “tsvector-kolommen en GIN-indexen ontwerpen” gratis?
Ja — je kunt hier op het web alle 3 lessen van het leerpad Prestaties en queryoptimalisatie in PostgreSQL, waaronder “tsvector-kolommen en GIN-indexen ontwerpen”, gratis volledig lezen. Daarna ontgrendelt CoddyKit PRO alle lessen, plus interactieve oefeningen met een ingebouwde code-editor en een AI-tutor die 24/7 beschikbaar is. De cursus Prestaties en queryoptimalisatie in PostgreSQL bevat in totaal 4 lessen.
Wat leer ik in “tsvector-kolommen en GIN-indexen ontwerpen”?
Bereken zoekdocumenten vooraf en indexeer ze, zodat full-textqueries op schaal onder een milliseconde blijven. Je oefent met Prestaties en queryoptimalisatie in PostgreSQL door code rechtstreeks in de browser uit te voeren. Een AI-begeleider die 24/7 beschikbaar is beantwoordt je vragen terwijl je de les doorwerkt.
Heb ik ervaring nodig om met Prestaties en queryoptimalisatie in PostgreSQL te beginnen?
Ervaring vooraf is niet nodig. Prestaties en queryoptimalisatie in PostgreSQL op CoddyKit is opgebouwd voor beginners tot gevorderden, zodat je hier of bij het begin kunt starten en in je eigen tempo kunt leren. Dit is les 1 van 4.
Hoe lang duurt de les “tsvector-kolommen en GIN-indexen ontwerpen”?
De meeste lessen van CoddyKit duren ongeveer 5–10 minuten. Elke les is kort en interactief, zodat je gestaag vooruitgaat en op het web en in de app precies verdergaat waar je was gebleven.
Kan ik code schrijven en uitvoeren in deze les over Prestaties en queryoptimalisatie in PostgreSQL?
Ja. Elke les over Prestaties en queryoptimalisatie in PostgreSQL bevat een ingebouwde code-editor, zodat je rechtstreeks in je browser echte code kunt schrijven en uitvoeren en direct feedback van AI krijgt — lokale installatie is niet nodig.
Alle lessen in deze cursus
- tsvector-kolommen en GIN-indexen ontwerpen
- Ranking en relevantie afstemmen met ts_rank
- Fuzzy matching met pg_trgm-similarity
- Filters combineren met zoekpredicaten