Det finns två allmänna tillvägagångssätt för låsning.
För det första har du pessimistisk låsning. I detta tillvägagångssätt låser du raden (SELECT ... FOR UPDATE
) som hindrar någon annan från att ändra raden. Sedan gör du UPDATE
. När du genomför din ändring frigörs låset. Det finns inget behov i det här fallet att ha ett versionsnummer/tidsstämpelkolumn (åtminstone inte för att stödja låsning) och koden är relativt enkel.
Nackdelen med pessimistisk låsning är att du måste hålla låset hela tiden en användare sitter på en sida som potentiellt redigerar data. Detta är tekniskt riktigt svårt om du bygger en webbaserad applikation eftersom HTTP är ett tillståndslöst protokoll. Begäran som initialt renderar sidan skulle normalt få en anslutning från anslutningspoolen, gör SELECT
, och återställ sedan anslutningen till poolen när sidan är klar. Den efterföljande begäran om att uppdatera data skulle vanligtvis ske på en annan anslutning med en annan databassession så du kan inte låsa raden i den första sessionen och uppdatera den i den andra. Om du pessimistiskt ville låsa raden, skulle du behöva göra mycket arbete på baksidan för att säkerställa att den ena databasanslutningen var bunden till en viss mellannivåsession tills användaren var klar med att redigera data. Detta har generellt mycket negativa effekter på skalbarheten och introducerar alla möjliga problem med sessionshantering - hur vet du till exempel om jag har begärt en sida, låst en rad och sedan stängt min webbläsare utan att någonsin logga ut eller göra en ändring? Hur länge ska du lämna posten låst i databasen? Vad händer om någon annan session försöker låsa raden? Hur länge ska du låta sessionsblocket vänta på ett lås om den första personen gick ut och åt lunch? I allmänhet implementerar människor inte pessimistisk låsning i webbaserade appar eftersom det är för opraktiskt att hantera sessioner och sessionstillstånd.
Det andra alternativet är optimistisk låsning. I detta tillvägagångssätt lägger du till ett versionsnummer/tidsstämpel på raden. Du väljer detta versionsnummer/tidsstämpel när du frågar efter data. Sedan använder du detta i din WHERE
när du senare gör uppdateringen och kontrollerar hur många rader som faktiskt ändrades. Om du ändrar exakt en rad vet du att raden inte har ändrats sedan du läste den. Om du ändrar 0 rader vet du att raden ändrades och du kan hantera felet.
Så, till exempel, skulle du välja data tillsammans med versionsnumret
SELECT address_line1, city, state, zip, version
FROM addressTable
WHERE address_id = `<<some key>>`
När du var redo att göra uppdateringen, skulle du göra något liknande där du använder version
i din UPDATE
och skicka ett felmeddelande om raden ändrades
UPDATE addressTable
SET address_line1 = `<<new address line 1>>`,
city = `<<new city>>`,
state = `<<new state>>`,
zip = `<<new zip>>`,
version = version + 1
WHERE address_id = `<<some key>>`
AND version = `<<version you read initially>>`
IF( SQL%ROWCOUNT = 0 )
THEN
-- Darn. The row must have changed since you read it. Do something to
-- alert the user. Most likely, the application will need to re-query the
-- data to see what the address has been changed to and then ask the user
-- whether they want to re-apply the changes.
RAISE_APPLICATION_ERROR( -20001, 'Oops, the row has changed since you read it.' );
END IF;
Din applikation skulle då göra något användbart med felet. Normalt skulle det innebära att göra något som att fråga efter data igen, presentera ändringarna för användaren och fråga dem om de fortfarande ville tillämpa sina ändringar. Om jag till exempel läser en adress och börjar redigera den, går på lunch, min kollega loggar in, läser samma adress, gör några ändringar och sparar den, då återkommer jag och försöker spara mina ändringar, det skulle generellt sett vara vettigt för att visa mig något som säger att min kollega redan har ändrat adressen till något nytt - vill jag fortsätta göra ändringar eller vill jag överge dem.