我应该如何对该评级功能进行编程,以最大限度地减少并发问题


How should I program this ratings feature to minimize concurrency issues?

我正在为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小时或更长时间过期一次。然而,我遇到了同样的问题:如果两个访问者同时触发缓存刷新,就会出现同样的并发问题。

我应该如何设计此代码以最大限度地减少并发问题?

并发的解决方案通常是锁定机制。有时只需要一行锁定,而其他时候则锁定整个表。第一个事务应用表锁定,此后用于搜索或修改数据的其余事务保持等待,直到第一个事务明确地执行解锁。我建议你阅读以下链接http://doctrine-orm.readthedocs.org/projects/doctrine-orm/en/latest/reference/transactions-and-concurrency.html#locking-支持。