【问题标题】:Mysql: locking table for read before the value is updatedMysql:在值更新之前锁定表以进行读取
【发布时间】:2025-12-06 07:35:01
【问题描述】:

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

$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 语句吗?或者创建一个存储过程来完成这项工作?或者可能完全改变生成数字的方式?

【问题讨论】:

  • 可怕......只需使用 auto_increment int 并完成它。您的代码会受到竞争条件的影响,并且永远如此。
  • 您是否有理由需要使用 MyISAM 作为您的表引擎?这对汽车公司来说很时髦,只会增加你的麻烦。首先重新考虑您的架构,然后制定您的计划。
  • auto_increment 不能使用,因为值是根据提到的其他字段生成的 (isNew)

标签: php mysql sql concurrency transactions


【解决方案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

【讨论】:

  • 表中有两组number 值,具体取决于isNew 字段。如果 isNew = 0 则 number 从 1000 开始,否则数字从 900000 开始。正如您所见,number 的值在表中是唯一的,除了那些为零的值。我喜欢你的序列方法,所以在我的情况下,我只需要创建并支持两个预定义的序列而不是一个。谢谢!
  • 对!这样可行。请参阅我关于在创建序列表时初始化 auto_increment 值的编辑。