在SQL数据库中处理并发更新的常用方法是什么?
考虑一个简单的SQL模式(约束和默认值未显示..)之类的
create table credits (
  int id,
  int creds,
  int user_id
);
目的是为用户存储某种信用,例如stackoverflow的声誉.
如何处理该表的并发更新?一些选择:
update credits set creds= 150 where userid = 1;
在这种情况下,应用程序检索当前值,计算新值(150)并执行更新.如果其他人同时做同样的事情,那么这就是灾难.我猜测包装当前值的撤销和事务中的更新将解决这个问题,例如,Begin; select creds from credits where userid=1; do application logic to calculate new value,  update credits set credits = 160 where userid = 1; end;在这种情况下,您可以检查新信用是否<0,如果负信用没有意义,则将其截断为0.
update credits set creds = creds - 150 where userid=1;
这种情况不需要担心并发更新,因为数据库负责一致性问题,但是有一些缺陷,即信用很高兴会变成负面的,这可能对某些应用程序没有意义.
那么简单地说,处理上面提到的(非常简单的)问题的公认方法是什么,如果db抛出错误怎么办?
bdo*_*lan 26
使用交易:
BEGIN WORK;
SELECT creds FROM credits WHERE userid = 1;
-- do your work
UPDATE credits SET creds = 150 WHERE userid = 1;
COMMIT;
一些重要的说明:
将事务与SQL存储过程相结合可以使后一部分更容易处理; 应用程序只会在事务中调用单个存储过程,并在事务中止时重新调用它.
小智 17
对于MySQL InnoDB表,这实际上取决于您设置的隔离级别.
如果使用默认级别3(REPEATABLE READ),则需要锁定影响后续写入的任何行,即使您处于事务中也是如此.在您的示例中,您将需要:
SELECT FOR UPDATE creds FROM credits WHERE userid = 1;
-- calculate --
UPDATE credits SET creds = 150 WHERE userid = 1;
如果您使用的是4级(SERIALIZABLE),那么简单的SELECT后跟更新就足够了.InnoDB中的第4级是通过读取锁定的每一行来实现的.
SELECT creds FROM credits WHERE userid = 1;
-- calculate --
UPDATE credits SET creds = 150 WHERE userid = 1;
但是在这个具体的例子中,由于计算(添加信用)很简单,可以在SQL中完成,这很简单:
UPDATE credits set creds = creds - 150 where userid=1;
将等同于SELECT FOR UPDATE,后跟UPDATE.
Abe*_*ROS 12
将代码包装在事务中,在某些情况下,无论您定义的隔离级别如何,都是不够的.
假设您有这些步骤和2个并发线程:
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
这个时间线:
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
事务阻止您使用错误的值覆盖creds值,但这还不够,因为我不想让任何错误失败.
我更喜欢一个永不失败的慢速进程,我在获取数据时采用"数据库行锁定"来解决问题(步骤2),以防止其他线程读取同一行,直到我完成它为止.
在SQL Server中有几种方法可以做,这是其中之一:
SELECT creds FROM credits WITH (UPDLOCK) WHERE userid = 1;
如果我使用此改进重新创建上一个时间线,您将获得以下内容:
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
使用新timestamp列的乐观锁可以解决这个并发问题。
UPDATE credits SET creds = 150 WHERE userid = 1 and modified_data = old_modified_date
| 归档时间: | 
 | 
| 查看次数: | 53686 次 | 
| 最近记录: |