Det gør ondt, når du gør det, så lad være med at gøre det.
I Oracle undervises markører som en del af programmering 101. I mange (hvis ikke de fleste) tilfælde er markører det første, Oracle-udvikleren lærer. Den første klasse starter normalt med:"Der er 13 logiske strukturer, hvoraf den første er løkken, som går sådan her..."
PostgreSQL er på den anden side ikke stærkt afhængig af markører. Ja, de findes. Der er flere syntaksvarianter for, hvordan man bruger dem. Jeg vil dække alle de store designs på et tidspunkt i denne artikelserie. Men den første lektion i PostgreSQL-markører er, at der er en del (og meget bedre) algoritmiske alternativer til at bruge markører i PostgreSQL. Faktisk har jeg i en 23-årig karriere med PostgreSQL kun fundet et behov for at bruge markører to gange. Og jeg fortryder en af dem.
Markører er en dyr vane.
Iteration er bedre end looping. "Hvad er forskellen?", spørger du måske. Forskellen handler om O(N) vs. O(N^2). Ok, jeg siger det igen på engelsk. Kompleksiteten ved at bruge markører er, at de går gennem datasæt ved hjælp af det samme mønster som en indlejret for loop. Hvert ekstra datasæt øger kompleksiteten af totalen ved eksponentiering. Det skyldes, at hvert ekstra datasæt effektivt skaber en anden inderste løkke. To datasæt er O(N^2), tre datasæt er O(N^3) og så videre. Det kan være dyrt at vænne sig til at bruge markører, når der er bedre algoritmer at vælge imellem.
De gør dette uden nogen af de optimeringer, der ville være tilgængelige for funktioner på lavere niveau i selve databasen. Det vil sige, at de ikke kan bruge indekser på nogen væsentlig måde, kan ikke transformere til sub-selects, trække op i joins eller bruge parallelle læsninger. De vil heller ikke drage fordel af fremtidige optimeringer, som databasen har tilgængelig. Jeg håber, du er en stormesterkoder, der altid får den korrekte algoritme og koder den perfekt første gang, fordi du lige har besejret en af de vigtigste fordele ved en relationel database. Ydeevne ved at stole på bedste praksis eller i det mindste en andens kode.
Alle er bedre end dig. Måske ikke individuelt, men kollektivt næsten sikkert. Bortset fra det deklarative vs. imperative argument, giver kodning i et sprog, der én gang er blevet fjernet fra det underliggende funktionsbibliotek, alle andre til at forsøge at få din kode til at køre hurtigere, bedre og mere effektivt uden at rådføre dig med dig. Og det er meget, meget godt for dig.
Lad os lave nogle data at lege med.
Vi starter med at konfigurere nogle data at lege med i løbet af de næste par artikler.
Indhold af cursors.bash:
set -o substantivsæt # Behandl ikke indstillede variabler som en fejl# Dette script forudsætter, at du har PostgreSQL kørende lokalt,# at du har en database med samme navn som den lokale bruger,# og at du kan oprette alle denne struktur.# Hvis ikke, så:# sudo -iu postgres createuser -s $USER# createdb# Ryd op fra sidste kørsel[[ -f itisPostgreSql.zip ]] &&rm itisPostgreSql.zipsubdirs=$(ls -1 itisPostgreSql* | grep :| sed -e 's/://')for sub i ${subdirs[@]}do rm -rf $subdone# Hent den nyeste filewget https://www.itis.gov/downloads/itisPostgreSql. zip# Pak itunzip itisPostgreSql.zip# Dette gør en mappe med det dummeste f-ing navn muligt# itisPostgreSqlDDMMYYsubdir=$(\ls -1 itisPostgreSql* | grep :| sed -e 's/://')# Scriptet ønsker at oprette en "ITIS"-database. Lad os bare gøre det til et schema.sed -i $subdir/ITIS.sql -e '/"ITIS"/d' # Klip linjerne om at lave dbsed -i $subdir/ITIS.sql -e '/-- PostgreSQL-databasen dump/s/.*/CREATE SCHEMA HVIS IKKE FINDER itis;/'sed -i $subdir/ITIS.sql -e '/SET search_path =public, pg_catalog;/s/.*/SET search_path TO itis;/'# ok, vi har et skema til at indsætte dataene i, lad os importere.# timeout hvis vi ikke kan oprette forbindelse, fejler ved fejl.PG_TIMEOUT=5 psql -v "ON_ERROR_STOP=1" -f $subdir/ITIS.sql
Dette giver os lidt over 600.000 poster at lege med i tabellen itis.hierarchy, som indeholder en taksonomi af den naturlige verden. Vi vil bruge disse data til at illustrere forskellige metoder til at håndtere komplekse datainteraktioner.
Det første alternativ.
Mit foretrukne designmønster til at gå langs rekordsæt, mens jeg laver dyre operationer, er Common Table Expression (CTE).
Her er et eksempel på grundformen:
MED REKURSIV fauna AS ( VÆLG tsn, parent_tsn, tsn::text taksonomi FRA itis.hierarchy HVOR parent_tsn =0 UNION ALLE VÆLG h1.tsn, h1.parent_tsn, f.taksonomi || '.' || h1.tsn FRA itis.hierarchy h1 JOIN fauna f PÅ h1.parent_tsn =f.tsn )VÆLG *FRA faunaORDER BY taksonomi;
Hvilket giver følgende resultater:
┌─────────┬────────┬────────────────────────── ─ ovearat ─ Hver ─ ovearats───eptessen .───eptarateterarateterarat 1. 1. plodvirkning 846497 │ 660046 │202422.846491.660046.846497 ││ 846508 │ 846497 │202422.846491.660046.846497.846508 ││ 846553 │ 846508 │202422.846491.660046.846497.846508.846553 ││ 954935 │ 846553 │202422.846491.660046.846497.846508.846553.954935 ││ 5549 │ 954935 │202422.846491. 660046.846497.846508.846553.954935.5549 ││ 5550 │ 5549 │202422.846491.660046. 846497.846508.846553.954935.5549.5550││ 954936 │ 846553 │202422.846491.660046.846497.846508.846553.954936 ││ 954904 │ 660046 │202422.846491.660046.954904 ││ 846509 │ 954904 │202422.846491.660046.954904.846509 ││ 11473 │ 846509 │202422.846491.660046.954904.846509.11473 │ │ 11474 │ 11473 │202422.846491.660046.954904.846509.11473.11474 ││ 11475 │ 11474 │202422.846491.660046.954904.846509.11473.11474.11475 ││ ... │ │...snip... │└───────── ┴──── den eptesse ─ ─ den ─esseomaratarats────essene ──────────────────┘(601187 rækker)
Denne forespørgsel kan let ændres til at udføre enhver beregning. Det inkluderer databerigelse, komplekse funktioner eller andet, dit hjerte begærer.
"Men se!", udbryder du. "Der står RECURSIVE
lige der i navnet! Gør den ikke præcis, hvad du sagde, du ikke skulle gøre?" Nå, faktisk nej. Under hætten bruger den ikke rekursion i indlejret forstand eller looping til at udføre "rekursionen". Det er kun en lineær læsning af tabellen, indtil den underordnede forespørgsel ikke returnerer nogen nye resultater. Og det virker også med indekser.
Lad os se på udførelsesplanen:
┌──────────────────────────────────────────── ──── den appearats────eptarat ─── den appearat ─ ─essen ──── den appearats────eptarat ─── den appearat 1 │ Sorteringsnøgle:fauna.taksonomi ││ CTE fauna ││ -> Rekursiv forening (pris=1000.00..208320.69 rækker=35858 bredde=40) ││ -> Saml (pris=10.000.000.4.00.4.00.4) ││ Output:hierarchy.tsn, hierarchy.pa rent_tsn, ((hierarchy.tsn)::text) ││ Planlagte arbejdere:2 ││ -> Parallel Seq Scan på itis.hierarchy (cost=0.00..14043.22 rows=8 width=40) ││output:n hierarchy.ts:, hierarchy.parent_tsn, (hierarchy.tsn)::text ││ Filter:(hierarchy.parent_tsn =0) ││ -> Hash Join (pris=5.85..19255.85 rækker=3584 width=40) . tsn, h1.parent_tsn, ((f. taksonomi || '.'::tekst) || (h1.tsn)::tekst) ││ Hash Cond:(h1.parent_tsn =f.tsn) ││ -> Seq Scan på itis.hierarchy h1 (pris=0.00..16923.87 rækker=601187 │=8) . Output:h1.hierarchy_string, h1.tsn, h1.parent_tsn, h1.level, h1.childrencount ││ -> Hash (pris=3.60..3.60 rows=180 width=36) ││ Output:f.taksonomi, f.taksonomi tsn ││ -> WorkTable Scan på fauna f (pris=0,00..3.60 rækker=180 width=36) ││ Output:f.taksonomi, f.tsn ││ -> CTE Scan på fauna (cost=0,716. rows=35858 width=40) ││ Output:fauna.tsn, fauna.parent_tsn, fauna.taksonomi ││ JIT:││ Funktioner:13 ││ Indstillinger:Inlining false, Optimization false, Expressions true, Deformering true │└────────────────────────────────── ──── den appearats────eptarat ─── den appearat ────────────────────────┘
Lad os gå videre og oprette et indeks og se, hvordan det virker.
OPRET UNIK INDEKS taxonomy_parents PÅ itis.hierarchy (parent_tsn, tsn);┌──────────────────────── ──── den appearats────eptarat ─── den appearat Forespørgselsplan │├────essene ─ ovearats───eptarats───eptarateter──esseneparat Output:fauna.tsn, fauna.parent_tsn, fauna.taksonomi ││ Sorteringsnøgle:fauna.taksonomi ││ CTE fauna ││ -> Rekursiv Union (pris=4.56..131718.31 rækker=35858) Bitwide=35858) Heap Scan på itis.hierarchy (pris=4.56..74.69 rækker=18) ││ Output:hierarchy.tsn, hierarchy.parent_tsn, (hierarchy.tsn) ││ Tjek igen tilstand:(hierarchy.parent_tsn) ->│ =0 Bitmap Index Scan på taxon omy_parents ││ (cost=0.00..4.56 rows=18) ││ Index Cond:(hierarchy.parent_tsn =0) ││ -> Nested Loop (cost=0.42..13092.65 rowsth=3504) ││ udgang:=3504 h1.tsn, h1.parent_tsn,((f.taksonomi || '.')||(h1.tsn))││ -> WorkTable Scan på fauna f (pris=0,00..3.60 rækker=180) ││ Output:f.tsn, f.parent_tsn, f.taksonomi ││ -> Kun indeks Scan ved hjælp af taxonomy_parents på itis.hierarchy ││ h1 (pris=0.42..72.32 rækker=20 width=8) ││ Output:h1.parent_tsn, h1.tsn ││ =f.tsn. tsn) ││ -> CTE Scan på fauna (pris=0.00..717.16 rækker=35858 width=40) ││ Output:fauna.tsn, fauna.parent_tsn, fauna.taksonomi ││JIT:││JIT:││ ──── den appearats────eptarat ─── den appearat ───────────────────────────┘
Nå, det var tilfredsstillende, var det ikke? Og det ville have været uoverkommeligt svært at oprette et indeks i kombination med en markør for at udføre det samme arbejde. Denne struktur bringer os langt nok til at kunne gå en ret kompleks træstruktur og bruge den til simple opslag.
I den næste rate taler vi om en anden metode til at få det samme resultat endnu hurtigere. Til vores næste artikel vil vi tale om udvidelsen ltree, og hvordan man ser på hierarkiske data forbløffende hurtigt. Følg med.