考虑并发是对的。除非您只有 1 个 PHP 线程响应客户端请求,否则实际上没有什么可以阻止它们从 data 分发同一行以进行处理 - 事实上,由于它们每个都将运行相同的查询,它们几乎每个当然分发同一行。
解决该问题的最简单方法是锁定,正如接受的答案中所建议的那样。如果 PHP 服务器线程运行 SELECT...FOR UPDATE 或 LOCK TABLE ... UNLOCK TABLES (非事务性)所花费的时间很短,那么这可能会起作用,这样其他线程可以在每个线程运行此代码时等待(它仍然是浪费的,因为它们可能是处理一些其他数据行,稍后会详细介绍)。
有一个更好的解决方案,尽管它需要更改架构。想象一下,你有一张这样的表:
CREATE TABLE `data` (
`data_id` int(10) unsigned NOT NULL AUTO_INCREMENT,
`data` blob,
`status` tinyint(1) DEFAULT '0',
PRIMARY KEY (`data_id`)
) ENGINE=InnoDB;
您无法以事务方式更新“下一条处理的记录”,因为您必须更新的唯一字段是 status。但是想象一下你的桌子看起来更像这样:
CREATE TABLE `data` (
`data_id` int(10) unsigned NOT NULL AUTO_INCREMENT,
`data` blob,
`status` tinyint(1) DEFAULT '0',
`processing_id` int(10) unsigned DEFAULT NULL,
PRIMARY KEY (`data_id`)
) ENGINE=InnoDB;
然后您可以编写类似这样的查询来使用您的“处理 id”更新要处理的“下一个”列:
UPDATE data
SET processing_id = @unique_processing_id
WHERE processing_id IS NULL and status = 0 LIMIT 1;
而且任何值得一试的 SQL 引擎都将确保您没有 2 个不同的处理 ID 来说明要同时处理的同一记录。然后在你闲暇时,你可以
SELECT * FROM data WHERE processing_id = @unique_processing_id;
并且知道您每次都会获得独一无二的记录。
这种方法也很适合解决持久性问题;您基本上可以识别每个data 行的批处理运行,这意味着您可以考虑每个批处理作业,而在此之前您可能只考虑数据行。
我可能会通过为此元数据添加第二个表来实现@unique_processing_id(自动增量键是真正的技巧,但可以添加其他数据处理元数据):
CREATE TABLE `data_processing` (
`id` int(10) unsigned NOT NULL AUTO_INCREMENT,
`date` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP,
`data_id` int(10) unsigned DEFAULT NULL,
PRIMARY KEY (`id`)
) ENGINE=InnoDB;
并将其用作您的唯一 ID 的来源,您最终可能会得到如下结果:
INSERT INTO data_processing SET date=NOW();
SET @unique_processing_id = (SELECT LAST_INSERT_ID());
UPDATE data
SET processing_id = @unique_processing_id
WHERE status = 0 LIMIT 1;
UPDATE data
JOIN data_processing ON data_processing.id = data.processing_id
SET data_processing.data_id = data.data_id;
SELECT * from data WHERE processing_id = @unique_processing_id;
-- you are now ready to marshal the data to the client ... and ...
UPDATE data SET status = 1
WHERE status = 0
AND processing_id = @unique_processing_id
LIMIT 1;
从而解决您的并发问题,并让您更好地审核持久性,这取决于您如何设置data_processing 表;您可以跟踪线程 ID、处理状态等,以帮助验证数据是否真正完成处理。
还有其他解决方案 - 消息队列可能是理想的,它允许您将每个未处理的数据对象的 ID 直接(或通过 php 脚本)排队到客户端,然后为该数据提供一个接口,以便单独检索和标记处理从“下一个”数据的队列中。但就“仅限 mysql”的解决方案而言,我在这里向您展示的概念应该可以很好地为您服务。