2015-12-11 2 views
0

Я пишу функцию для приложения Symfony, которое позволяет пользователям отправлять оценки для продукта. Я вычисляю среднее значение рейтингов для продукта после каждого рейтинга, поэтому мне не нужно запускать потенциально дорогостоящие AVG() запросов каждый раз, когда мне нужен средний рейтинг.Как мне запрограммировать эту функцию оценки, чтобы минимизировать проблемы параллелизма?

Вот простая функция, которая вычисляет среднюю оценку и сохраняет его:

public function calculateAndSaveAverageRating(Product $product) 
{ 
    // Run the SUM() query and return a float containing the average, 
    // or null if there are no ratings for the product. 
    $calculatedAverage = $this 
     ->em 
     ->getRepository('AppBundle:Product') 
     ->findAverageRating($product); 

    // Lookup an existing ProductRatingAverage entity if it exists. This 
    // stores the average value of ratings for each product. Returns null 
    // if there is no existing entity. 
    $existingAverageEntity = $this 
     ->em 
     ->getRepository('AppBundle:ProductRatingAverage') 
     ->findOneBy(array('product' => $product)); 

    // Save the calculated average if we got a non-null value. Otherwise 
    // there are no ratings for this product, so delete the existing 
    // average entity if it exists. 
    if ($calculatedAverage) { 
     // If we have an existing average entity, update it. Otherwise 
     // create a new one and store the average. 
     if ($existingAverageEntity) { 
      $existingAverageEntity->setAverage($calculatedAverage); 
     } else { 
      $existingAverageEntity = new ProductRatingAverage(); 
      $existingAverageEntity->setProduct($product); 
      $existingAverageEntity->setAverage($calculatedAverage); 
      $this->em->persist($existingAverageEntity); 
     } 
    } else { 
     if ($existingAverageEntity) { 
      $this->em->remove($existingAverageEntity); 
     } 
    } 

    $this->em->flush(); 
} 

Но есть некоторые проблемы параллелизма здесь. Вот два из них:

  1. Если два пользователи представляют оценки для продукта без каких-либо предыдущих оценок, в то же время (или очень близко), то этот код будет пытаться создать две средних рейтинговые объекты на тот же продукт, когда может быть только одно (уникальные ограничения базы данных).
  2. Если два пользователя представили оценки для продукта, у которого были рейтинги ранее, (или очень близко), то этот код, вероятно, исключает один из этих оценок из рассчитанного среднего значения.

Я мог бы принять другой подход: поместить истекающий кэш перед запросом, который запускает запрос AVG(), и истекать через каждые 1 час или что-то в этом роде. Однако я сталкиваюсь с теми же проблемами: если два посетителя одновременно запускают обновление кеша, возникают те же проблемы параллелизма.

Как мне создать этот код для минимизации проблем параллелизма?

ответ

0

Решение для параллелизма обычно является механизмом блокировки. Иногда требуется только однострочный замок, а иногда блокирует всю таблицу. Первая транзакция применяется к блокировке таблицы, и после этого остальные транзакции для поиска или изменения данных остаются в ожидании, пока первый явно не выполнит разблокировку. Я рекомендую вам прочитать следующую ссылку http://doctrine-orm.readthedocs.org/projects/doctrine-orm/en/latest/reference/transactions-and-concurrency.html#locking-support.