2

我正在开发一个票务系统,用户在认领之前一次性托管大量票(基本上所有没有缺货的票)。这些票显示给用户,他们可以选择他们想要领取的任何票。

如果两个用户试图同时托管同一张票并且没有足够的票,则此托管系统可能会引入竞争条件,例如:

剩余票数:1

用户 A 点击页面,查看剩余票数。还剩 1 张票 用户 B 点击页面,查看剩余票数。还剩 1 张票

由于他们都剩下一张票,他们都会托管这张票,使票剩下-1。

如果可能的话,我想避免锁定,并且想知道带有子查询的语句是否像

INSERT INTO ticket_escrows (`ticket`,`count`) 
SELECT ticket,tickets_per_escrow FROM tickets WHERE tickets.total > (
    COALESCE(
        SELECT SUM(ticket_escrows.count) FROM ticket_escrows 
        WHERE ticket_escrows.ticket = tickets.id
        AND ticket_escrows.valid = 1
    ,0)
    +
    COALESCE(
        SELECT SUM(ticket_claims.count) 
        FROM ticket_claims
        WHERE ticket_claims.ticket = tickets.id
    ,0)
)

将是原子的,允许我在不锁定的情况下防止竞争条件。

具体来说,我想知道上述查询是否会阻止以下情况的发生:

Max tickets: 50 Claimed/Escrowed tickets: 49
T1: start tx -> sums ticket escrows --> 40
T2: start tx -> sums ticket escrows --> 40
T1: sums ticket claims --> 9
T2: sums ticket claims --> 9
T1: Inserts new escrow since there is 1 ticket left --> 0 tickets left
T2: Inserts new escrow since there is 1 ticket left --> -1 tickets left

我正在使用 InnoDB。

4

2 回答 2

2

要回答您的问题“如果带有子查询的语句......将是原子的”:在您的情况下,是的。

仅当包含在事务中时它才会是原子的。由于您声明您使用的是 InnoDB,因此即使带有子查询的查询也是 SQL 语句,因此在事务中执行。引用文档

在 InnoDB 中,所有用户活动都发生在事务中。如果启用了自动提交模式,则每个 SQL 语句都会单独形成一个事务。

...如果语句返回错误,则提交或回滚行为取决于错误。

此外,隔离级别很重要。

在 SQL:1992 事务隔离级别方面,默认 InnoDB 级别是 REPEATABLE READ

REPEATABLE READ 对您来说可能还不够,这取决于您的程序的逻辑。它防止事务写入由另一个事务读取的数据,直到读取事务完成,但是,幻读是可能的。检查SET TRANSACTION以了解如何更改隔离级别。


要回答您的第二个问题“如果上述查询将阻止以下情况的发生……”:在具有 SERIALIZABLE 隔离级别的事务中,它不会发生。我相信默认级别在您的情况下也应该是安全的(假设tickets.total不会改变),但我更希望有人确认它。

于 2013-05-30T15:22:19.543 回答
1

你真的留下了很多关于你希望它如何工作的信息,这就是为什么你没有得到更多/更好的答案。

票务是一个权衡问题。如果您向某人展示有 10 张门票可用,则您要么立即让其他所有人无法使用所有 10 张门票(这对其他人不利),要么您不这样做,这意味着该人可能会订购一张其他人抢购的门票,而他们正在决定买哪张票。“托管”系统并没有真正帮助问题,因为它只是将问题从购买哪些门票转移到托管哪些门票。

在您不锁定其他人的期间,最佳做法是设计您的 SQL,如果在您处理数据时其他人修改了数据,则更新或插入将失败。这可以像每次更改行时增加行中的计数器并在 UPDATE 语句的 WHERE 子句中使用该计数器(加上主键)一样简单。如果计数器发生变化,则更新失败并且您知道您已经输掉了比赛。

我不明白你想要发生什么或你的数据结构足以给你更多建议。

于 2013-05-30T08:36:54.913 回答