Question

If the answer to this question depends on the DMBS, I'd be interested to hear the answer for Oracle 11g or higher and SQL Server 2012.

We have a table which has a foreign key that references itself:

CREATE TABLE Versions (
    Id INT IDENTITY(1,1) NOT NULL,
    [Date] DATETIME NOT NULL,
    BasedOnVersion INT NULL  -- foreign key that references Versions
)

We have Stored Procedures that insert new records into the Versions table. If these are running concurrently, we need to make sure that no two versions reference the same other version, so there must not be any forks in the version hierarchy (unless we're deliberately creating a fork):

How it should run                          

Transaction 1 reads current version 17
Transaction 1 writes new version 18 based on 17
Transaction 2 reads current version 18
Transaction 2 writes new version 19 based on 18

How it should NOT run

Transaction 1 reads current version 17
Transaction 2 reads current version 17
Transaction 1 writes new version 18 based on 17
Transaction 2 writes new version 19 based on 17

In the second case, we have two versions that are based on the same version.

So we need a way to serialize the two transactions. First, we thought of using ISOLATION LEVEL SERIALIZABLE, but isolation levels do not have any impact on INSERTs, so they're not the solution.

Another way would be to acquire a lock on the Versions table at the beginning of the transactions. This could work with SQL Server, but not with Oracle, since Oracle's LOCK TABLE IN EXCLUSIVE MODE does not prevent transaction 2 from reading the table.

So what's the best solution for this kind of problem?

Was it helpful?

Solution

This is known as the "Lost Update Problem" and it has a number of solutions. Not all of them require pessimistic locks.

One way is to modify your "write" method to use a predicate in the WHERE clause that will stop the update from updating the version if the current version is not the same as what that session read.

i.e.

  1. Transaction 1 reads current version 17
  2. Transaction 2 reads current version 17
  3. Transaction 1 writes new version 18 based on 17
  4. Transaction 2 tries to write new version 18, but the UPDATE v=18 WHERE v=17 results in "0 records updated", and the session handles this as an error, e.g. "another user has modified this version, please requery and try again".

I wrote on this back in 2005 and listed a number of approaches:

http://jeffkemponoracle.com/2005/10/21/avoiding-lost-updates-protecting-data-in-a-multi-user-environment/

OTHER TIPS

You could use a sequence for versions. Sequences are safe for concurrent operations

So;

Transaction 1 reads nextvalue version 18 ( So current is 18 - 1 = 17 )
Transaction 2 reads nextvalue version 19 ( So current is 19 - 1 = 18)
Transaction 1 writes new version 18 based on 17
Transaction 2 writes new version 19 base on 18

It seems that you determine the latest version along the lines of

 select MAX(ID) from Versions

So if that is included in the serializable transaction, either as part of the insert or separately, then the inserts will be serializable

set transaction isolation level serializable

begin tran
    insert Versions (basedonversion)
    select MAX(ID) from Versions
commit tran 
Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top