我正在开发一个票务系统,在这个系统中,用户可以在申请之前立即托管大量的门票(基本上所有门票都没有缺货)。这些票证显示给用户,他们可以选择他们想要声明的票证。
如果两个用户试图同时托管相同的门票并且没有足够的门票,则此托管系统可能会引入竞争条件,如:
门票离开: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。
答案 0 :(得分:2)
回答你的问题“如果带有子查询的语句......将是原子的”:在你的情况下,是的。
只有封闭在事务中才会是原子的。由于您声明正在使用InnoDB,因此即使使用子查询,查询也是一个SQL语句,因此在事务中执行。引用documentation:
在InnoDB中,所有用户活动都发生在事务中。如果启用了自动提交模式,则每个SQL语句都会单独形成一个事务。
...如果语句返回错误,则提交或回滚行为取决于错误。
另外,isolations levels很重要。
就SQL:1992事务隔离级别而言,默认的InnoDB级别是REPEATABLE READ
REPEATABLE READ 对您来说可能不够,具体取决于程序的逻辑。它可以防止事务写入另一个事务读取的数据,直到读取事务完成,然而phantom reads成为可能。检查SET TRANSACTION 以了解如何更改隔离级别。
回答第二个问题“如果上述查询会阻止以下情况发生......”:在具有SERIALIZABLE隔离级别的事务中,它不会发生。我认为默认级别在您的情况下也应该是安全的(假设tickets.total
没有改变),但我更愿意让某人确认。
答案 1 :(得分:1)
你真的留下了很多关于你希望如何工作的信息,这就是为什么你没有得到更多/更好的答案。
票务是一个权衡问题。如果你向某人展示有10张门票,你要么立即让所有10张门票对其他人都不可用(这对其他人都不好),要么你不这样做,这意味着这个人可能会订购一张其他人抢购的门票。他们决定拿哪张票。 “托管”系统并没有真正帮助解决问题,因为它只是将购买门票的问题转移到托管的门票上。
在您没有锁定其他人的期间,最佳做法是以这样的方式制作SQL:如果其他人在您处理数据时修改了数据,则更新或插入将失败。这可以像每次更改行时在行中递增计数器一样简单,并在UPDATE语句的WHERE子句中使用该计数器(加上主键)。如果计数器发生变化,则更新失败,您知道自己已经输掉了比赛。
我不明白你想要发生什么,或者你的数据结构足以给你更多的建议。