如何使我的文件上传后端脚本(PHP,MySQL)线程安全?

时间:2014-01-14 21:00:41

标签: php mysql multithreading

我开发了一个基于CodeIgniter框架和MySQL / InnoDB的LAMP应用程序。它基本上是一个照片社区。该应用程序的前端有一个文件上传器,允许一次选择和上传多张照片。接下来,这些照片由后端处理脚本处理,CI脚本中的模型。因此,如果正在上载6张照片,则后端脚本将被并行调用6次。我需要保持文件上传(和处理)多线程,因为它带来了极大的速度优势。

因此,并行多次调用后端脚本。该脚本负责许多任务,除了脚本的一部分外,其中大多数任务在多线程场景中都能正常工作。

上传者每上传10张照片都会获得“奖牌”。此检查在后端脚本的末尾完成。这是一个出错的情况:说上传者之前上传了9张照片,现在又上传了4张照片。后端脚本的第一个实例应该得出结论,新上传的照片将计数推到10,因此应该颁发奖章。接下来,期望的结果应该是其他3个后端任务不能奖励奖牌,因为它已经超过了10个计数。然而,实际结果是后端任务的所有4个实例,因为它们并行运行,得出相同的结论,因此分发了4枚奖牌。

显然,我的后端脚本的这一部分不是线程安全的,并行运行时行为不正确。我的想法是,如果我只是简单地将后端脚本的这一部分包装在一个事务中,那么它将成为线程安全的。在伪代码中:

1. bulk of back-end processing task. Thread-safe by design, so not in a transaction
2. $this->db->trans_begin();
3. medal handling code here
4. $this->db->trans_commit();

我的理论是,通过将这个非线程安全的部分包装在一个事务中,它将成为线程安全的。它会锁定触及的表并保证新的读写。锁定将非常简短。

看来我的理论错了。此更改后问题仍然存在。重现起来相当困难,但我已经看过两次再次失败的情况。

我想知道我的想法中是否存在概念上的缺陷?如何让我的后端任务的这一小部分安全地并行运行?

讨论Zak答案的额外信息:

我的后端脚本的详细步骤:

  1. 基本验证(检查文件是否已收到等) - 无数据库活动
  2. 将图像记录保存在数据库中(这会增加用户的图像数量,这对奖牌部分很重要
  3. 将上传的文件处理成拇指(没有数据库活动,但需要很长时间,比如20秒)
  4. 确定当前用户上传的当前图像数量
  5. 如果超过10,或者确切数量为10,则奖励奖励
  6. 为了尝试Zak的答案,我已将图像计数查询重写为:

    SELECT COUNT(id) as count FROM image WHERE user_id = ? AND status='active' FOR UPDATE
    

    这是后端脚本的第4步。为了使调试更容易,我在此步骤中写入了一个日志文件中的图像数量。接下来,我重复上传图像集并检查计数器的日志。不幸的是,我仍然遇到多个并行进程报告相同图像数量的情况,这会导致多个奖牌。

    我玩过各种各样的变化,我已经将步骤4移到了第2步。我已经尝试在一个事务中包装步骤2和4(在将它们移到第3步之后)。一切都无济于事,在所有情况下,我仍无法可靠地获得正确的图像数。

2 个答案:

答案 0 :(得分:1)

我不知道我是在给你一个想法,或者我说的是胡言乱语,在概念方面,而不是代码,也许你可以在几秒钟之后,在处理完所有交易之后给予奖牌。我见过很多使用这个概念的脚本和游戏。奖励不是实时给出的。在用户完成上传照片后 - 上传的处理方式无关紧要 - 然后他会收到一条消息,说明他有新的奖牌。当他浏览另一页左右时,可以通知他。

答案 1 :(得分:1)

交易不一定能为您提供所需的锁定功能。查看:

http://dev.mysql.com/doc/refman/5.0/en/set-transaction.html#isolevel_read-committed

你想要一个读锁定。基本上,在你的选择中,你说FOR UPDATE,这将阻止并行读取读取该数据,直到事务被提交。

注意:FOR UPDATE只会粘贴到查询的末尾,这只适用于您的表是InnoDb

的情况