Detta har kommit upp några gånger nyligen, både på SO och på PostgreSQL-postlistorna.
TL;DR för dina två sista punkter:
(a) De större shared_buffers kan vara anledningen till att TRUNCATE är långsammare på CI-servern. Olika fsync-konfigurationer eller användningen av roterande media istället för SSD-enheter kan också vara felet.
(b) TRUNCATE
har en fast kostnad, men inte nödvändigtvis långsammare än DELETE
, plus att det fungerar mer. Se den detaljerade förklaringen som följer.
UPPDATERING: En betydande diskussion om pgsql-prestanda uppstod från detta inlägg. Se den här tråden.
UPPDATERING 2: Förbättringar har lagts till i 9.2beta3 som borde hjälpa till med detta, se detta inlägg.
Detaljerad förklaring av TRUNCATE
kontra DELETE FROM
:
Även om jag inte är expert på ämnet, är min uppfattning att TRUNCATE
har en nästan fast kostnad per tabell, medan DELETE
är åtminstone O(n) för n rader; värre om det finns några främmande nycklar som refererar till tabellen som raderas.
Jag har alltid antagit att den fasta kostnaden för en TRUNCATE
var lägre än kostnaden för en DELETE
på ett nästan tomt bord, men detta är inte alls sant.
TRUNCATE table;
gör mer än DELETE FROM table;
Databasens tillstånd efter en TRUNCATE table
är ungefär detsamma som om du istället kör:
DELETE FROM table;
VACCUUM (FULL, ANALYZE) table;
(endast 9.0+, se fotnot)
... men naturligtvis TRUNCATE
uppnår faktiskt inte sina effekter med en DELETE
och en VACUUM
.
Poängen är att DELETE
och TRUNCATE
gör olika saker, så att du inte bara jämför två kommandon med identiska resultat.
En DELETE FROM table;
tillåter döda rader och uppsvällning att finnas kvar, låter indexen bära döda poster, uppdaterar inte tabellstatistiken som används av frågeplaneraren, etc.
En TRUNCATE
ger dig en helt ny tabell och index som om de bara vore CREATE
ed. Det är som att du raderade alla poster, indexerade om tabellen och gjorde en VACUUM FULL
.
Om du inte bryr dig om det finns smuts kvar i tabellen eftersom du är på väg att gå och fylla den igen, kan det vara bättre att använda DELETE FROM table;
.
Eftersom du inte kör VACUUM
du kommer att upptäcka att döda rader och indexposter ackumuleras som uppblåsthet som måste skannas och sedan ignoreras; detta saktar ner alla dina frågor. Om dina tester inte skapar och raderar så mycket data kanske du inte märker eller bryr dig, och du kan alltid göra en VACUUM
eller två halvvägs genom din testkörning om du gör det. Bättre, låt aggressiva autovakuuminställningar se till att autovacuum gör det åt dig i bakgrunden.
Du kan fortfarande TRUNCATE
alla dina bord efter hela testsviten körs för att säkerställa att inga effekter byggs upp över många körningar. På 9.0 och nyare, VACUUM (FULL, ANALYZE);
globalt på bordet är minst lika bra om inte bättre, och det är mycket enklare.
IIRC Pg har några optimeringar som betyder att den kanske märker när din transaktion är den enda som kan se tabellen och omedelbart markera blocken som gratis ändå. När jag i tester har velat skapa uppsvälldhet har jag behövt ha mer än en samtidig anslutning för att göra det. Jag skulle dock inte lita på det här.
DELETE FROM table;
är väldigt billigt för små bord utan f/k refs
För att DELETE
alla poster från en tabell utan främmande nyckelreferenser till den, måste alla Pg göra en sekventiell tabellskanning och ställa in xmax
av de påträffade tuplarna. Detta är en mycket billig operation - i princip en linjär läsning och en semi-linjär skrivning. AFAIK det behöver inte röra indexen; de fortsätter att peka på de döda tuplarna tills de rensas upp av ett senare VACUUM
som också markerar block i tabellen som endast innehåller döda tuplar som fria.
DELETE
blir bara dyrt om det finns massor av poster, om det finns många främmande nyckelreferenser som måste kontrolleras, eller om du räknar den efterföljande VACUUM (FULL, ANALYZE) table;
behövs för att matcha TRUNCATE
s effekter inom kostnaden för din DELETE
.
I mina tester här, en DELETE FROM table;
var vanligtvis 4 gånger snabbare än TRUNCATE
vid 0,5 ms mot 2 ms. Det är en test-DB på en SSD som körs med fsync=off
för jag bryr mig inte om jag förlorar all denna data. Naturligtvis, DELETE FROM table;
gör inte samma arbete, och om jag följer upp med en VACUUM (FULL, ANALYZE) table;
det är mycket dyrare 21ms, så DELETE
är bara en vinst om jag faktiskt inte behöver bordet orörda.
TRUNCATE table;
gör mycket mer fast kostnadsarbete och hushållning än DELETE
Däremot en TRUNCATE
måste göra mycket arbete. Den måste allokera nya filer för tabellen, dess TOAST-tabell om någon, och varje index som tabellen har. Rubriker måste skrivas in i dessa filer och systemkatalogerna kan behöva uppdateras också (osäker på den punkten, har inte kontrollerat). Den måste sedan ersätta de gamla filerna med de nya eller ta bort de gamla, och måste se till att filsystemet har kommit ikapp ändringarna med en synkroniseringsoperation - fsync() eller liknande - som vanligtvis tömmer alla buffertar till disken . Jag är inte säker på om synkroniseringen hoppas över om du kör med alternativet (dataätande) fsync=off
.
Jag lärde mig nyligen att TRUNCATE
måste också spola alla PostgreSQL:s buffertar relaterade till den gamla tabellen. Detta kan ta en icke-trivial tid med enorma shared_buffers
. Jag misstänker att det är därför det är långsammare på din CI-server.
Saldo
Hur som helst, du kan se att en TRUNCATE
av en tabell som har en associerad TOAST-tabell (de flesta gör det) och flera index kan ta en stund. Inte lång, men längre än en DELETE
från ett nästan tomt bord.
Följaktligen kan det vara bättre att du gör en DELETE FROM table;
.
--
Obs:på DB:er före 9.0, CLUSTER table_id_seq ON table; ANALYZE table;
eller VACUUM FULL ANALYZE table; REINDEX table;
skulle vara en närmare motsvarighet till TRUNCATE
. VACUUM FULL
impl ändrades till en mycket bättre i 9.0.