安全地减少用户余额列。我应该使用乐观锁吗?

时间:2018-01-29 15:48:56

标签: php mysql sql concurrency doctrine-orm

我有一个简单的Silex Web应用程序,带有MySQL / Doctrine ORM。每个用户都有balance(它是一个简单的应用,所以只有列很好)我需要在一些操作后减少它(检查它当然是> 0)。

据我所知,我可以使用乐观锁定来避免冲突/漏洞。我已阅读文档http://docs.doctrine-project.org/projects/doctrine-orm/en/latest/reference/transactions-and-concurrency.html,但我找不到任何关于使用它的完整示例。

我从哪里获得"预期版本"?我是否需要将其作为输入传递(隐藏表单字段)?还是有更好的方法?文档说了一些关于会话的内容,但是我不知道如何将它存储在那里(每个请求的更新会话?)。

此外,如果我将其作为输入传递,那么据我所知,在捕获OptimisticLockException之后无法自动重复查询而不通知用户该问题? (例如,如果用户打开两个选项卡并逐个提交请求)

我的目标是在用户同时发送多个请求并且余额只减少一次等时防止潜在问题。因此,如果能够在不涉及用户的情况下自动重复锁定错误,那将是一件好事。因为如果我通过表单传递它,那么很可能因为多个选项卡而出现此错误。所以看起来有点复杂,也许还有别的东西而不是乐观的锁定?

3 个答案:

答案 0 :(得分:5)

创建名为"版本"的列。在"用户"表并使其成为"时间戳"列(使用"更新CURRENT_TIMESTAMP"属性)。所以,"用户" ORM类如下所示:

function signin() {
 var action = $("#form-signin").attr("name");
}

现在,使用"版本"。

读取当前记录
class User
{
    // ...
    /** @Version @Column(type="timestamp") */
    private $version;
    // ...
}

答案 1 :(得分:3)

您应该仅对无法以原子方式执行的操作使用锁定。因此,如果可能,请避免查询对象,检查金额,然后更新它。相反,如果你这样做:

update user set balance = (balance + :amount) 
where (balance + :amount) >= 0 
and id = :user_id

您将在一次操作中检查并更新,如果检查通过且余额已更新,则更新的行数将为1,否则为0。

答案 2 :(得分:0)

乐观锁将允许并发访问读取实体(意味着可能有一些线程将读取过期数据),而悲观锁将锁定读取,如果有人在该注册表上执行操作。

取决于您希望并发访问的准确度如何?! 读取过时的数据是否可以?

例如:

{OTIMISTIC LOCK}
Thread1 -> read(balance1[200$][version=1])
Thread2 -> read(balance1[200$][version=1])
Thread1 -> balance.add(100$).save()[300$ total and version=2]
Thread2 -> balance.add(50$).save()[OtimisticLockError Version-> 2 != 1]

{PESSIMISTIC LOCK}
Thread1 -> read(balance1[200$]) [lock for update | select for update |... depends on DB])
Thread2 -> read(balance1) [Pessimistic lock exception]
Thread1 -> balance.add(100$).save()[300$ total]
Thread1 -> release lock balance1
Thread2 -> read(balance1[300$]) Ok

OPTIMISTIC LOCK

PESSIMISTIC LOCK

PESSIMISTIC vs. OPTIMISTIC