【问题标题】:SELECT FOR UPDATE with INSERT INTO使用 INSERT INTO 选择更新
【发布时间】:2014-02-11 05:29:54
【问题描述】:

我正在制作某种游戏彩票,我得到了从 MyISAM 切换到 InnoDB 并开始使用 FOR UPDATE 的建议,因此彩票(从 1 到 16 不等)无法卖出更多不止一次。

现在我想知道,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 这种方式是否有可能。

我对这种海量数据交互还很陌生,我不知道从哪里开始。

我希望你们中的一些人可以帮助我。

亲切的问候, 拉西1


我认为以下想法会转化为可能的解决方案:

想法一:

一个用户正在购买一张彩票 ID 为 5 的彩票。目前这个 彩票已售出 3 张彩票,所以我想为此锁定行数 抽签,直到我提交/完成此插入。

想法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())

【问题讨论】:

  • 我猜你的INSERT 语句是伪代码,但是WHERE 子句在INSERT 语句中会完成什么?
  • 这几乎只是为了给FOR UPDATE一个小小的“敲门”,发生了一个“更新”。但我不知道它实际上是如何工作的。

标签: mysql select insert transactions innodb


【解决方案1】:

您可以尝试将一种锁/MUTEX 与备用表一起使用: 见http://blog.udby.com/archives/14(实现简单的数据库信号量)

在开始处理给定类型之前,您在锁定表中使用 INSERT + DELETE 锁定“类型”。

【讨论】:

    【解决方案2】:

    SELECT ... FOR UPDATE with UPDATE

    使用 InnoDB 事务(关闭自动提交),SELECT ... FOR UPDATE 允许一个会话临时锁定特定记录(或记录),以便其他会话无法更新它。然后,在同一事务中,会话实际上可以对同一记录执行UPDATE 并提交或回滚该事务。这将允许您锁定记录,以便在您执行一些其他业务逻辑时没有其他会话可以更新它。

    这是通过锁定来完成的。 InnoDB 利用索引来锁定记录,因此锁定现有记录似乎很容易——只需锁定该记录的索引即可。

    SELECT ... FOR UPDATE with INSERT

    但是,要将SELECT ... FOR UPDATEINSERT 一起使用,如何为尚不存在的记录锁定索引?如果您使用REPEATABLE READ 的默认隔离级别,InnoDB 还将使用 gap 锁。只要您知道要锁定的 id(甚至是 id 范围),InnoDB 就可以锁定间隙,因此在我们完成之前不能在该间隙中插入其他记录。

    如果您的 id 列是自动增量列,那么带有 INSERT INTOSELECT ... FOR UPDATE 会出现问题,因为在插入之前您不会知道新的 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())
    

    保持简单

    以这种方式处理并发问题可能很难理解,而且我经常看到人们将事情过于复杂化。我建议提出一个单独的问题,详细说明您要完成的工作,提供您提出的解决方案,并征求建议。

    【讨论】:

    • 我明白了,谢谢。 ID 列不是自动增量的,因为它是一个 varchar,包含一个使用 PHP 类创建的 GUID。如果我能给你适当的数据,你能给我一个这样的FOR UPDATE 的例子吗?我知道该网站并非主要用于案例准备示例,“让社区构建您的软件”。但是,我可以在此基础上建立一个示例,将受到高度评价。再说一次,如果您愿意,测试数据对您来说是否足够? -- 问题也更新了
    • 我还不明白“买彩票”的含义。如果您添加插入语句,我将看看我是否可以演示SELECT ... FOR UPDATE 语句。
    • @MarcusAdams 您的回答完全正确。我可能会添加一个额外的注释,在这种情况下,“间隙”已锁定,因此请确保这是您真正想要的。您将无法在整个范围内插入具有任何其他值的数据,这可能是表的很大一部分。例如,在具有 1、2、3、4、5 的表中,您锁定 10,它实际上会锁定从 5 -> 最高的范围,这意味着您不能锁定任何 >= 5 的键。这种情况很常见带递增键。使用 GUID/UUID,您基本上可以锁定表的任意大部分。
    • 还值得注意的是,如果不使用START TRANSACTIONCOMMIT 或为相关会话设置autocommit=0,这些都不起作用。 (因为我注意到提问者没有提到这一点。)
    • @MarcusAdams 我稍后会将查询添加到起始主题。
    猜你喜欢
    • 2012-06-01
    • 2019-01-10
    • 2013-03-09
    • 2010-11-17
    • 1970-01-01
    • 2010-10-19
    • 1970-01-01
    • 2017-02-15
    • 1970-01-01
    相关资源
    最近更新 更多