How to deal with concurrent updates in databases?

后端 未结 9 1655
名媛妹妹
名媛妹妹 2020-11-27 12:24

What\'s the common way to deal with concurrent updates in an SQL database ?

Consider a simple SQL schema(constraints and defaults not shown..) like

c         


        
相关标签:
9条回答
  • 2020-11-27 13:02

    For MySQL InnoDB tables, this really depends on the isolation level you set.

    If you are using the default level 3 (REPEATABLE READ), then you would need to lock any row that affects subsequent writes, even if you are in a transaction. In your example you will need to :

    SELECT FOR UPDATE creds FROM credits WHERE userid = 1;
    -- calculate --
    UPDATE credits SET creds = 150 WHERE userid = 1;
    

    If you are using level 4 (SERIALIZABLE), then a simple SELECT followed by update is sufficient. Level 4 in InnoDB is implemented by read-locking every row that you read.

    SELECT creds FROM credits WHERE userid = 1;
    -- calculate --
    UPDATE credits SET creds = 150 WHERE userid = 1;
    

    However in this specific example, since the computation (adding credits) is simple enough to be done in SQL, a simple:

    UPDATE credits set creds = creds - 150 where userid=1;
    

    will be equivalent to a SELECT FOR UPDATE followed by UPDATE.

    0 讨论(0)
  • Table can be modified as below, introduce new field version to handle optimistic locking. This is more cost effective and efficient way to achieve better performance rather than using locks at database level create table credits ( int id, int creds, int user_id, int version );

    select creds, user_id, version from credits where user_id=1;

    assume this returns creds = 100 and version=1

    update credits set creds = creds*10, version=version+1 where user_id=1 and version=1;

    Always this ensure that whoever is having latest version number can only updates this record and dirty writes will not be allowed

    0 讨论(0)
  • 2020-11-27 13:08

    Wrapping the code inside a transaction it's not enough in some cases regardless the isolation level you define (e.g imaging you have deployed your code into 2 different servers in production).

    Let's say you have these steps and 2 concurrency threads:

    1) open a transaction
    2) fetch the data (SELECT creds FROM credits WHERE userid = 1;)
    3) do your work (credits + amount)
    4) update the data (UPDATE credits SET creds = ? WHERE userid = 1;)
    5) commit
    

    And this time line:

    Time =  0; creds = 100
    Time =  1; ThreadA executes (1) and creates Txn1
    Time =  2; ThreadB executes (1) and creates Txn2
    Time =  3; ThreadA executes (2) and fetches 100
    Time =  4; ThreadB executes (2) and fetches 100
    Time =  5; ThreadA executes (3) and adds 100 + 50
    Time =  6; ThreadB executes (3) and adds 100 + 50
    Time =  7; ThreadA executes (4) and updates creds to 150
    Time =  8; ThreadB tries to executes (4) but in the best scenario the transaction
              (depending of isolation level) won't allow it and you get an error
    

    The transaction prevents you to override the creds value with a wrong value but it's not enough because I don't want to fail any error.

    I prefer instead an slower process that never fail and I solved the problem with a "database row lock" in the moment I fetch the data (step 2) that prevents other threads can read the same row until I'm done with it.

    There are few ways to do in SQL Server and this is one of them:

    SELECT creds FROM credits WITH (UPDLOCK) WHERE userid = 1;
    

    If I recreate the previous time line with this improvement you get something like this:

    Time =  0; creds = 100
    Time =  1; ThreadA executes (1) and creates Txn1
    Time =  2; ThreadB executes (1) and creates Txn2
    Time =  3; ThreadA executes (2) with lock and fetches 100
    Time =  4; ThreadB tries executes (2) but the row is locked and 
                       it's has to wait...
    
    Time =  5; ThreadA executes (3) and adds 100 + 50
    Time =  6; ThreadA executes (4) and updates creds to 150
    Time =  7; ThreadA executes (5) and commits the Txn1
    
    Time =  8; ThreadB was waiting up to this point and now is able to execute (2) 
                       with lock and fetches 150
    Time =  9; ThreadB executes (3) and adds 150 + 50
    Time = 10; ThreadB executes (4) and updates creds to 200
    Time = 11; ThreadB executes (5) and commits the Txn2
    
    0 讨论(0)
  • 2020-11-27 13:11

    For the first scenario you could add another condition in the where-clause to make sure you won't overwrite changes made by a concurrent user. E.g.

    update credits set creds= 150 where userid = 1 AND creds = 0;
    
    0 讨论(0)
  • 2020-11-27 13:14

    Optimistic locking using a new timestamp column can solve this concurrency issue.

    UPDATE credits SET creds = 150 WHERE userid = 1 and modified_data = old_modified_date
    
    0 讨论(0)
  • 2020-11-27 13:14

    If you store a last update timestamp with the record, when you read the value, read the timestamp as well. When you go to update the record, check to make sure the timestamp matches. If someone came in behind you and updated before you, the timestamps would not match.

    0 讨论(0)
提交回复
热议问题