Php 使用表锁(MySQL)防止并行执行

Php 使用表锁(MySQL)防止并行执行,php,mysql,cron,table-locking,table-lock,Php,Mysql,Cron,Table Locking,Table Lock,我有一个名为cronjobs的MySQL表,它保存了每个cronjob所需的entires(例如,删除旧电子邮件、更新配置文件年龄等)。对于每个cronjob,都有一个定义的代码块,如果cronjob到期,就会执行该代码块(对于不同的cronjob,我得到了不同的间隔) 对于due-cronjobs的执行,我得到了一个PHP脚本,它每分钟由UNIX crontab执行一次(调用execute\u cronjobs\u due.sh,它调用“PHP-f/path/to/file/execute\u

我有一个名为cronjobs的MySQL表,它保存了每个cronjob所需的entires(例如,删除旧电子邮件、更新配置文件年龄等)。对于每个cronjob,都有一个定义的代码块,如果cronjob到期,就会执行该代码块(对于不同的cronjob,我得到了不同的间隔)

对于due-cronjobs的执行,我得到了一个PHP脚本,它每分钟由UNIX crontab执行一次(调用execute\u cronjobs\u due.sh,它调用“PHP-f/path/to/file/execute\u cronjobs\u due.PHP”)

在执行execute_cronjobs_due.php时,所有cronjobs都会被标记为将要执行,这样,对execute_cronjobs_due.php的另一个调用不会导致已经执行的同一个cronjob的并行执行

现在的问题是:有时执行时间超过60秒,但crontab程序在这60秒后不会调用execute_cronjobs_due.sh。实际发生的情况是,在执行前一个crontab之后立即调用execute_cronjobs_due.sh。如果一次执行需要120秒以上,那么接下来的两次执行将同时初始化

时间线:

2015-06-15 10:00:00:执行执行cronjobs.sh(需要140秒)

2015-06-15 10:02:20:execute_cronjobs_due.sh的两次同时执行

因为它是完全同时执行的,所以没有必要将cronjob标记为它们正在执行,因为选择(实际上应该排除标记的一次)是在完全相同的时间执行的。因此,更新发生在两个作业都已选择到期的作业之后

如何解决这个问题,使cronjobs不会同时执行?我可以使用MySQL表锁吗

非常感谢您事先的帮助


Frederic

是的,您可以使用mysql表锁,但对于您的情况来说,这可能有些过头了。不管怎么说,我想用最普通的方式来做

  • 确保您已关闭自动提交
  • 锁定工作表
  • 做你的事
  • 解锁表格
  • 对于确切的语法和细节,请仔细阅读文档,我个人从未使用过表级锁定,因此可能涉及到一些我不知道的问题

    如果使用InnoDB表引擎,我会做的是使用乐观锁定:

  • 在脚本中首先启动事务
  • 获取脚本的一些id或其他,可能是进程pid(
    getmypid()
    )或主机+pid的组合。或者,如果您不知道哪一个是完美的,只需生成guid即可
  • 执行类似以下操作:
    UPDATE cronjobs SET executed\u by=my\u id,其中executed\u by为null,并且/*无论什么条件都可以让作业运行*/
  • 然后
    从cronjobs中选择*,其中执行者=my\u pid
  • 在上面选择返回的任何内容上执行您的操作
  • UPDATE cronjobs set executed\u by=null,其中executed\u by=my\u pid
  • 这应该很容易做到,更容易跟踪发生的情况,并在将来进行扩展(即,只要实例执行不同的脚本,就可以有很少的实例并行运行)

    使用此解决方案,第二个脚本不会失败(技术上),它只运行0个作业

    负的是,您必须清理已声明但脚本未能将其标记为已完成的作业,但您可能必须使用当前的解决方案来执行此操作。最简单的方法是添加一个时间戳列,该列将跟踪上次申请作业的时间,并根据业务需求在15分钟或一小时后过期(短伪代码:第一次更新将执行
    SET executed_by=my_id,start_at=NOW(),其中executed_by为null或(executed_by不为null,start_at

    如何解决这个问题,使cronjobs不会同时执行

    有多种方法可以解决此问题。它们也可能有帮助:

    我的建议是保持简单,使用文件锁定或文件存在检查方法

    • 文件_exist()+基于PID的CronHelper类
    • flock()基于:
    • 如果要避免IO,请将锁定状态存储到memcache中
    • 数据库事务:见下文和@sakfa的答案
    • 使用Redis作为中心锁定分布式系统中的cronjobs:&
    我可以使用MySQL表锁吗

    是的,但是有点过分了

    您可以使用带有cronjob状态列(“ToDo,Started,Complete”或“ToDo,Running,Done”)和PID列的“cronjob处理表”。 然后选择作业并使用事务标记其状态。
    这确保了“从Todo中选择作业”和“将其标记为正在运行/已启动”只需一步即可完成。最后,您可能仍然拥有“central cronjob processing script”的多个exec,但不会多次选择要处理的作业。

    您使用的cron不是按照请求的计划执行作业,而是等待其他作业完成?