Symfony Form 与悲观锁的实现方案
悲观锁的基本概念
悲观锁假设并发冲突经常发生,在操作数据时直接锁定记录,防止其他事务修改。

Symfony Doctrine 悲观锁实现
use Doctrine\DBAL\LockMode;
use Doctrine\ORM\EntityManagerInterface;
class ProductController extends AbstractController
{
#[Route('/product/{id}/edit', name: 'product_edit')]
public function edit(int $id, EntityManagerInterface $em, Request $request): Response
{
// 锁定模式
$modes = [
'PESSIMISTIC_READ' => LockMode::PESSIMISTIC_READ,
'PESSIMISTIC_WRITE' => LockMode::PESSIMISTIC_WRITE,
'OPTIMISTIC' => LockMode::OPTIMISTIC,
];
$dbalConnection = $em->getConnection();
$dbalConnection->beginTransaction();
try {
// 获取实体并加悲观写锁
$product = $em->find(Product::class, $id, LockMode::PESSIMISTIC_WRITE);
if (!$product) {
throw $this->createNotFoundException('Product not found');
}
$form = $this->createForm(ProductType::class, $product);
$form->handleRequest($request);
if ($form->isSubmitted() && $form->isValid()) {
$em->flush();
$dbalConnection->commit();
return $this->redirectToRoute('product_list');
}
return $this->render('product/edit.html.twig', [
'form' => $form->createView(),
'product' => $product,
]);
} catch (\Exception $e) {
$dbalConnection->rollBack();
throw $e;
}
}
}
使用 Repository 封装悲观锁逻辑
namespace App\Repository;
use Doctrine\DBAL\LockMode;
use Doctrine\ORM\EntityRepository;
class ProductRepository extends EntityRepository
{
/**
* 获取记录并加悲观写锁
*/
public function findWithPessimisticWriteLock(int $id): ?Product
{
$entityManager = $this->getEntityManager();
$dql = "SELECT p FROM App\Entity\Product p WHERE p.id = :id";
$query = $entityManager->createQuery($dql)
->setParameter('id', $id);
// 原生SQL方式获取悲观锁
$query->setLockMode(LockMode::PESSIMISTIC_WRITE);
return $query->getOneOrNullResult();
}
/**
* 使用QueryBuilder获取悲观锁
*/
public function findWithLockQueryBuilder(int $id): ?Product
{
$entityManager = $this->getEntityManager();
$qb = $this->createQueryBuilder('p')
->where('p.id = :id')
->setParameter('id', $id);
$query = $qb->getQuery();
$query->setLockMode(LockMode::PESSIMISTIC_WRITE);
return $query->getOneOrNullResult();
}
/**
* 原生SQL实现悲观锁(MySQL示例)
*/
public function findWithNativeLock(int $id): ?array
{
$conn = $this->getEntityManager()->getConnection();
$sql = "SELECT * FROM product WHERE id = :id FOR UPDATE";
$stmt = $conn->prepare($sql);
$stmt->execute(['id' => $id]);
return $stmt->fetch();
}
}
完整的控制器示例(含超时处理)
namespace App\Controller;
use App\Entity\Product;
use App\Form\ProductType;
use Doctrine\DBAL\LockMode;
use Doctrine\ORM\EntityManagerInterface;
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\Routing\Annotation\Route;
use Symfony\Component\Lock\LockFactory;
class ProductController extends AbstractController
{
private EntityManagerInterface $entityManager;
private LockFactory $lockFactory;
public function __construct(
EntityManagerInterface $entityManager,
LockFactory $lockFactory
) {
$this->entityManager = $entityManager;
$this->lockFactory = $lockFactory;
}
#[Route('/product/{id}/edit', name: 'product_edit')]
public function edit(int $id, Request $request): Response
{
// 使用 Symfony Lock 组件实现应用层锁
$lock = $this->lockFactory->createLock('product_edit_' . $id, 30);
if (!$lock->acquire()) {
throw $this->createAccessDeniedException('该产品正在被其他人编辑');
}
try {
$this->entityManager->beginTransaction();
// 数据库悲观锁
$product = $this->entityManager->find(
Product::class,
$id,
LockMode::PESSIMISTIC_WRITE
);
if (!$product) {
throw $this->createNotFoundException('Product not found');
}
$form = $this->createForm(ProductType::class, $product);
$form->handleRequest($request);
if ($form->isSubmitted()) {
// 检查表单之前的版本号(用于乐观锁检测)
$originalStock = $product->getStock();
if ($form->isValid()) {
// 业务逻辑检查
if ($product->getStock() !== $originalStock) {
// 库存变化时的特殊处理
$this->addFlash('warning', '库存已被其他用户修改');
}
$this->entityManager->flush();
$this->entityManager->commit();
return $this->redirectToRoute('product_list');
}
}
return $this->render('product/edit.html.twig', [
'form' => $form->createView(),
'product' => $product,
]);
} catch (\Exception $e) {
$this->entityManager->rollback();
$this->addFlash('error', '编辑失败:' . $e->getMessage());
return $this->redirectToRoute('product_edit', ['id' => $id]);
} finally {
$lock->release();
}
}
#[Route('/product/{id}/purchase', name: 'product_purchase', methods: ['POST'])]
public function purchase(int $id, Request $request): Response
{
// 高并发场景下的锁定处理
$lockKey = 'product_purchase_' . $id;
$lock = $this->lockFactory->createLock($lockKey, 10);
if (!$lock->acquire()) {
throw $this->createNotFoundException('系统繁忙,请稍后重试');
}
try {
$this->entityManager->beginTransaction();
// 使用 DQL 进行锁定
$dql = "SELECT p FROM App\Entity\Product p
WHERE p.id = :id
AND p.stock > 0";
$query = $this->entityManager->createQuery($dql)
->setParameter('id', $id)
->setLockMode(LockMode::PESSIMISTIC_WRITE);
$product = $query->getOneOrNullResult();
if (!$product) {
throw new \RuntimeException('商品已售罄');
}
// 更新库存
$product->setStock($product->getStock() - 1);
$this->entityManager->flush();
$this->entityManager->commit();
return $this->json(['success' => true, 'stock' => $product->getStock()]);
} catch (\Exception $e) {
$this->entityManager->rollback();
return $this->json(['success' => false, 'message' => $e->getMessage()], 500);
} finally {
$lock->release();
}
}
}
悲观锁的超时与重试机制
namespace App\Service;
use Doctrine\DBAL\Connection;
use Doctrine\ORM\EntityManagerInterface;
use Symfony\Component\Lock\LockFactory;
class PessimisticLockService
{
private EntityManagerInterface $entityManager;
private LockFactory $lockFactory;
private int $maxRetries = 3;
private int $retryDelay = 100; // 毫秒
public function __construct(
EntityManagerInterface $entityManager,
LockFactory $lockFactory
) {
$this->entityManager = $entityManager;
$this->lockFactory = $lockFactory;
}
/**
* 带重试机制的悲观锁操作
*/
public function executeWithLock(int $entityId, callable $callback, int $timeout = 5): mixed
{
$attempts = 0;
$lastException = null;
while ($attempts < $this->maxRetries) {
try {
$this->entityManager->beginTransaction();
// 设置锁超时
$connection = $this->entityManager->getConnection();
$connection->executeStatement('SET innodb_lock_wait_timeout = ?', [$timeout]);
// 获取实体并锁定
$entity = $this->entityManager->find(
'App\Entity\Product',
$entityId,
\Doctrine\DBAL\LockMode::PESSIMISTIC_WRITE
);
if (!$entity) {
throw new \RuntimeException('Entity not found');
}
// 执行回调
$result = $callback($entity);
$this->entityManager->flush();
$this->entityManager->commit();
return $result;
} catch (\Doctrine\DBAL\Exception\LockWaitTimeoutException $e) {
$this->entityManager->rollback();
$lastException = $e;
$attempts++;
if ($attempts < $this->maxRetries) {
usleep($this->retryDelay * 1000);
}
} catch (\Exception $e) {
$this->entityManager->rollback();
throw $e;
}
}
throw new \RuntimeException(
'Failed to acquire lock after ' . $this->maxRetries . ' attempts',
0,
$lastException
);
}
}
Form 中的版本号处理(辅助乐观锁)
namespace App\Form;
use App\Entity\Product;
use Symfony\Component\Form\AbstractType;
use Symfony\Component\Form\Extension\Core\Type\HiddenType;
use Symfony\Component\Form\FormBuilderInterface;
use Symfony\Component\OptionsResolver\OptionsResolver;
class ProductType extends AbstractType
{
public function buildForm(FormBuilderInterface $builder, array $options): void
{
$builder
->add('name')
->add('price')
->add('stock')
->add('_version', HiddenType::class, [
'mapped' => false,
'data' => $options['current_version'] ?? 0,
'attr' => ['class' => 'version-field']
])
;
}
public function configureOptions(OptionsResolver $resolver): void
{
$resolver->setDefaults([
'data_class' => Product::class,
'current_version' => 0,
'csrf_protection' => true,
]);
}
}
使用建议
-
选择合适的锁模式:
PESSIMISTIC_READ: 防止写入,允许读取PESSIMISTIC_WRITE: 完全锁定,防止读写
-
事务管理:确保在事务内使用悲观锁
-
超时设置:设置合理的锁等待超时时间
-
监控与日志:记录锁等待和死锁信息
-
应用层锁:配合 Symfony Lock 组件使用双重保障
# config/packages/lock.yaml
framework:
lock:
product_edit: 'semaphore'
product_purchase: 'redis://localhost:6379'
这种实现确保了在高并发场景下数据的一致性,同时提供了足够的灵活性来处理不同的业务需求。
版权声明:NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
CBA直播
CBA直播
CBA直播
CBA直播
CBA直播
CBA直播
CBA直播
CBA直播
CBA直播
CBA直播
CBA直播
CBA直播
CBA直播
CBA直播
CBA直播
CBA直播
CBA直播
CBA直播
足球直播
足球直播
足球直播
足球直播
足球直播
足球直播
足球直播
足球直播
足球直播
足球直播
足球直播
足球直播
足球直播
足球直播
足球直播
足球直播
足球直播
足球直播
英超直播
英超直播
英超直播
英超直播
英超直播
英超直播
英超直播
英超直播
英超直播
英超直播
英超直播
英超直播
英超直播
英超直播
英超直播
英超直播
英超直播
英超直播