2014-01-21 86 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“类型” +在锁定表中删除。