# Transaktion

## Vad det är

En transaktion är en grupp ändringar som en databas genomför som en enhet:
antingen får alla verkan eller ingen av dem. När den behöver hållas isär från en
betalning, som också kallas transaktion, skriver man databastransaktion.
PostgreSQL-handledningen beskriver idén så här: flera steg samlas i en enda
operation där allt eller inget sker, och ingen annan ser något av det som ligger
däremellan.

I PostgreSQL öppnas en transaktion med `BEGIN` och avslutas antingen med
`COMMIT`, som gör ändringarna beständiga och synliga för andra sessioner, eller
med `ROLLBACK`, som kastar dem. En sats som skickas ensam körs också i en
transaktion: PostgreSQL lägger ett implicit `BEGIN` runt den och, om den lyckas,
ett implicit `COMMIT`. En lång transaktion kan sätta en sparpunkt med
`SAVEPOINT` och senare gå tillbaka till den med `ROLLBACK TO`, så att arbetet
efter punkten kastas medan allt före den behålls.

Garantierna en transaktion ger sammanfattas som ACID. Atomicitet betyder att
transaktionen genomförs som en enhet eller inte alls och att inget halvfärdigt
resultat överlever en krasch. Konsistens betyder att integritetsvillkoren som
definierats för datan gäller när den committas, och att en transaktion som
skulle bryta mot något av dem rullas tillbaka i stället. Isolering betyder att
dess ändringar är osynliga för samtidiga transaktioner tills den har committats.
Hållbarhet betyder att en committad ändring överlever att servern kraschar.

Isolering finns i nivåer, och SQL-standarden definierar fyra: Read Uncommitted,
Read Committed, Repeatable Read och Serializable. PostgreSQL startar varje
transaktion på Read Committed om inget annat anges, och dess Read Uncommitted
beter sig exakt som Read Committed. På Read Committed ser varje sats de data som
committats innan satsen började, så två frågor i samma transaktion kan se olika
siffror. På Repeatable Read arbetar hela transaktionen utifrån en
ögonblicksbild, tagen när dess första sats efter `BEGIN` körs. Serializable går
längre och ser till att samtidiga transaktioner ger samma resultat som om de
hade körts en i taget.

## Varför det spelar roll

Nästan varje handling som är värd att registrera rör mer än en rad. En order är
en rad i en tabell, en nedräkning av lagret i en annan och en post i en
huvudbok. Utan transaktion kan en krasch, en timeout eller ett brutet
integritetsvillkor mellan andra och tredje satsen lämna kvar en order vars lager
aldrig drogs, eller lager som dragits för en order som inte finns. Sedan hittar
någon det i en rapport och rättar det för hand. Med en transaktion finns det
halva tillståndet aldrig för någon att se, och det är databasen, inte
applikationen, som garanterar det.

Det andra skälet är samtidighet. Två anrop som kommer samtidigt körs sida vid
sida, och isoleringsnivån avgör vad vart och ett ser medan lås avgör vilket som
får vänta. Det är här team oftast blir överraskade: att slå in kod i `BEGIN` och
`COMMIT` hindrar inte i sig två anrop från att läsa samma saldo och båda
spendera det. På Read Committed läser varje sats det som var committat när den
startade, så en kontroll i en sats och en skrivning i nästa kan skiljas åt av en
commit från en annan session. Kontrollen måste antingen låsa raden först eller
bli en del av skrivningen.

Transaktioner har också ett pris, och det betalas i tid. Radlås som tas av
`UPDATE`, `DELETE` och `SELECT ... FOR UPDATE` hålls tills transaktionen
avslutas, och en session som vill ändra eller låsa samma rad väntar så länge det
tar. En öppen transaktion hindrar också `VACUUM` från att ta bort gamla
radversioner som den fortfarande kan behöva se, så en transaktion som står öppen
i timmar får tabellerna att svälla. Designfrågan är därför aldrig bara om en
transaktion ska användas, utan var dess gränser går och hur lite som händer
mellan dem.

Slutligen omfattar en transaktion en databas och inget annat. Ett anrop till en
betalleverantör, ett mejl eller ett meddelande på en kö sker utanför den, och en
rollback kan inte ta tillbaka något av dem. Att bestämma vad som hamnar innanför
gränsen, och hur omvärlden får veta om en commit, är det mesta av arbetet med
att utforma en transaktion.

## Så ser det ut i praktiken

En medlem i ett lojalitetsprogram löser in 250 poäng mot en biobiljett. Tre
saker måste ske tillsammans: plånboken förlorar poängen, huvudboken registrerar
varför och inlösen skapas. Skrivet som en transaktion ser det ut så här:

```sql
BEGIN;

UPDATE wallet
SET    points = points - 250
WHERE  member_id = 42
AND    points >= 250
RETURNING points;
-- ingen rad tillbaka: för få poäng, så ROLLBACK och stopp här

INSERT INTO points_entry (member_id, delta, reason)
VALUES (42, -250, 'inlöst: biobiljett');

INSERT INTO redemption (member_id, reward_id)
VALUES (42, 7);

COMMIT;
```

`UPDATE` tar ett radlås på plånboken för medlem 42. Fram till `COMMIT` ser varje
annan session som läser plånboken fortfarande det gamla saldot, eftersom radlås
inte blockerar läsare och ändringar som inte har committats är osynliga. En
andra inlösen för samma medlem, skickad från en annan flik ett ögonblick senare,
når sin egen `UPDATE` och väntar. När den första transaktionen committas
utvärderar PostgreSQL på Read Committed den andras `WHERE`-villkor på nytt mot
raden som den ser ut nu. Om bara 100 poäng finns kvar är villkoret inte
uppfyllt, ingen rad kommer tillbaka och det andra anropet rullas tillbaka utan
att ha skrivit något. Om i stället den sista `INSERT` misslyckas, till exempel
för att belöning 7 har dragits tillbaka och en främmande nyckel avvisar den,
rullar klienten tillbaka och poängen är exakt där de var.

Det här är formen på
[lojalitetsplånboken med ligor](/sv/portfolio/built-a-loyalty-wallet-leagues-and-badges/).
Varje skrivoperation där tar ett radlås på plånboken innan den läses, så att det
andra anropet väntar på att det första ska bli klart i stället för att kapplöpa
med det, och eftersom plånboken ligger i samma databas som allt annat committas
saldot och det som köptes för det tillsammans eller inte alls. Sidan säger öppet
vad det kostar: radlåsning är det långsammare svaret, och vid trängsel får
transaktionerna vänta på sin tur, men alternativet är en medlem som spenderar
samma poäng två gånger.

Anteckningen om
[att sälja den sista biljetten exakt en gång](/sv/notes/selling-limited-stock-without-overselling/)
prövar samma verktyg under en blixtrea. Lösningen där gör kontrollen och
nedräkningen till en enda villkorad `UPDATE`, eftersom ett lås som hålls medan
applikationen tänker mellan en `SELECT` och en `UPDATE` får nästa köpare att
vänta varenda millisekund av det. Först på listan över resten av arbetet står
att hålla allt externt utanför transaktionen: ett betalningsanrop, en
HTTP-begäran eller ett publicerat meddelande hör hemma före den eller efter den.

## Var det går fel

**En session står overksam i en transaktion och allt bakom den får vänta.** En
applikation öppnar en transaktion, uppdaterar en rad och väntar sedan på ett
långsamt API, på användarens nästa klick eller på en bakgrundsprocess som
kraschade utan att någonsin skicka `COMMIT`. Låsen ligger kvar, och PostgreSQL
låter en session som vill ha ett konflikterande lås vänta på obestämd tid om den
inte upptäcker ett deadlock. Dokumentationen kallar det en dålig idé att hålla
en transaktion öppen medan man väntar på indata från användaren.
`pg_stat_activity` visar sådana sessioner med tillståndet `idle in transaction`,
och `xact_start` anger när transaktionen började:

```sql
SELECT pid, now() - xact_start AS open_for, state, query
FROM pg_stat_activity
WHERE state LIKE 'idle in transaction%'
ORDER BY xact_start;
```

Flytta ut det långsamma arbetet ur transaktionen, och ge servern en gräns så att
en bortglömd transaktion inte kan hålla lås i timmar.
`idle_in_transaction_session_timeout` avslutar varje session som har stått
overksam i en öppen transaktion längre än värdet. Standardvärdet är 0, vilket
stänger av den, så den måste sättas, till exempel för applikationens roll med
`ALTER ROLE app SET idle_in_transaction_session_timeout = '30s';`.

**Två anrop spenderar samma saldo.** Koden läser ett värde i en sats, fattar
beslut i applikationen och skriver resultatet i en annan. Två sessioner läser
samma tal, båda klarar kontrollen och båda skriver, så den ena ändringen skriver
tyst över den andra. Ett omgivande `BEGIN` förhindrar det inte på Read
Committed. Gör kontrollen till en del av skrivningen med ett villkor i `UPDATE`,
som i exemplet ovan, eller lås raden innan den läses med
`SELECT ... FOR UPDATE`, så att den andra sessionen väntar på den första.

**Fel dyker upp under last på Repeatable Read eller Serializable.** På de här
nivåerna avvisar PostgreSQL en transaktion som skulle bryta nivåns löfte och
rapporterar ett serialiseringsfel, SQLSTATE `40001`, med meddelanden som
`could not serialize access due to concurrent update`. Det är så det är tänkt
att fungera, och dokumentationen säger uttryckligen att applikationer som
använder de här nivåerna måste vara beredda att försöka igen. Fånga `40001` och
kör hela transaktionen igen från `BEGIN`, läsningarna inräknade, eftersom
beslutet den fattade byggde på data som sedan har ändrats. Att bara köra om den
sats som misslyckades upprepar misstaget.

**Två transaktioner hamnar i deadlock (baklås).** Den ena låser order 1 och
sedan order 2, den andra låser order 2 och sedan order 1, och var och en väntar
på den andra. PostgreSQL upptäcker cirkeln och avbryter en av transaktionerna så
att den andra kan bli klar, och vilken den väljer är svårt att förutse.
Försvaret som dokumentationen anger är att alla kodvägar tar lås i samma
ordning, till exempel genom att uppdatera rader i stigande ordning efter
primärnyckel, och att en transaktion som avbröts på grund av deadlock körs igen.

**Ett mejl går ut för en order som rullades tillbaka.** Applikationen skickar en
bekräftelse eller publicerar en händelse inifrån transaktionen, och sedan
misslyckas en senare sats. Rollbacken tar bort ordern, men inget kan kalla
tillbaka mejlet. Skriv meddelandet i en outbox-tabell i samma transaktion som
ändringen det beskriver, och låt en separat process skicka det när commiten har
skett. Då finns meddelandet exakt när ordern finns.

## Läs mer

- [Transactions](https://www.postgresql.org/docs/current/tutorial-transactions.html)
  — kapitlet i PostgreSQL-handledningen: allt-eller-inget-beteende, implicita
  transaktioner och sparpunkter, visade med en banköverföring.
- [Transaction Isolation](https://www.postgresql.org/docs/current/transaction-iso.html)
  — de fyra standardnivåerna, vad var och en ser i PostgreSQL och
  serialiseringsfelen som kräver ett nytt försök.
- [Explicit Locking](https://www.postgresql.org/docs/current/explicit-locking.html)
  — rad- och tabellås, hur länge de hålls och hur deadlocks upptäcks och
  undviks.
- [Client Connection Defaults](https://www.postgresql.org/docs/current/runtime-config-client.html)
  — `default_transaction_isolation`, `idle_in_transaction_session_timeout`,
  `lock_timeout` och de andra gränserna för en session.
- [Routine Vacuuming](https://www.postgresql.org/docs/current/routine-vacuuming.html)
  — varför gamla radversioner ligger kvar så länge någon transaktion fortfarande
  kan se dem, och vad långa öppna transaktioner gör med städningen.

<https://engineer.company/sv/glossary/transaction/>
