2013-05-03 73 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