Mysql:在更新值之前读取锁定表

时间:2014-02-09 20:41:20

标签: php mysql sql concurrency transactions

在我的数据库(MySQL)中,我有一个表(MyISAM),其中包含一个名为number的字段。该字段的每个值都是0或正数。非零值必须是唯一的。最后一点是该字段的值是根据此表中另一个字段(称为isNew)的值在我的php代码中生成的。代码如下。

$maxNumber = $db->selectField('select max(number)+1 m from confirmed where isNew = ?', array($isNew), 'm');
$db->query('update confirmed set number = ? where dataid = ?', array($maxNumber, $id));

第一行代码选择数字字段的最大值并递增它。第二行通过设置新生成的数字来更新记录。

此代码由数百个客户端同时使用,因此我注意到有时会出现数字字段的重复。据我所知,当两个客户几乎同时读取数字字段的值时会发生这种情况,这一事实导致重复。

我已经读过SELECT ... FOR UPDATE语句,但我不太确定它适用于我的情况。

所以问题是我应该将FOR UPDATE附加到我的SELECT语句中吗?或者创建一个存储过程来完成这项工作?或者可能完全改变数字的生成方式?

1 个答案:

答案 0 :(得分:1)

这绝对是可以做到的。 MyISAM不提供事务锁定,所以忘记FOR UPDATE之类的东西。在示例代码中,两个语句之间肯定存在争用条件的空间。你实现它的方式,这个就像说话的驴子。令人惊讶的是,它的工作原理并不是很有效! : - )

我不明白你在用这个SQL做什么:

 select max(number)+1 m from confirmed where isNew = ?

number的值在整个表格中是唯一的,还是仅在isNew具有特定值的集合中?如果number的值在整个表格中是唯一的,它会起作用吗?这将更容易创建,调试和维护。

您需要一种多连接安全的方式来获取号码。

你可以尝试这个SQL。它将在一个语句中设置最大数量。

 UPDATE confirmed 
    SET number = (SELECT 1+ MAX(number) FROM confirmed WHERE isNew = ?)
  WHERE dataid = ?

这将表现不佳。如果(isNew, number)没有复合索引,并且没有声明NOT NULL的列,那么它的执行情况会非常糟糕。

如果您可以使用整个表格中唯一的数字,我建议您自己创建sequence设置,每次使用时都会返回一个唯一的编号。您需要使用一系列连续的SQL语句来执行此操作。这是怎么回事。

首先,当您创建表时,创建一个名为sequence的表(或您喜欢的任何名称)。这是一个单列表。

CREATE TABLE sequence (
     sequence_id INT NOT NULL AUTO_INCREMENT,
     PRIMARY KEY (`sequence_id`)
) AUTO_INCREMENT = 990000

这将使序列表开始发出990,000的数字。

其次,当您在应用程序中需要唯一编号时,请执行以下操作。

INSERT INTO sequence () VALUES ();
DELETE FROM sequence WHERE sequence_id < LAST_INSERT_ID();
UPDATE confirmed 
    SET number = LAST_INSERT_ID()
  WHERE dataid = ?

这里发生了什么? MySQL函数LAST_INSERT_ID()返回最近自动增量生成的ID号的值。因为您在sequence表中插入了一行,所以它会返回生成的ID号。 DELETE FROM命令使该表不会占用磁盘空间;我们不关心旧的身份证号码。

LAST_INSERT_ID()是连接安全的。如果与数据库的不同连接上的软件使用它,它们都会获得自己的值。

如果您需要知道最后插入的ID号,可以发出以下SQL:

SELECT LAST_INSERT_ID() AS sequence_id

你会得到它。

如果您使用的是Oracle或PostgreSQL,而不是MySQL,您会发现它们提供的SEQUENCE对象基本上都是这样做的。

这是另一个类似问题的答案。

Fastest way to generate 11,000,000 unique ids