← назад к разделу

Параллельное изменение одной строки опасно не само по себе: опасно потерять чужой результат. Версия и блокировка дают два разных способа это заметить.

Optimistic lock через версию

@Entity('products')
export class Product {
  @PrimaryGeneratedColumn()
  id: number;

  @VersionColumn()
  version: number;
}

@VersionColumn увеличивает версию при обновлении и проверяет старое значение в условии. Если другая транзакция обновила строку раньше, текущее обновление не проходит как ни в чем не бывало: TypeORM выбрасывает ошибку optimistic lock.

Приложение должно повторить сценарий: прочитать свежую строку, заново проверить инвариант и только потом записать. Повторять без перечитывания бессмысленно.

Pessimistic lock для критического участка

pessimistic_write использует SELECT ... FOR UPDATE: другие транзакции ждут снятия блокировки строки.

await dataSource.transaction(async manager => {
  const product = await manager.getRepository(Product)
    .createQueryBuilder('product')
    .setLock('pessimistic_write')
    .where('product.id = :id', { id: 42 })
    .getOne();
  product.stock -= 1;
  await manager.save(product);
});

Блокировка действует внутри транзакции. setLock вне нее не заменяет атомарную операцию и создает ложную уверенность. Для простого уменьшения остатка часто лучше один SQL stock = stock - 1 WHERE stock > 0 без чтения в память.

Коротко

  • Версия обнаруживает конкурирующее обновление, не скрывает его.
  • После optimistic lock ошибки сценарий читают и выполняют заново.
  • pessimistic_write применяют внутри транзакции.
  • Для атомарного изменения предпочтителен один SQL с условием.
  • Любую гонку проверяют двумя параллельными сценариями.

Что почитать дальше