2014-01-21 129 views
3

我在製作遊戲時有一些抽獎活動,我有建議從MyISAM切換到InnoDB並開始使用FOR UPDATE,所以彩票(從1到16)可以不能多賣一次。SELECT FOR UPDATE WITH INSERT INTO

現在我想知道,FOR UPDATE是如何工作的。

我已經看到了網絡上,它是這樣的:

SELECT * FROM [table] WHERE [column] = [value] FOR UPDATE 
UPDATE [table] SET [column] = [new_value] 

但我的問題是關於以下,將它也爲INSERT工作?

我的數據庫設計:

CREATE TABLE IF NOT EXISTS `Lottery` (
    `id` varchar(50) NOT NULL, 
    `preferedLotteryId` varchar(50) NOT NULL, 
    `winningTicketId` int(11) NOT NULL DEFAULT '-1', 
    `createdOn` datetime NOT NULL DEFAULT '1001-00-00 00:00:00', 
    `startedOn` datetime NOT NULL DEFAULT '1001-00-00 00:00:00', 
    `finishedOn` datetime NOT NULL DEFAULT '1001-00-00 00:00:00', 
    `active` tinyint(1) NOT NULL, 
    `deliveredOn` datetime NOT NULL DEFAULT '1001-00-00 00:00:00', 
    `preferedDeliverMethod` int(2) NOT NULL DEFAULT '-1', 
    `deliveredMethod` int(2) NOT NULL DEFAULT '-1', 
    `deliveredByAccountId` varchar(50) DEFAULT NULL, 
    PRIMARY KEY (`id`) 
) ENGINE=InnoDB DEFAULT CHARSET=latin1; 

CREATE TABLE IF NOT EXISTS `LotteryBid` (
    `bidId` varchar(50) NOT NULL, 
    `accountId` varchar(50) NOT NULL, 
    `auctionId` varchar(50) NOT NULL, 
    `ticketId` int(50) NOT NULL, 
    `datetime` datetime NOT NULL DEFAULT '1001-00-00 00:00:00', 
    PRIMARY KEY (`bidId`) 
) ENGINE=InnoDB DEFAULT CHARSET=latin1; 

這意味着這種設計將需要FOR UPDATE類似於:

SELECT * FROM Lottery WHERE id = [id] FOR UPDATE 
INSERT INTO LotteryBids SET [LotteryBids.values & Lottery.id] 

SELECT * FROM Lottery WHERE id = [id] FOR UPDATE 
INSERT INTO LotteryBids, Lottery SET [LotteryBids.values] WHERE Lottery.id = [id] 

但是,我不知道這是否方式FOR UPDATE甚至可能會略有可能。

我對這種海量數據交互相當陌生,我不知道從哪裏開始。

我希望你們中的一些人能幫助我。

親切的問候, Larssy1


我認爲下面的想法會轉變爲一個可能的解決方案:

思考1:

用戶正在購買彩票與門票ID 5.目前這個 彩票已售出3張門票,所以我想鎖定這個 彩票的行數,直到我h大家承諾/完成了這個插入。

思考2:

用戶正在購買帶票ID彩票5.目前這個 拍賣與票號5無人售票,所以我想保留一個條目 這次拍賣具有類似的拍賣ID和票證ID。在查詢完成之前, 會阻止進一步添加。


使用的查詢

選擇彩票(包括附加信息):

SELECT au.*, asp.* FROM Lottery au, LotteryPrefered asp 
WHERE au.preferedAuctionId = asp.id AND au.id = '" . $_auctionId . "' 

OR(不含附加信息)

SELECT au.* FROM Lottery au 
WHERE au.id = '" . $_auctionId . "' 

購買蜱等:

INSERT INTO LotteryBids (bidId, accountId, auctionId, ticketId, datetime) 
VALUES ('" . $guid . "', '" . $_accountId . "', '" . $_auctionId . "', 
    '" . $_ticketId . "', NOW()) 
+0

我猜你的'INSERT'語句是僞代碼,但'WHERE'子句在INSERT語句中完成了什麼? –

+0

這幾乎只是爲了給'FOR UPDATE'敲'',''更新'發生了。但我不知道它是如何工作的。 – larssy1

回答

8

SELECT ... FOR UPDATE與UPDATE

使用交易帶的InnoDB(自動提交關閉),一個SELECT ... FOR UPDATE允許一個會話暫時鎖定一個特定的記錄(或記錄),以便其他會話無法更新它。然後,在同一個事務中,會話實際上可以在同一條記錄上執行UPDATE並提交或回滾事務。這將允許您鎖定該記錄,以便其他會話可以更新它,同時也許您可以執行其他業務邏輯。

這是通過鎖定完成的。 InnoDB利用索引鎖定記錄,因此鎖定現有記錄似乎很容易 - 只需鎖定該記錄的索引即可。

SELECT ... FOR用INSERT,UPDATE

但是,要使用SELECT ... FOR UPDATEINSERT,你如何鎖定爲不存在的記錄的指數?如果您使用的默認隔離級別爲REPEATABLE READ,InnoDB也將利用間隙鎖。只要您知道要鎖定的id(或甚至範圍的ID),InnoDB就可以鎖定該差距,因此在我們完成該操作之前,無法將其他記錄插入該間隙。

如果您id列是一個自動增量列,然後用SELECT ... FOR UPDATEINSERT INTO是有問題的,因爲你不知道什麼新的id是直到你插入它。但是,由於您知道要插入的id,因此SELECT ... FOR UPDATEINSERT將起作用。

看看ticketid5可供拍賣12

SELECT * FROM LotteryBids 
WHERE auctionid = 12 AND ticketid = 5 
FOR UPDATE 

如果沒有行返回,那麼它不存在。將其插入:

INSERT INTO LotteryBids (bidId, accountId, auctionId, ticketId, datetime) 
VALUES ('abcd-efgh-ijkl-mnop', 100, 12, 5, NOW()) 

保持簡單

以這樣的方式與併發問題處理可能很難理解,我經常看到人們過於複雜的問題。我建議詢問另一個問題,詳細說明您要完成的任務,提供您提出的解決方案並徵求建議。

+0

我明白了,謝謝。 ID列不是自動遞增的,因爲它是一個varchar,它包含一個使用PHP類創建的GUID。如果我想給你一些有用的數據,你能否給我一個這樣的「FOR UPDATE」的例子?我知道這個網站不是主要用於案例準備的例子,「讓社區構建你的軟件」。但是我可以向前推進的一個例子將會受到高度評價。那麼,如果你願意,那麼測試數據就足夠了嗎? - 問題也更新 – larssy1

+0

我還不明白什麼是「購買彩票」。如果添加插入語句,我會看看是否可以演示「SELECT ... FOR UPDATE」語句。 –

+0

@MarcusAdams你的回答完全正確。我可能會添加一個附加說明,在這種情況下,「缺口」被鎖定*,因此請確保這是您真正想要的。您將無法在整個範圍內插入任何其他值的數據,這可能是表格的很大一部分。例如,在一個包含1,2,3,4,5的表中,並且你鎖定了10,它實際上將鎖定範圍從5 - > supremum,這意味着你不能鎖定> = 5的任何鍵。這是經常看到的與增量鍵。使用GUID/UUID時,您基本上可以鎖定表的任意大部分。 – jeremycole

0

您可以嘗試使用一種鎖定/互斥使用備用表: 看到http://blog.udby.com/archives/14(實現簡單的數據庫旗語)

開始對給定類型之前,您鎖定使用INSERT「類型」 +在鎖定表中刪除。