SQL行锁和事务

时间:2019-04-20 15:29:37

标签: mysql sql database-design database-locking

我对关系数据库真的很陌生。我正在从事一个涉及财务的项目,所以我希望任何不会影响平衡的操作都不能同时发生,我想使用锁来实现,但是我不确定如何使用它们。我现在的愿景: 我要为每个操作创建一个单独的表,并在用户表中有一个余额字段,其值将从所有相关表中得出。令人难过的是,我实际上从不打算更新现有记录-仅添加它们。我想确保在这些表中一次只为每个用户插入一条记录。例如:3个事务同时发生,因此3个记录将被添加到任何相关表中。其中两条记录具有相同的用户ID,是我的用户表的外键,另一条具有不同的ID。我希望将具有相同外键的记录进行管道传输,而另一条记录则可以随时进行。我该如何实现?有没有更好的方法来解决这个问题?

2 个答案:

答案 0 :(得分:0)

  

我希望不会影响平衡的任何动作

为什么?

  

我想通过锁来实现

为什么?

举一个反例。假设您要避免帐户余额出现负数。当用户提取500美元时,如何在没有锁的情况下对其建模。

UPDATE accounts
   SET balance = balance - 500
 WHERE accountholderid = 42
   AND balance >= 500

此方法无需任何显式锁定,并且可以安全地进行并发访问。您将必须检查更新计数,如果更新计数为0,则说明您已透支该帐户。

(我知道MySQL仍将获得行锁)

拥有分类帐仍然很有意义,但是即使在那儿,对我来说锁的需求也不明显。

答案 1 :(得分:0)

  1. 对所有表使用ENGINE=InnoDB
  2. 使用交易记录:

    BEGIN;
    do all the work for a single action
    COMMIT;
    

单个操作的经典示例是从一个帐户中删除资金,然后将其添加到另一个帐户中。删除将包括透支检查,在这种情况下,您将拥有ROLLBACK而不是COMMIT的代码。

您获得的锁可确保单个操作的所有操作已完全完成,或者什么也没有完成。如果系统在BEGINCOMMIT之间崩溃,这甚至适用。

没有begin和commit,但是使用autocommit = ON,每个语句被begin和commit隐式包围。那就是先前答案中的UPDATE示例是'atomic'。但是,如果需要将从一个帐户中扣除的钱添加到另一个帐户中,如果在UPDATE之后发生崩溃,会发生什么情况?钱消失了。所以,您真的需要

 BEGIN;
 if not enough funds, ROLLBACK and exit
 UPDATE to take money from one account
 UPDATE to add that money to another account
 INSERT into some log or audit trail to track all transactions
 COMMIT;

在每个步骤之后进行检查-回滚并针对任何意外错误采取规避措施。

如果在“同一时间”发生2个(或更多)动作,会发生什么?

  • 一个在等待另一个。
  • 有一个死锁,并且强制执行了ROLLBACK。

但是,在任何情况下,数据都不会被弄乱。

进一步的说明...在某些情况下,您需要FOR UPDATE

BEGIN;
SELECT some stuff from a row FOR UPDATE;
test the stuff, such as account balance
UPDATE that same row;
COMMIT;

FOR UPDATE对其他线程说:“请放开这一行,我很可能会对其进行更改;请等待我完成。”如果没有FOR UPDATE,则另一个线程可能会潜入并耗尽您以为在那里的钱的帐户。

对您的一些想法发表评论:

  • 一个表通常足以满足许多用户及其帐户。它包含每个帐户的“当前”余额。我提到了“日志”;那将是一个单独的表;它会包含一个“历史记录”(而不是仅包含“当前”信息)。
  • FOREIGN KEYs在此讨论中基本上无关紧要。它们有两个作用:验证另一个表是否应有一行;并隐式创建一个INDEX,以加快检查速度。
  • 管道铺设?如果您每秒执行的事务不超过一百次,那么BEGIN..COMMIT逻辑就是您所需要担心的。
  • “相同时间”和“同时”是误用的术语。两个用户不可能在“同一时间”访问数据库-考虑浏览器延迟,网络延迟,操作系统延迟等。再加上大多数这些步骤都迫使活动进入单个文件的事实。网络迫使一条消息先到达另一条消息。同时,如果您的“交易”之一花费了0.01秒,那么谁在乎“同时”请求是否必须等待它完成。关键是我所描述的将在需要时强制“等待”,以免弄乱数据。

总而言之,仍然会有一些“同时”-如果事务不涉及相同的行,那么从BEGINCOMMIT所花费的几毫秒可能会重叠。考虑一下几乎同时发生的两个事务的时间表:

BEGIN;  -- A
pull money from Alice  -- A
      BEGIN;   -- B
      pull money from Bobby  -- B
give Alice's money to Alan  -- A
      give Bobby's money to Betty  --B
COMMIT;   --A
      COMMIT;  --B