为什么此数据库同步例程失败?

时间:2012-05-09 17:40:25

标签: java mysql jpa concurrency synchronization

我有一个数据库,可以维护各种处理机器处理的作业。因此,它的基本架构是:

+-------------+--------------+------+-----+---------+----------------+
| Field       | Type         | Null | Key | Default | Extra          |
+-------------+--------------+------+-----+---------+----------------+
| ID          | int(11)      | NO   | PRI | NULL    | auto_increment |
| EndTime     | datetime     | YES  |     | NULL    |                |
| GroupID     | varchar(255) | NO   | MUL | NULL    |                |
| HostAddress | varchar(15)  | YES  |     | NULL    |                |
| StartTime   | datetime     | YES  |     | NULL    |                |
+-------------+--------------+------+-----+---------+----------------+

ID是自动递增,HostAddress表示已声明此作业的处理机器,StartTime表示最近处理它的尝试的开始,EndTime是成功完成处理的时间,GroupID是任意字符串用。引用其他表。

所有处理机器围绕此表同步以获取工作。尽管所有处理机器都可以更新现有记录,但只能手动插入新记录。我们的想法是让加工机器在失控时执行以下操作:

  • 查看是否有任何作业(HostAddress =其IP)并且尚未启动。
  • 如果没有,请查看是否尚未声明任何作业(HostAddress IS NULL)。
  • 如果有无人认领的作业,请声明一些(将HostAddress更新为其IP)。
  • 处理属于它的所有作业(与#1相同,但我们可能已经通过#3添加了一些作业)。

我原以为这一系列操作会让数据库为我同步不同机器在同一工作中的尝试;即使两台机器同时尝试声明相同的作业,其中只有一个IP最终会出现在HostAddress列中,因此当他们再次询问其HostAddress中的所有作业时,其中只有一个会得到该作业。 / p>

但事实并非如此。昨晚几乎同时启动了35台加工机器时,我观察到多台机器处理同一工作的情况,尽管其中只有一台最终在数据库中声明了它。这对我来说意味着最后一次检查工作不正常。这是我正在做的更具体的版本。数据库调用使用em.createNamedQuery,为简洁起见,我将在下面对它们进行总结。 JPA由Hibernate 3.6.8提供,数据库是MySQL 5.1.61。

protected void poll(EntityManager em) {
    List<JobRecord> candidates = null;
    //Synchronized only for this machine. Others are running concurrently.
    synchronized (em) {
        //Check if anything is already claimed by us.
        candidates = JobRecord.selectReady(em);
        //SELECT record FROM JobRecord record WHERE HostAddress=[IP]
        //    AND StartTime IS NULL AND EndTime IS NULL;
            if (candidates.isEmpty()) {
            //None claimed. Check if any jobs aren't claimed by anyone.
            candidates = JobRecord.selectAvailable(em);
            //SELECT record FROM JobRecord record WHERE HostAddress IS NULL
            //    AND StartTime IS NULL AND EndTime IS NULL;
            if (candidates.isEmpty()) {
                //All jobs have been processed.
                return;
            }
            //Claim these jobs we found for ourselves.
            em.getTransaction().begin();
            for (JobRecord job : candidates) {
                job.setStartTime(null);
                job.setEndTime(null);
                job.setHostAddress([IP]);
                em.merge(job);
            }
            em.getTransaction().commit;
            //Only process what is actually claimed by us; could be nothing.
            candidates = JobRecord.selectReady(em);
            //(The first query again.)
        }
    //Do processing with candidates list.
}

我想到的唯一解释是,当我执行em.getTransaction()。commit时,结果会以某种方式缓存,当我在它之后执行selectReady NamedQuery时,它返回缓存的结果打扰咨询数据库。但情况可能并非如此,我不确定我能证明这一点。我的计划甚至可能存在一些根本上存在缺陷的问题。

所以,实际上提出我的问题,为什么这个数据库同步例程失败了,我该怎么做才能纠正它?

1 个答案:

答案 0 :(得分:2)

多台计算机可以在任何执行selectAvailable()事务之前调用UPDATE。因此,他们可能都认为可以获得相同的工作。

您需要在selectAvailable()调用之前开始事务,该调用应该使用SELECT ... FOR UPDATE来锁定可用的作业记录,以便在提交事务之前不能从其中读取任何其他数据库连接。