2013-05-03 74 views
2

我正在制定一个票务系统,用户在声明它们之前立即托管大量票(基本上所有没有缺货的票)。这些门票向用户显示,他们可以选择他们想要声明的任何门票。MySQL INSERT SELECT WHERE竞赛条件

该中介系统可以引入竞争条件,如果两个用户尝试到托管在同一时间同一门票和没有足够的门票,如:

剩票: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。

+1

“*我想尽量避免锁定*” - 为什么?即使该语句是原子的(我认为它可能取决于您的隔离级别),它的原子性将通过使用锁来实施... – eggyal 2013-05-29 03:48:15

+1

您应该锁定UserA的行,因此当UserB的查询命中该表时,它将等待“ INSERT'完成并获得正确的门票数量,即0。 – Stoleg 2013-05-29 12:13:33

+0

不应在第一个子查询中有SELECT ID,ticket_per_escrow? – Mifeet 2013-05-30 15:43:56

回答

2

回答你的问题“如果子查询语句将是原子的”:就你而言,是的。

只有在封闭在事务中时它才是原子。既然你声明你使用的是InnoDB,那么即使是使用子查询的查询也是一条SQL语句,并且在事务中执行。引用documentation

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

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

另外,还有isolations levels的事。

在SQL方面:1992事务隔离级别,默认InnoDB的级别是可重复读

重复读你可能没有足够的,这取决于你的程序的逻辑。它可以防止事务写入另一个事务读取的数据,直到读取事务完成,但是可以使用phantom reads。查询SET TRANSACTION了解如何更改隔离级别。


要回答你的第二个问题:“如果上面的查询将防止发生下列...”:与SERIALIZABLE隔离级别不能发生的交易。我相信在你的情况下,默认级别应该也是安全的(假设tickets.total不会改变),但是我希望让它由某人确认。

1

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

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

在您没有锁定其他人的期间,最好的做法是制作您的SQL,以便更新或插入操作失败,如果其他人在您处理数据时修改了数据。这可以像在每次更改行时递增行中的计数器一样简单,并在UPDATE语句的WHERE子句中使用该计数器(加上主键)。如果计数器改变了,那么更新失败,并且你知道你已经失去了比赛。

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

+0

绝对。问题不是真正的技术问题,而是一个商业决策。 – RandomSeed 2013-05-31 21:52:28

+0

@YaK,直接的问题是OP没有具体说明他正在尝试实施哪些业务决策。如果其他人可以在他们托管时抢夺他们,那么托管票的含义是什么? – 2013-05-31 22:08:40

+0

是的,我的意思是需要做出这个业务决策,然后技术反应会变得很明显(可能很简单)。我的意图是赞同你,对不起,如果这不明确。 – RandomSeed 2013-05-31 22:13:46