2

我有一个中央数据库,用于处理多个服务器读取和写入的用户信用。应用程序位于这些服务器之上,通过对每个请求执行以下操作来服务用户请求:

1.通过读取数据库来检查用户是否有足够的任务信用。
2.执行耗时请求
3.从用户帐户中扣除一个信用,将新的信用计数保存回db。

应用程序使用数据库的乐观锁定。所以可能会发生以下情况

1.请求a进来,看到用户x有足够的信用,
2.请求b进来,看到用户x有足够的信用,
3. a 执行工作
4. a 将新的信用计数保存回 db
5. b 执行工作
6. b 尝试将新的信用计数保存回 db,应用程序出现异常并且未能考虑此信用扣除。

使用悲观锁定,应用程序将需要显式地获取用户帐户的锁定以保证独占访问,但这会降低性能,因为系统有许多并发请求。
那么这个信用系统有什么好的新设计呢?

4

4 回答 4

1

使用时间戳/行版本方法,您可以在除 MySQL 之外的所有真实数据库引擎中找到该方法。

您可以通过这种方式使用 MySQL 模拟它们。有一个 TIMESTAMP 列(更新),每当更新行时就会更新。选择该列以及您需要的其余数据。将返回的时间戳用作 WHERE 子句中的条件,以便仅当时间戳与您读取该行时的时间戳相同时才会更新该行。

UPDATE table SET col1 = value WHERE id = 1 AND updated = timestamp_value_read

现在,当您运行更新并且时间戳不匹配时,将不会执行更新。您可以通过使用受影响的行来测试这一点,如果更新了零行,那么您知道该行在读取和写入之间被修改。以最适合您的应用程序和用户的方式在您的代码中处理该条件。

时间戳教程

于 2015-04-01T22:51:25.227 回答
1

您需要Escrow Transactional Method

您在向每个更新过程分配一些信用后记录剩余的信用,并将信用分配给(即托管)它们。一个进程重试直到成功的事务,该事务增加了它所需要的信用并减少了它所需要的信用;只有这样会使信用保持非负值,它才会成功。然后它会进行长时间的计算。无论计算是否成功,它都会应用一项交易来减少发放的信用。但是在成功时它也会增加资产,而在失败时它会增加剩余的信用。

于 2015-04-01T08:41:57.203 回答
1

您没有明确说明您想要实现什么,所以我假设您不想执行这项工作只是为了意识到由于信用低而导致它徒劳无功。

无锁

在步骤 (1) 上实施信用保留,并将工作 (2) 和扣除 (3) 与保留相关联。这样低信用用户将不会通过步骤(1)。

乐观锁定

由于在乐观锁定事后检测到冲突,我认为它不符合假设。

悲观锁定

不知道架构是不可能确定的,但我认为这对于杀死性能来说是夸大其词。您可以巧妙地结合 MySQL InnoDB事务隔离级别并以比完全锁定用户帐户更精细的粒度锁定读取。例如,使用SELECT ... LOCK IN SHARE MODEwhich 设置共享锁并允许读取其他事务。

Rick 对任务执行时间长于 MySQL 将等待 ( ) 的警告innodb_lock_wait_timeout适用于此。

于 2015-03-31T12:55:22.573 回答
1

这里有两种“锁定”机制,可以避免使用 InnoDB 的锁定机制,原因有两个:

  • BEGIN...COMMIT一项花费比您在InnoDB中花费的时间更长的任务。
  • 以不同于开始的程序(或不同的网页)结束的任务。

计划 A。 (假设竞争条件很少见,在这些罕见的情况下,第 2 步所浪费的时间是可以接受的。)

  1. (相同)通过从 db 读取来检查用户是否对任务有足够的信用。
  2. (同)执行耗时请求
  3. (添加)START TRANSACTION;
  4. (添加)再次检查用户是否有足够的信用。(ROLLABCK如果没有,则中止。)
  5. (与旧 #3 相同)从用户帐户中扣除一个信用,将新的信用计数保存回 db。
  6. (添加)COMMIT;

START..COMMIT是 InnoDB 事务的东西。如果竞态条件导致“x”在第 4 步没有得分,您将ROLLBACK不会执行第 4 步和第 5 步。

B 计划。 (这更复杂,但您可能更喜欢它。)

  1. 有一张桌子Locks用于锁定。它包含 user_id 和时间戳。
  2. START TRANSACTION;
  3. 如果 user_id 在 中Locks,则中止(ROLLBACK并退出)。
  4. INSERT INTO Locksuser_id 和 current_timestamp Locks(从而“锁定”'x')。
  5. COMMIT;
  6. 执行处理(原始步骤 1、2、3)
  7. DELETE FROM Locks WHERE user_id = 'x';autocommit=1这里就够了。)

一个潜在的问题:如果处理在第 6 步中终止,没有及时释放锁定,那么该用户将被永远锁定。“解决方案”是定期检查Locks任何“非常”旧的时间戳。如果找到,则假定处理已终止,并删除行。

于 2015-03-31T05:44:12.033 回答