Race conditions i CRUD-appar kan orsaka dubbla beställningar och felaktiga totalsummor. Lär dig vanliga konfliktytor och åtgärder med begränsningar, lås och UI-skydd.

En race condition uppstår när två (eller fler) förfrågningar uppdaterar samma data nästan samtidigt, och det slutliga resultatet beror på timingen. Varje förfrågan ser korrekt ut för sig. Tillsammans ger de ett felaktigt utfall.
Ett enkelt exempel: två personer klickar på Spara på samma kundpost inom en sekund. Den ena uppdaterar e-post, den andra uppdaterar telefonnumret. Om båda skickar hela posten kan den andra skrivningen skriva över den första och en ändring försvinner utan något felmeddelande.
Det här syns mer i snabba appar eftersom användare kan trigga fler åtgärder per minut. Det ökar också under hektiska tillfällen: flash sales, månadsavslut, en stor e-postkampanj eller när en backlog av requests träffar samma rader.
Användare rapporterar sällan "en race condition." De rapporterar symptom: dubbla beställningar eller kommentarer, saknade uppdateringar ("jag sparade det, men det gick tillbaka"), konstiga totalsummor (lager blir negativt, räknare hoppar bakåt), eller statusar som växlar oväntat (godkänd, sedan tillbaka till väntande).
Retries gör det värre. Folk dubbelklickar, uppdaterar efter en långsam respons, skickar från två flikar eller har svajiga nät som får browsern eller mobilappen att skicka om. Om servern behandlar varje request som en ny skrivning kan du få två skapelser, två betalningar eller två statusändringar som bara skulle ske en gång.
De flesta CRUD-appar känns enkla: läs en rad, ändra ett fält, spara det. Fällan är att din app inte kontrollerar timingen. Databasen, nätverket, retries, bakgrundsjobb och användarbeteenden överlappar hela tiden.
En vanlig trigger är två personer som redigerar samma post. Båda laddar de "aktuella" värdena, båda gör giltiga ändringar och sista sparandet skriver tyst över det första. Ingen gjorde något fel, men en uppdatering går förlorad.
Det händer också med en person. Ett dubbelklick på Spara, att trycka fram och tillbaka, eller en långsam anslutning som får någon att trycka Skicka igen kan skicka samma skrivning två gånger. Om endpointen inte är idempotent kan du skapa dubbletter, debitera två gånger eller flytta en status två steg framåt.
Moderna användningsmönster ökar överlappningen. Flera flikar eller enheter inloggade på samma konto kan skicka konfliktande uppdateringar. Bakgrundsjobb (e-post, fakturering, synk, städning) kan röra samma rader som webbförfrågningar. Automatiska retries i klienten, load balancern eller jobbrunnern kan upprepa en request som redan lyckats.
Om ni snabblevererar funktioner uppdateras samma post oftare från fler ställen än någon minns. Om du använder en chattstyrd byggare som Koder.ai kan appen växa ännu snabbare, så behandla samtidig körning som normalt beteende, inte ett kantfall.
Race conditions syns sällan i "skapa en post"-demos. De syns där två förfrågningar rör samma sanning nästan samtidigt. Att känna till vanliga hotspots hjälper dig att designa säkra skrivningar från start.
Allt som känns som "bara lägg till 1" kan gå sönder vid belastning: likes, visningar, totalsummor, fakturanummer, biljettnummer. Riskmönstret är att läsa värdet, lägga till, och sedan skriva tillbaka. Två requests kan läsa samma startvärde och skriva över varandra.
Workflows som Draft -> Submitted -> Approved -> Paid verkar enkla, men kollisioner är vanliga. Problem uppstår när två åtgärder är möjliga samtidigt (godkänn och redigera, avbryt och betala). Utan skydd kan en post hoppa över steg, växla tillbaka eller visa olika tillstånd i olika tabeller.
Behandla statusändringar som ett kontrakt: tillåt bara nästa giltiga steg och neka allt annat.
Återstående platser, lagerantal, bokningsslotar och fält för "återstående kapacitet" skapar det klassiska översäljningsproblemet. Två köpare går till kassan samtidigt, båda ser tillgänglighet och båda lyckas. Om databasen inte är slutgiltig domare kommer du till slut att sälja mer än du har.
Vissa regler är absoluta: en e-post per konto, en aktiv prenumeration per användare, en öppen kundvagn per användare. Dessa misslyckas ofta när du först kontrollerar ("finns det redan?") och sedan gör ett insert. Vid samtidighet kan båda requests passera kontrollen.
Om du snabbt genererar CRUD-flöden (till exempel genom att chatta fram appen med Koder.ai), skriv ner dessa hotspots tidigt och backa upp dem med constraints och säkra skrivningar, inte bara UI-kontroller.
Många race conditions börjar med något tråkigt: samma åtgärd skickas två gånger. Användare dubbelklickar. Nätverket är långsamt så de klickar igen. En telefon registrerar två tryck. Ibland är det inte avsiktligt: sidan uppdateras efter en POST och webbläsaren erbjuder att skicka om formuläret.
När det händer kan backend köra två creates eller updates parallellt. Om båda lyckas får du dubbletter, felaktiga totalsummor eller en statusändring som körs två gånger (t.ex. approve och ännu en approve). Det ser slumpmässigt ut eftersom det beror på timingen.
Den säkraste strategin är försvar i flera lager. Fixa UI:n, men anta att UI:n kommer att misslyckas.
Praktiska ändringar du kan applicera på de flesta skrivflöden:
Exempel: en användare trycker på "Betala faktura" två gånger i mobilen. UI:n bör blockera det andra trycket. Servern bör också förkasta det andra anropet när den ser samma idempotency-nyckel och istället returnera det ursprungliga lyckade resultatet i stället för att debitera två gånger.
Statusfält känns enkla tills två saker försöker ändra dem samtidigt. En användare klickar Godkänn medan ett automatiserat jobb markerar samma post som Utgånget, eller två teammedlemmar arbetar i olika flikar. Båda uppdateringarna kan lyckas, men slutstatus beror på timingen, inte dina regler.
Behandla status som en liten state machine. Ha en kort tabell över tillåtna steg (t.ex. Draft -> Submitted -> Approved, och Submitted -> Rejected). Då kontrollerar varje skrivning: "Är denna övergång tillåten från nuvarande status?" Om inte, neka den istället för att tyst skriva över.
Optimistisk låsning hjälper dig fånga uppdateringar som blivit föråldrade utan att blockera andra användare. Lägg till ett versionsnummer (eller updated_at) och kräva att det matchar vid spar. Om någon annan ändrade raden efter att du läste den påverkar din update noll rader och du kan visa ett tydligt meddelande som "Denna post har ändrats, uppdatera sidan och försök igen."
Ett enkelt mönster för statusuppdateringar är:
Håll också statusändringar på ett ställe. Om uppdateringar sprids över olika skärmar, bakgrundsjobb och webhooks missar du lätt en regel. Placera dem bakom en enda funktion eller endpoint som alltid kontrollerar övergångsregler.
Den vanligaste räknebuggen ser harmlös ut: appen läser ett värde, lägger till 1 och skriver tillbaka. Vid belastning kan två requests läsa samma tal och båda skriva samma nya tal, så en inkrement försvinner. Det är lätt att missa eftersom det "vanligtvis fungerar" i tester.
Om ett värde bara ska inkrementeras eller dekrementeras, låt databasen göra det i en sats. Då applicerar databasen förändringarna säkert även när många requests slår mot samma rad.
UPDATE posts
SET like_count = like_count + 1
WHERE id = $1;
Samma idé gäller lager, visningsräknare, retry-räknare och allt som kan uttryckas som "new = old + delta".
Totalsummor går ofta fel när du lagrar ett härlett tal (order_total, account_balance, project_hours) och sedan uppdaterar det från flera ställen. Om du kan beräkna totalen från källrader (orderrader, kassaboksposter) undviker du en hel klass av driftsfel.
När du måste lagra en total för prestanda, behandla den som en kritisk skrivning. Håll uppdateringar av källrader och den lagrade totalen i samma transaktion. Se till att bara en writer kan uppdatera samma total åt gången (låsning, guarded updates eller en enda owner-path). Lägg till constraints som förhindrar omöjliga värden (t.ex. negativt lager). Gör sedan en periodisk kontroll som räknar om och flaggar avvikelser.
Ett konkret exempel: två användare lägger till varor i samma kundvagn samtidigt. Om varje request läser cart_total, lägger till priset och skriver tillbaka kan en addition försvinna. Om du uppdaterar kundvagnsartiklarna och cart_total tillsammans i en transaktion så förblir totalen korrekt även under tunga parallella klick.
Om du vill få färre race conditions, börja i databasen. Appkoden kan försöka om, time out eller köras två gånger. En databasconstraint är sista skyddet som alltid håller korrekt även när två requests träffar samtidigt.
Unika constraints stoppar dubbletter som "aldrig borde hända" men ändå händer: e-postadresser, ordernummer, faktura-id:n eller en "en aktiv prenumeration per användare"-regel. När två signups landar tillsammans accepterar databasen en rad och avvisar den andra.
Foreign keys förhindrar brutna referenser. Utan dem kan en request ta bort en parentrad medan en annan skapar ett barn som pekar på inget, vilket lämnar föräldralösa rader som är svåra att städa upp.
Check constraints håller värden inom säkra intervall och tvingar enklare state-regler. Till exempel quantity >= 0, rating mellan 1 och 5 eller status begränsad till ett tillåtet set.
Behandla constraint-fel som förväntade utfall, inte som "serverfel." Fånga unika-, foreign key- och check-överträdelser, returnera ett tydligt meddelande som "Den e-posten är redan i bruk" och logga detaljer för felsökning utan att läcka intern information.
Exempel: två personer klickar på "Skapa order" två gånger under lagg. Med en unik constraint på (user_id, cart_id) får du inte två ordrar. Du får en order och en ren, förklarbar avvisning.
Vissa skrivningar är inte en enda sats. Du läser en rad, kontrollerar en regel, uppdaterar en status och kanske inserterar en audit-rad. Om två requests gör det samtidigt kan båda klara kontrollen och båda skriva. Det är det klassiska felmönstret.
Wrappa den flerstegs-skrivningen i en databastransaktion så att alla steg lyckas tillsammans eller inget gör det. Viktigare är att transaktionen ger en plats för att kontrollera vem som får ändra samma data samtidigt.
När endast en aktör kan redigera en rad åt gången, använd radnivålåsning. Till exempel: lås orderraden, bekräfta att den fortfarande är i "pending"-läge, ändra den till "approved" och skriv audit-posten. Den andra requesten väntar då, re-kollar status och avbryter.
Välj beroende på hur ofta kollisioner händer:
Håll låstid kort. Gör så lite arbete som möjligt medan du håller låset: inga externa API-anrop, inga långsamma filoperationer, inga stora loopar. Om du bygger flöden i ett verktyg som Koder.ai, håll transaktionen runt bara databastegen och gör resten efter commit.
Välj ett flöde som kan kosta pengar eller förtroende om det kolliderar. Ett vanligt är: skapa en order, reservera lager och sätt sedan orderstatus till bekräftad.
Skriv ner exakt vilka steg din kod tar idag, i ordning. Var specifik om vad som läses, vad som skrivs och vad "framgång" betyder. Kollisioner gömmer sig i glappet mellan en läsning och en senare skrivning.
En hårdningsväg som fungerar i de flesta stackar:
Lägg till ett test som bevisar fixen. Kör två requests samtidigt mot samma produkt och kvantitet. Kontrollera att exakt en order blir bekräftad och den andra misslyckas på ett kontrollerat sätt (inget negativt lager, inga dubbla reservationsrader).
Om du genererar appar snabbt (inklusive med plattformar som Koder.ai) är denna checklista fortfarande värd att göra för de få skrivvägar som verkligen spelar roll.
Ett av de största problemen är att lita på UI:n. Inaktiverade knappar och klientkontroller hjälper, men användare kan dubbelklicka, uppdatera sidan, öppna två flikar eller spela upp en request från en svajig anslutning. Om servern inte är idempotent smyger dubbletter igenom.
En annan tyst bug: du fångar ett databasfel (som en unikhetsöverträdelse) men fortsätter workflowen ändå. Det blir ofta "skapa misslyckades, men vi skickade ändå e-post" eller "betalning misslyckades, men vi markerade ordern som betald." När sidoeffekter inträffat är det svårt att backa.
Långa transaktioner är också en fälla. Om du håller en transaktion öppen medan du kallar e-post, betalningar eller tredjeparts-APIer håller du lås längre än nödvändigt. Det ökar väntetider, timeouts och chansen att requests blockerar varandra.
Att blanda bakgrundsjobb och användaråtgärder utan en enda källa till sanning skapar split-brain-tillstånd. Ett jobb försöker igen och uppdaterar en rad medan en användare redigerar den, och båda tror att de var sista skrivaren.
Några "fixar" som inte faktiskt löser problemet:
Om du bygger med ett chat-to-app-verktyg som Koder.ai gäller samma regler: be om server-side-constraints och tydliga transaktionsgränser, inte bara snyggare UI-skydd.
Race conditions visas ofta bara under verklig trafik. En pre-release-genomgång kan hitta de vanligaste kollisionerna utan att behöva en stor omskrivning.
Börja med databasen. Om något måste vara unikt (e-post, fakturanummer, en aktiv prenumeration per användare), gör det till en riktig unik constraint, inte en appnivå "vi kollar först"-regel. Se sedan till att din kod förväntar sig att begränsningen ibland kan slå tillbaka och returnerar ett tydligt, säkert svar.
Titta sedan på state. Varje statusändring (Draft -> Submitted -> Approved) bör valideras mot en explicit uppsättning tillåtna övergångar. Om två requests försöker flytta samma post ska den andra nekas eller bli en no-op, inte skapa ett mellanläge.
En praktisk pre-release-checklista:
Om du bygger flöden i Koder.ai, gör dessa till acceptanskriterier: den genererade appen ska misslyckas säkert vid repetition och samtidighet, inte bara passera happy path.
Två medarbetare öppnar samma inköpsbegäran. Båda klickar Godkänn inom några sekunder. Båda requests når servern.
Vad som kan gå fel är rörigt: begäran blir "godkänd" två gånger, två notifikationer skickas ut och totalsummor kopplade till godkännanden (budget, dagliga godkännanden) kan öka med 2. Båda uppdateringarna är giltiga var för sig, men de kolliderar.
Här är en fix-plan som fungerar bra med en PostgreSQL-liknande databas.
Lägg till en regel som garanterar att endast en approvals-rad kan finnas per begäran. Till exempel, lagra godkännanden i en separat tabell och sätt en unik constraint på request_id. Nu misslyckas det andra insertet även om applikationskoden har en bugg.
När du godkänner, gör hela övergången i en transaktion:
Om den andra medarbetaren kommer för sent får de antingen 0 uppdaterade rader eller ett unique-constraint-fel. Hur som helst vinner bara en av ändringarna.
Efter fixen ser den första medarbetaren Approved och får normal bekräftelse. Den andra ser ett vänligt meddelande som: "Denna begäran har redan godkänts av någon annan. Uppdatera sidan för att se senaste status." Ingen snurr, inga dubbla notiser, inga tysta fel.
Om du genererar ett CRUD-flöde i en plattform som Koder.ai (Go-backend med PostgreSQL) kan du baka in dessa kontroller i approve-actionen en gång och återanvända mönstret för andra "endast en vinnare"-åtgärder.
Race conditions är lättast att åtgärda om du behandlar dem som en återkommande rutin, inte en engångsjakt på buggar. Fokusera på de få skrivvägar som spelar roll mest och gör dem tråkigt korrekta innan du finslipar något annat.
Börja med att namnge dina största kollisionpunkter. I många CRUD-appar är det samma trio: räknare (likes, lager, saldon), statusändringar (Draft -> Submitted -> Approved) och dubbla inskick (dubbelklick, retries, långsamma nät).
En rutin som håller:
Om du bygger på Koder.ai är Planning Mode en praktisk plats att mappa varje skrivflöde som steg och regler innan du genererar ändringar i Go och PostgreSQL. Snapshots och rollback är också användbara när du rullar ut nya constraints eller låsbeteenden och vill ha ett snabbt sätt att backa om du träffar ett kantfall.
Med tiden blir detta en vana: varje ny skrivfunktion får en constraint, en transaktionsplan och ett samtidighetstest. Så slutar race conditions i CRUD-appar att vara överraskningar.