Dette er dukket op et par gange for nylig, både på SO og på PostgreSQL-mailinglisterne.
TL;DR for dine sidste to punkter:
(a) De større shared_buffers kan være årsagen til, at TRUNCATE er langsommere på CI-serveren. Forskellig fsync-konfiguration eller brugen af rotationsmedier i stedet for SSD'er kan også være en fejl.
(b) TRUNCATE
har en fast pris, men ikke nødvendigvis langsommere end DELETE
, plus det gør mere arbejde. Se den detaljerede forklaring, der følger.
OPDATERING: En væsentlig diskussion om pgsql-performance opstod fra dette indlæg. Se denne tråd.
OPDATERING 2: Der er blevet tilføjet forbedringer til 9.2beta3, som skulle hjælpe med dette, se dette indlæg.
Detaljeret forklaring af TRUNCATE
vs DELETE FROM
:
Selvom jeg ikke er ekspert på emnet, er min forståelse, at TRUNCATE
har en næsten fast pris pr. tabel, mens DELETE
er mindst O(n) for n rækker; værre, hvis der er nogen fremmednøgler, der refererer til den tabel, der slettes.
Jeg har altid antaget, at de faste omkostninger ved en TRUNCATE
var lavere end prisen på en DELETE
på et næsten tomt bord, men det er slet ikke sandt.
TRUNCATE table;
gør mere end DELETE FROM table;
Databasens tilstand efter en TRUNCATE table
er meget det samme, som hvis du i stedet ville køre:
DELETE FROM table;
VACCUUM (FULL, ANALYZE) table;
(kun 9.0+, se fodnote)
... selvom selvfølgelig TRUNCATE
opnår faktisk ikke sine effekter med en DELETE
og en VACUUM
.
Pointen er, at DELETE
og TRUNCATE
gør forskellige ting, så du ikke bare sammenligner to kommandoer med identiske resultater.
En DELETE FROM table;
tillader døde rækker og bloat at forblive, tillader indekserne at bære døde poster, opdaterer ikke tabelstatistikken, der bruges af forespørgselsplanlæggeren, osv.
En TRUNCATE
giver dig en helt ny tabel og indekser, som om de bare var CREATE
udg. Det er, som om du har slettet alle posterne, genindekseret tabellen og lavet en VACUUM FULL
.
Hvis du er ligeglad med, om der er crud tilbage i tabellen, fordi du er ved at gå og fylde den op igen, kan du være bedre tjent med at bruge DELETE FROM table;
.
Fordi du ikke kører VACUUM
du vil opdage, at døde rækker og indeksposter akkumuleres som oppustethed, der skal scannes og derefter ignoreres; dette forsinker alle dine forespørgsler. Hvis dine test faktisk ikke skaber og sletter al så meget data, bemærker du måske ikke eller bekymrer dig, og du kan altid lave en VACUUM
eller to halvvejs gennem dit testløb, hvis du gør det. Bedre, lad aggressive autovacuum-indstillinger sikre, at autovacuum gør det for dig i baggrunden.
Du kan stadig TRUNCATE
alle dine borde efter hele testsuite kører for at sikre, at der ikke opbygges effekter på tværs af mange kørsler. På 9.0 og nyere, VACUUM (FULL, ANALYZE);
globalt på bordet er mindst lige så god, hvis ikke bedre, og det er en hel del nemmere.
IIRC Pg har et par optimeringer, der betyder, at den måske bemærker, når din transaktion er den eneste, der kan se tabellen og straks markere blokkene som gratis alligevel. I test, når jeg har ønsket at skabe bloat, har jeg været nødt til at have mere end én samtidig forbindelse for at gøre det. Jeg ville dog ikke stole på dette.
DELETE FROM table;
er meget billigt til små borde uden f/k refs
Til DELETE
alle poster fra en tabel uden fremmednøglereferencer til den, skal alle Pg lave en sekventiel tabelscanning og indstille xmax
af de stødte tupler. Dette er en meget billig operation - grundlæggende en lineær læsning og en semi-lineær skrivning. AFAIK det behøver ikke at røre indekserne; de fortsætter med at pege på de døde tupler, indtil de er ryddet op af et senere VACUUM
der også markerer blokke i tabellen, der kun indeholder døde tupler som frie.
DELETE
bliver kun dyrt, hvis der er masser af poster, hvis der er masser af fremmednøglereferencer, der skal kontrolleres, eller hvis du tæller den efterfølgende VACUUM (FULL, ANALYZE) table;
nødvendig for at matche TRUNCATE
s effekter inden for prisen på din DELETE
.
I mine tests her, en DELETE FROM table;
var typisk 4x hurtigere end TRUNCATE
ved 0,5 ms vs. 2 ms. Det er en test-DB på en SSD, der kører med fsync=off
fordi jeg er ligeglad med, om jeg mister alle disse data. Selvfølgelig, DELETE FROM table;
ikke gør det samme arbejde, og hvis jeg følger op med en VACUUM (FULL, ANALYZE) table;
det er meget dyrere 21ms, så DELETE
er kun en sejr, hvis jeg faktisk ikke har brug for bordet uberørt.
TRUNCATE table;
udfører meget mere faste omkostninger og husholdning end DELETE
Derimod en TRUNCATE
skal lave en masse arbejde. Den skal allokere nye filer til tabellen, dens TOAST-tabel, hvis nogen, og hvert indeks tabellen har. Overskrifter skal skrives ind i disse filer, og systemkatalogerne skal muligvis også opdateres (ikke sikker på det punkt, har ikke tjekket). Den skal så erstatte de gamle filer med de nye eller fjerne de gamle og skal sikre, at filsystemet har indhentet ændringerne med en synkroniseringsoperation - fsync() eller lignende - der normalt tømmer alle buffere til disken . Jeg er ikke sikker på, om synkroniseringen er sprunget over, hvis du kører med (data-spise) muligheden fsync=off
.
Jeg lærte for nylig, at TRUNCATE
skal også tømme alle PostgreSQL's buffere relateret til den gamle tabel. Dette kan tage en ikke-triviel mængde tid med enorme shared_buffers
. Jeg formoder, at det er derfor, det er langsommere på din CI-server.
Saldoen
I hvert fald kan du se, at en TRUNCATE
af en tabel, der har en tilknyttet TOAST-tabel (de fleste gør), og flere indekser kan tage et øjeblik. Ikke lang, men længere end en DELETE
fra et næsten tomt bord.
Derfor er det måske bedre at lave en DELETE FROM table;
.
--
Bemærk:på DB'er før 9.0, CLUSTER table_id_seq ON table; ANALYZE table;
eller VACUUM FULL ANALYZE table; REINDEX table;
ville være mere ækvivalent med TRUNCATE
. VACUUM FULL
impl ændret til en meget bedre i 9.0.