Richard Klein <[EMAIL PROTECTED]> wrote: Dan Kennedy wrote:
> On Wed, 2007-10-24 at 21:38 -0700, Richard Klein wrote:
>> As I was thinking about the locking mechanism in SQLite 3,
>> it occurred to me that the following race condition could
>> occur.
>>
>> Imagine a joint bank account with a balance of $10,000.
>> The wife makes a withdrawal of $1,000 at ATM 'A' (serviced
>> by process A in the bank's mainframe), while at the same
>> time the husband makes a deposit of $1,000 at ATM 'B'
>> (serviced by process B).  The steps performed by each
>> process are as follows:
>>
>> Process A
>> ---------
>> BEGIN TRANSACTION;
>> SELECT balance FROM accounts WHERE accountId = '123-45-6789';
>> UPDATE accounts SET balance = 
>>                  WHERE accountId = '123-45-6789';
>> COMMIT;
>>
>> Process B
>> ---------
>> BEGIN TRANSACTION;
>> SELECT balance FROM accounts WHERE accountId = '123-45-6789';
>> UPDATE accounts SET balance = 
>>                  WHERE accountId = '123-45-6789';
>> COMMIT;
>>
>> Both processes open the accounts database, obtain SHARED
>> locks, and proceed at about the same pace.  Process A
>> updates her local cache with a new balance of $900, while
>> process B updates his local cache with a new balance of
>> $11,000.
> 
> Evaluating the UPDATE requires a RESERVED lock on the database 
> file. Since only one process can hold the RESERVED lock, this
> particular scenario cannot occur. One or other of the updates
> will fail with SQLITE_BUSY.
> 
> Dan.

I went over the documentation again, and it appears that you
are correct:  A process wanting to do an UPDATE must first
acquire a RESERVED lock.

However, I believe there is still a race condition.  Assume
that process B is the first to reach the UPDATE statement,
and so process B gets the RESERVED lock (which he later
escalates to PENDING and EXCLUSIVE).  Then process A is
stuck in a busy wait until process B commits and releases
his locks.  At this process A acquires the RESERVED lock
and does *her* UPDATE, but she does it using the old, now
*stale*, value of the account balance ($10,000).  That is,
she computes the new balance as ($10,000 - $1,000) = $9,000,
which is *wrong*.

She needs to *re-execute* her SELECT statement to pick up
the new balance of $11,000.  Or, in general:

She needs to start her transaction over from the beginning.

- Richard Klein

-----------------------------------------------------------------------------
To unsubscribe, send email to [EMAIL PROTECTED]
-----------------------------------------------------------------------------
I submit that there is no race condition present. Merely a progamming error.

BEGIN TRANSACTION;
SELECT balance FROM accounts WHERE accountId = '123-45-6789';
 UPDATE accounts SET balance = <balance - withdrawal>
                  WHERE accountId = '123-45-6789';
COMMIT;

This is a comman and naive assumption that the balance selected will remain
consistent. Even in other DB's such as oracle the problem described would 
persist.

Two my knowledge there are two solutions.
1: Don't programatically put the balance into a variable.
BEGIN TRANSACTION;
 SELECT balance FROM accounts WHERE accountId = '123-45-6789';
 UPDATE accounts SET balance = balance - <withdrawal>
                  WHERE accountId = '123-45-6789';
COMMIT;

2:  Lock the row with the select statement, this requires
    additional syntax, which sqlite does not support.
BEGIN TRANSACTION;
 SELECT balance FOR UPDATE 
       FROM accounts WHERE accountId = '123-45-6789';

 UPDATE accounts SET balance = <balance - withdrawal>
                  WHERE accountId = '123-45-6789';
COMMIT;




 


Reply via email to