programmera.net -> oracle -> normal     för utskrift      info@programmera.net

Förlorad uppdatering

1. Förlorad uppdatering
2. Steg1: Användare A söker fram Ford
3. Steg2: Användare B söker fram Ford
4. Steg3: Användare A uppdaterar lönen för Ford
5. Steg4: Användare B ger Ford en ny chef
6. Steg5: Användare A upptäcker att Fords lön inte har uppdaterats

1. Förlorad uppdatering

På denna sida ska vi beskriva fenomenet med förlorad uppdatering (lost update). Förlorad uppdatering uppkommer oftast i applikationer men kan också inträffa för DBA-er som sitter och skriver SQL direkt i t.ex. SQL*Plus. Detta fenomen är ett mycket stort problem p.g.a:

  • Vanligt förekommande: Många applikationer är inte programmerade för att skydda mot förlorad uppdatering.
  • Allvarligt fel: Förlorad uppdatering gör att applikationen ur användarnas synvinkel fungerar ibland men "tappar data" ibland. Detta skadar användarnas förtroende för applikationen. När användarna ska visa vad som blev fel kan de heller inte reproducera felet, eftersom felet beror på att någon annan (osynlig) användare har varit aktiv.
Förlorad uppdatering beskrivs av bilden nedan:



Stegen beskrivs översiktligt nedan:
  1. (tid 13:22:52) Användare A söker fram Ford (genom att göra en SELECT).
  2. (tid 13:25:16) Användare B söker fram Ford (genom att göra en SELECT).
  3. (tid 13:26:31) Användare A uppdaterar lönen för Ford (genom att göra UPDATE och sedan COMMIT).
  4. (tid 13:29:20) Användare B ger Ford en ny chef (genom att göra UPDATE och sedan COMMIT).
  5. (tid 14:11:10) Användare A upptäcker att Fords lön fortfarande är 3000 och tror att lönen inte har uppdaterats. Egentligen har lönen uppdaterats 2 gånger, först till 3200 och sedan tillbaka till 3000.
Nedan beskrivs stegen i detalj:

2. Steg1: Användare A söker fram Ford

Klockan 13:22:52 loggar användare A in på applikationen för att uppdatera data för en anställd vid namn "Ford" (som lagras i tabellen EMP). A söker fram "Ford" genom att använda siffran 7902 som är Fords anställningsnummer. Anta att den webbsida som visas efter sökningen ser ut såhär:



Vi simulerar själva sökningen i SQL*Plus genom att köra följande SELECT-sats:
SQL>  SELECT * FROM emp WHERE empno=7902;

     EMPNO ENAME      JOB              MGR HIREDATE          SAL       COMM     DEPTNO
---------- ---------- --------- ---------- ---------- ---------- ---------- ----------
      7902 FORD       ANALYST         7566 1981-12-03       3000                    20

3. Steg2: Användare B söker fram Ford

Klockan 13:25:16 vill användare B, som använder samma applikation, också uppdatera data för "Ford". B söker fram "Ford" på samma sätt och kommer att se samma data, se bilden:



Vi simulerar denna sökning genom att öppna ett nytt terminalföster (eller MSDOS-fönster) och loggar in i SQL*Plus och kör samma SELECT-sats:
[olle@dev1]$  sqlplus scott/tiger

SQL*Plus: Release 10.2.0.1.0 - Production on Sat Sep 2 09:44:36 2006

Copyright (c) 1982, 2005, Oracle.  All rights reserved.


Connected to:
Oracle Database 10g Enterprise Edition Release 10.2.0.1.0 - Production
With the Partitioning, OLAP and Data Mining options

SQL>  SELECT * FROM emp WHERE empno=7902;

     EMPNO ENAME      JOB              MGR HIREDATE          SAL       COMM     DEPTNO
---------- ---------- --------- ---------- ---------- ---------- ---------- ----------
      7902 FORD       ANALYST         7566 1981-12-03       3000                    20

Anledningen till att vi öppnar ett nytt terminalfönster är att vi vill att användare B ska ha en egen anslutning till databasen.

4. Steg3: Användare A uppdaterar lönen för Ford

Klockan 13:26:31 bestämmer sig användare A (efter att ha stirrat på data för Ford i c.a. 4 minuter) för att uppdatera Fords lön från 3000 till 3200, se bild:



När A ny trycker på SUBMIT kommer samtliga data i formuläret skickas till databasen. Vi utgår från att applikationen alltid uppdaterar samtliga kolumner då man utför en UPDATE. Alltså ENAME sätts till "Ford" trots att det redan var "Ford" tidigare osv. För att simulera detta ska du utföra SQL-satsen nedan i den första SQL*Plus-terminalen:
SQL>  UPDATE emp SET ename='FORD', job='ANALYST', mgr=7566, sal=3200 WHERE empno=7902;

1 row updated.

SQL>  COMMIT;

Commit complete.

COMMIT utförs här direkt efter UPDATE eftersom användare A är klar med sin transaktion.OBS: Det kan tyckas dumt att applikationen uppdaterar samtliga kolumner vid varje update, men detta är faktiskt mycket vanligt. Orsaken till att man uppdaterar alla kolumner är:

  1. Om man märker att värdet i ett fält skiljer sig från det värde som ligger i databasen, hur ska man veta om det är den som har skickat formuläret som har uppdaterat värdet eller någon som har uppdaterat databasen? Ett sätt kan vara att i formuläret skicka både det ursprungliga värdet och det nya värdet till servern. Sen lägger man till logik i servern som identifierar vilka fält som användaren faktiskt har ändrat. Detta medför en del extra jobb.
  2. Det är dessutom ganska jobbigt att dynamiskt bygga SQL-satsen så att endast de kolumner som faktiskt har förändrats kommer att uppdateras.
Alla applikationer jag har sett källkoden för uppdaterar samtliga kolumner, vare sig det behövs eller ej.

5. Steg4: Användare B ger Ford en ny chef

Klockan 13:29:20 byter Användare B chef för "Ford" så att MANAGER blir "King", se nedan:



Märk att användare Bs webbsida inte har uppdaterats med det nya värdet på lön, här står alltså det gamla värdet på lönen ("sal=3000") som var aktuellt när han sökte fram "Ford" klockan 13:25:16. Värdet 3000 skickas tillsammans med det nya värdet på MANAGER till servern och används i UPDATE-satsen (som uppdaterar samtliga fält, se ovan för diskussion om detta). För att simulera den UPDATE applikationen utför öppnar du den andra SQL*Plus-terminalen och skriver:
SQL>  UPDATE emp SET ename='FORD', job='ANALYST', mgr=7839, sal=3000 WHERE empno=7902;

1 row updated.

SQL>  COMMIT;

Commit complete.

6. Steg5: Användare A upptäcker att Fords lön inte har uppdaterats

När nu användare A klockan 14:11:10 loggar in på applikationen för att titta på data för Ford kommer han se att lönen har gått tillbaka till att bli 3000, men fältet MANAGER har uppdaterats till "King", se nedan:



För att själv kolla detta, kör följande SQL i någon av SQL*Plus-terminalerna:
SQL>  SELECT sal FROM emp WHERE empno=7902;

       SAL
----------
      3000