【问题标题】:How to perform a MySQL row lock and delete rows using PHP?如何使用 PHP 执行 MySQL 行锁定和删除行?
【发布时间】:2015-07-07 20:44:40
【问题描述】:

LAMP 堆栈 - 所以代码是 PHP

我有一个 innodb 表 - 称为 queueTable

有一个每 15 分钟运行一次的 cron 作业,它查询数据库以确定是否需要将任何记录放入 queueTable。有时没有添加到 queueTable 的记录,而其他时候可能有超过 1,000 条记录添加到 queueTable。

需要发生的是 queueTable 中的每一行都需要被读取,需要执行一个过程,然后需要删除该行。

目前,我有两个守护程序作业,除了每 5 秒查询一次 queueTable 之外什么都不做。

一个作业读取所有奇数行记录,另一个读取所有偶数行记录。有两个工作可以快速完成所有处理(两个工作比一个好)。

我想创建大约 20 个守护进程来全部访问 queueTable - 从而将行在 queueTable 中的时间缩短到最低限度。

把它想象成一个邮件队列(它不是一个很好的例子)。

我需要尽快清空队列。

我可以进行事务并锁定该行,但这并不能阻止另一个守护进程尝试锁定同一行。如果一个守护进程试图读取一个锁定的行,将会发生什么——它会等待它。但是锁定该行的守护程序将删除它。不确定此时第二个守护进程会做什么?

完成我想做的事情的最佳方法是什么?

我希望这是有道理的。

谢谢!

顺便说一句 - 我已经尝试过 pthreads - 并发现它在吞吐量方面确实没有多大帮助。 IE。读取记录并在获取下一条记录之前对其进行处理的进程 - 如果我在多线程模式下执行此操作(即每个进程都是一个新线程),则清空队列的时间与在每一行中执行的时间相同一次。至少在我的测试中是这样的。

【问题讨论】:

  • 锁是原子的。要么成功,要么失败。您不能让两个不同的客户端锁定同一个记录,因为只有一个可以成功。
  • 数据库表是事务性的,如果您的键定义正确,只需运行查询并执行一个或多个步骤应该更快,然后尝试创建一个 php 线程解决方案,因为 php 是无状态的。你为什么做这个?或者您为什么认为这是解决您的问题的好方法?您甚至可以在这里使用存储过程。
  • 为了解决我这样做的原因......这是一个时间问题。我们正在处理的信息需要在一分钟内处理完——不管有多少记录。现在有两个守护程序作业,当 queueTable 中有几千条记录时 - 守护程序在大约 4 分钟内处理这些记录。这些信息对时间非常关键 - 因此在一分钟内处理这些数据是一项重要要求。
  • @LenS 真正的解决方案是使用 RabbitMQ 或其他队列技术。它有 php 版本,但这里有一个示例:rabbitmq.com/tutorials/tutorial-two-python.html。这应该对你好一点。正如我之前的评论中所述,唯一更好的解决方案是存储过程,它在将行添加到表中时激活。理想情况下,您可以使用队列创建单独的工作人员,因为数据可用,而不是作为一个巨大的“批次”。
  • 在一个可能可行的理想世界中,但创建这些记录的作业是分批进行的……创建大约 4,000 条记录大约需要 15 秒。我会检查你的 rabitMq

标签: mysql locking innodb


【解决方案1】:

我在 MySQL 中实现队列的口头禅:“不要排队,就去做!”

使用 PHP 进行多处理并不是那么容易实现的。 Perl(也是 Lamp 的一部分)要好得多。此外,由于这是一项后台任务,请不要将 Web 服务器和浏览器(Apache + IE)放在图片之外;它们的开销很大。

处理一个“项目”需要多长时间?如果只需要几秒钟,那么BEGIN; SELECT one item FOR UPDATE; process it; COMMIT;

如果需要更长的时间,请不要将BEGIN...COMMIT 放在所有内容周围;而是做类似的事情:

SET autocommit=1

  1. $id = SELECT id ... WHERE process_id IS NULL ORDER BY ... LIMIT 1; (或者你可以抓住 10 个步骤,然后有一个步骤 2..5 的内部循环。)
  2. UPDATE ... SET process_id = $pid WHERE id = $id AND process_id IS NULL; 并检查它是否有效。注意SELECTUPDATE 故意不在事务中;这是为了尽量减少干扰。但是,如果没有事务,另一个线程将抓住它的可能性不大。如果是,则 rows_affected 将为 0;返回第 1 步。
  3. 处理项目
  4. UPDATE (or DELETE) ... SET process_id = NULL WHERE id = $item; -- 释放锁
  5. sleep(1) -- 延迟一点 -- 以避免人为地淹没系统。这可能需要调整。
  6. 返回步骤 1。

您可以有任意数量的线程来执行此操作。

【讨论】:

  • 那么事务锁定(来自多个线程)会起作用吗?这样所有记录都将被处理(并在提交流程/事务时删除)并且任何行都不会被处理多次?
  • 第 1 步得到一个暂定项。第 2 步是棘手的​​一步:它原子地 抓取它或影响零行。第 4 步从考虑中删除到行。
  • 我更新了第 2 步和第 4 步。操作方法有很多变体,但这是大体思路。
  • 我将它设计为很少有锁定。第 1 步可能需要一个更好的索引来最小化它的时间。第 2 步和第 4 步(假设 PRIMARY KEY(id))的锁定时间非常短。通过不使用事务,而是自动提交,每个语句都是独立的。 (第 3 步可能有事务;但那是另一回事。)第 1 步是最大的开销;专注于它。
  • 实际上 BEGIN, SELECT for UPDATE, process, DELETE, COMMIT 似乎有效。我有两个工作针对一个有 8500 行的测试表做同样的事情。第一次迭代,而不是 DELETE,我进行了 UPDATE 并设置了一个计数器来跟踪该行的更新频率,并将另一个字段设置为已处理 - 因此 SELECT 包含 where processes = '0' 多次运行模拟 - 计数器总是达到 1 并且所有行都得到更新。完成后将其更改为删除记录-似乎也可以按我的预期工作。我不确定这样做有多少开销,但它确实有效。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 2014-01-03
  • 1970-01-01
  • 2012-04-06
  • 1970-01-01
  • 2012-04-26
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多