2012-05-09 3 views
0

У меня есть база данных, которая поддерживает работу, обрабатываемую различными обрабатывающими машинами. Его основная схема такова:Почему эта процедура синхронизации базы данных терпит неудачу?

+-------------+--------------+------+-----+---------+----------------+ 
| 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 |    | 
+-------------+--------------+------+-----+---------+----------------+ 

идентификатора автоинкрементный, HostAddress представляет собой машину для обработки, который утверждал, что эту работу, время_запуск представляет собой начало последней попытки при обработке его, EndTime это время, в котором он успешно завершила обработку, а GroupID - произвольная строка для ссылки на другие таблицы.

Все обрабатывающие машины синхронизируются вокруг этой таблицы для захвата работы. Новые записи вставляются вручную, хотя все обрабатывающие машины могут обновлять существующие записи. Идея заключалась в том, чтобы обрабатывающая машина выполняла следующие операции, когда это не работает:

  • Посмотрите, принадлежат ли какие-либо задания (HostAddress = его IP) и еще не запущены.
  • Если их нет, посмотрите, не требуются ли какие-либо задания (HostAddress IS NULL).
  • Если есть невостребованные задания, попробуйте некоторые (обновите HostAddress до своего IP-адреса).
  • Обработать все задания, принадлежащие ему (такая же проверка, как и # 1, за исключением того, что мы добавили некоторые из них через # 3).

Я думал, что эта последовательность операций заставит базу данных синхронизировать попытки разных машин на одной и той же работе для меня; даже если обе машины попытались одновременно заявить одну и ту же работу, только один из их IP-адресов попадет в столбец HostAddress, поэтому, когда они снова запросят все задания в своем HostAddress, только один из них вернет эту работу.

Но это не так. При запуске 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(). Зафиксируйте результаты кэшируются каким-то образом, и что, когда я делаю selectReady NamedQuery только после этого, что возвращается кэшированный результат без необходимости обращаться к базе данных. Но это может быть даже не так, и я не уверен, что смогу это доказать. Возможно, даже что-то принципиально испорчено моей схемой, которую я пропускаю.

Итак, чтобы действительно задать свой вопрос, почему эта процедура синхронизации базы данных терпит неудачу и что я могу ее исправить?

ответ

2

Несколько машин могут вызывать selectAvailable(), прежде чем кто-либо из них выполнит транзакцию UPDATE. Следовательно, каждый может подумать, что доступны одни и те же рабочие места.

Вам необходимо начать транзакцию перед вызовом selectAvailable(), который должен использовать SELECT ... FOR UPDATE, чтобы заблокировать доступные записи задания, чтобы никакое другое соединение с базой данных не могло читать их до тех пор, пока транзакция не будет выполнена.

+0

Ах! Я думаю, что у меня есть бит, который мне не хватало.Я ожидал, что несколько машин смогут выбрать одну и ту же работу, и несколько компьютеров будут пытаться ОБНОВИТЬ его, но я предполагал, что из-за того, что только один HostAddress попадает в БД, только один HostAddress будет считаться окончательным SELECT. Но я забыл, что он может временно на самом деле иметь одно значение в БД, перечитать это значение и начать обработку, а затем другой может выполнить свое ожидающее обновление и впоследствии перечитать новое значение. Я посмотрю на ОБНОВЛЕНИЕ немного больше, но это должно сделать трюк. Благодарю. –

+0

Я думаю, что еще одна важная вещь - заметить, что, если я правильно ее понимаю, SELECT ... FOR UPDATE и сам UPDATE должны быть частью одной и той же транзакции. В противном случае он блокировал бы строки, ничего не делал с ними и не разблокировал бы их перед обновлением, после чего еще одна машина могла бы напасть. –

+0

@HammerBro: Разве мой ответ не говорит об этом? «* начинайте транзакцию перед вызовом' selectAvailable() ', чтобы заблокировать ... до момента совершения транзакции *"; P – eggyal

Смежные вопросы