MikroORM 事务与并发控制实战:边界划分、传播模式与乐观/悲观锁全解析
发布时间:2026/9/25 3:37:09来源:尧图网络
后端【免费下载链接】mikro-ormTypeScript ORM for Node.js based on Data Mapper, Unit of Work and Identity Map patterns. Supports MongoDB, MySQL, MariaDB, MS SQL Server, PostgreSQL and SQLite/libSQL databases.项目地址https://gitcode.com/gh_mirrors/mi/mikro-orm点击查看免费下载导读本文聚焦 MikroORM 中事务与并发控制的完整技术体系涵盖事务边界划分隐式 flush 事务与显式em.transactional()/Transactional()、七种事务传播模式NESTED、REQUIRED、REQUIRES_NEW等、乐观锁版本字段与并发检查字段、悲观锁六种数据库级锁模式以及隔离级别与禁用事务等高级配置。通过本文你将掌握在 TypeScript ORM 场景下如何精确控制事务生命周期、在多服务层构建安全的事务嵌套逻辑以及如何应对丢失更新脏读等经典并发问题。MongoDB 驱动同样支持事务详见 MongoDB 使用指南。事务边界划分Transaction Demarcation事务边界划分Transaction Demarcation是指明确定义事务起止边界的任务。如果划分不当会直接影响应用性能多数数据库与数据库抽象层默认工作在 auto-commit 模式即每一条 SQL 语句都被包裹在一个小事务中。在没有显式事务边界的情况下这种模式会迅速累积大量廉价但昂贵的提交开销导致性能恶化。MikroORM 在大多数场景下已经替你完成了正确的事务划分所有写操作INSERT/UPDATE/DELETE都会排队等待直到em.flush()被调用而 flush 会把所有这些变更包裹在单个事务中提交。这正是 Unit of Work 模式的体现——DML 操作的聚合使得一次 flush 即一个事务成为可能。与此同时MikroORM 也允许并且鼓励你接管事务边界自行控制。下面介绍两种主要方式。方式一隐式事务Implicitly不写任何显式事务代码时MikroORM 的EntityManager会提供隐式事务处理const user new User(...); user.name George; await orm.em.persist(user).flush();由于上述代码没有自定义事务边界em.flush()会自动开启事务并在成功后提交、失败时回滚。该行为由 MikroORM 对 DML 操作的聚合机制保证当某个工作单元Unit of Work内的所有数据操作都经由领域模型即 ORM完成时隐式事务已经足够。方式二显式事务Explicitly显式的替代方案是直接使用事务 API 控制边界await orm.em.transactional(em { //... do some work const user new User(...); user.name George; em.persist(user); });也可以显式调用begin/commit/rollback方法下面的示例与上面的transactional()写法等价const em orm.em.fork(); await em.begin(); try { //... do some work const user new User(...); user.name George; em.persist(user); await em.commit(); // 会在真正发出 commit 查询之前先 flush } catch (e) { await em.rollback(); throw e; }从源码看em.commit()的实现确实是先 flush 再提交它首先调用em.flush()随后才通过连接层发出 commit 查询若此时没有活动事务上下文会抛出ValidationError.transactionRequired()见 EntityManager.ts。em.rollback()在回滚后还会清空 Unit of Work 的 action 队列避免脏变更残留。显式事务的适用场景当你希望把自定义 DBAL 操作纳入同一个工作单元或需要使用某些要求活动事务的EntityManagerAPI 时例如悲观锁必须采用显式事务。这些方法在没有事务时会抛出ValidationError来提醒你。使用 Transactional() 装饰器除了命令式 API还可以通过Transactional()装饰器声明式地管理事务import { EntityManager, MikroORM, Transactional } from mikro-orm/core; export class MyService { constructor(private readonly em: EntityManager) { } Transactional() async doSomething() { //... do some work const user new User(...); user.name George; this.em.persist(user); } }该装饰器本质上是对em.transactional()的包装因此可以像em.transactional()一样传入TransactionOptions。其独有差异在于可通过context选项指定事务开始的上下文省略时事务会在当前上下文中隐式开始。它只能作用于async 函数源码中会检查value.constructor.name ! AsyncFunction并抛出错误并且可以与em.transactional()互相嵌套。装饰器在解析EntityManager时会依次尝试context选项、TransactionContext、RequestContext详见 decorators/src/es/Transactional.ts。需要强调em.transactional(cb)和Transactional()都会在事务提交前自动 flush 内部的EntityManager因此回调内em.persist()后无需手动调用flush()。事务传播Transaction Propagation事务传播定义了多个事务方法相互调用时事务之间如何关联。在构建跨越多个服务层的复杂业务逻辑时尤其重要。默认行为em.transactional默认使用NESTED传播——当你在已有事务内再次调用em.transactional()时它会自动创建 savepoint嵌套事务而不是加入现有事务从而使内层事务的失败可以被独立处理await em.transactional(async em1 { // 外层事务 await em1.transactional(async em2 { // 内层事务NESTED 传播 }); });Transactional()装饰器的默认传播则是REQUIRED如果已存在事务则直接使用否则创建新事务class BookService { constructor(private readonly em: EntityManager) { } Transactional() // 默认使用 REQUIRED 传播 async createBook() { // 外层事务 await this.addAuthor(); // 内层事务REQUIRED 传播 } Transactional() async addAuthor() { // 内层事务 } }传播行为可以通过propagation选项自定义。完整的传播模式如下表Propagation说明NESTED默认存在事务时创建 savepoint否则创建新事务REQUIRED存在事务时直接使用不创建 savepoint否则创建新事务REQUIRES_NEW总是创建独立事务挂起现有事务SUPPORTS存在事务时使用否则以非事务方式执行MANDATORY必须有现有事务否则抛出错误NOT_SUPPORTED挂起现有事务并以非事务方式执行NEVER必须无事务执行若存在事务则抛出错误这些枚举值与传播逻辑在 enums.ts 中定义。底层的分派逻辑集中在 TransactionManager.ts 的executeWithPropagation()例如REQUIRED在已有事务时直接执行回调NESTED则调用executeNestedTransaction()通过传入现有ctx来创建 savepointREQUIRES_NEW会先suspendTransaction()挂起当前事务再开新事务MANDATORY与NEVER在条件不满足时分别抛出TransactionStateError.requiredTransactionNotFound()与TransactionStateError.transactionNotAllowed()。仓库在 tests/features/transactions/transaction-propagation.test.ts 中对每种模式都有对应的集成测试验证。NESTED 传播存在事务时创建 savepoint否则创建新事务。这是em.transactional()的默认行为// 使用 em.transactional() await em.transactional(async (em1) { const book new Book(...); book.title Domain-Driven Design; em1.persist(book); await em1.transactional(async (em2) { const author new Author(...); author.name Eric Evans; em2.persist(author); throw new Error(Nested transaction failed); }); // 尽管内层事务失败Book 依然被保存 }); // 使用 Transactional() 装饰器 class BookService { constructor(private readonly em: EntityManager) { } Transactional() async createBook() { const book new Book(...); book.title Domain-Driven Design; this.em.persist(book); await this.addAuthor(); // 嵌套调用创建 savepoint } Transactional({ propagation: TransactionPropagation.NESTED }) async addAuthor() { const author new Author(...); author.name Eric Evans; this.em.persist(author); throw new Error(Nested transaction failed); } }嵌套调用自动创建 savepoint使内层事务可以独立失败而不影响外层事务。测试 transaction-propagation.test.ts 中的 NESTED propagation should properly isolate savepoint rollbacks 验证了该隔离行为。SUPPORTS 传播灵活适应当前上下文——有事务就用没有就以非事务方式执行// 使用 em.transactional() await em.transactional(async (em1) { await em1.transactional(async (em2) { const author new Author(...); author.name Jane Smith; author.email janeexample.com; em2.persist(author); }, { propagation: TransactionPropagation.SUPPORTS }); }); // 使用 Transactional() 装饰器 class BookService { constructor(private readonly em: EntityManager) { } Transactional({ propagation: TransactionPropagation.SUPPORTS }) async findBookWithAuthor(id: number) { const book await this.em.findOneOrFail(Book, id, { populate: [author] }); // 无论是否有事务都能工作 return book; } Transactional() async updateBookTitle(id: number, title: string) { // 这里会创建事务 const book await this.findBookWithAuthor(id); // 加入现有事务 book.title title; } }适合那些有事务一致性更好、没有也无妨的读操作。从实现看SUPPORTS在无现有事务时走executeWithoutTransaction()即挂起当前上下文并用disableTransactions: true的 fork 执行回调。REQUIRED 传播使用现有事务但不创建 savepoint所有操作共享同一事务上下文// 使用 em.transactional() await em.transactional(async (em1) { const author new Author(...); author.name John Doe; author.email johnexample.com; em1.persist(author); await em1.transactional(async (em2) { const book new Book(...); book.title Clean Code; book.author author; em2.persist(book); throw new Error(); }, { propagation: TransactionPropagation.REQUIRED }); }); // 使用 Transactional() 装饰器 class LibraryService { constructor(private readonly em: EntityManager) { } Transactional() async createAuthorWithBook() { const author new Author(...); author.name Robert C. Martin; author.email bobexample.com; this.em.persist(author); await this.addBook(author); // 将抛出错误 } Transactional() async addBook(author: Author) { const book new Book(...); book.title Clean Code; book.author author; this.em.persist(book); throw new Error(); // author 与 book 都会被回滚 } }当所有操作必须要么全部成功、要么全部失败作为一个原子单元时使用REQUIRED。MANDATORY 传播强制方法必须在现有事务内被调用// 使用 em.transactional() async function updateBookStock(bookId: number, quantity: number) { await em.transactional(async (em) { const book await em.findOneOrFail(Book, bookId); book.stock quantity; }, { propagation: TransactionPropagation.MANDATORY }); } // 使用 Transactional() 装饰器 class InventoryService { constructor(private readonly em: EntityManager) { } Transactional({ propagation: TransactionPropagation.MANDATORY }) async updateStock(bookId: number, quantity: number) { const book await this.em.findOneOrFail(Book, bookId); book.stock quantity; } } // 正确用法——在事务内调用 await em.transactional(async () { await inventoryService.updateStock(1, 10); }); // 抛出错误——不存在事务 await inventoryService.updateStock(1, 10);适用于那些绝不应在事务上下文之外运行的关键操作。源码中当不存在现有事务时会抛出TransactionStateError.requiredTransactionNotFound()测试 MANDATORY propagation should throw error when no transaction existstransaction-propagation.test.ts验证了这一行为。REQUIRES_NEW 传播创建完全独立的事务其提交/回滚互不影响// 使用 em.transactional() await em.transactional(async (em1) { const author new Author(...); author.name John Doe; author.email johnexample.com; em1.persist(author); await em1.transactional(async (em2) { const book new Book(...); book.title Domain-Driven Design; em2.persist(book); }, { propagation: TransactionPropagation.REQUIRES_NEW }); throw new Error(); // Author 被回滚但 book 已经提交 }); // 使用 Transactional() 装饰器 class BookService { constructor(private readonly em: EntityManager) { } Transactional() async createAuthor(name: string, email: string) { const author new Author(...); author.name name; author.email email; this.em.persist(author); await this.logCreation(AUTHOR_CREATED); // 独立事务 throw new Error(); // Author 被回滚但日志已持久化 } Transactional({ propagation: TransactionPropagation.REQUIRES_NEW }) async logCreation(action: string) { const log new AuditLog(...); log.action action; log.timestamp new Date(); this.em.persist(log); // 独立提交 } }适用于必须无视外层事务结果而完成的操作如审计日志、支付处理。源码实现中REQUIRES_NEW会先挂起现有事务suspendTransaction()为内层 fork 清空ctx后再开启新事务最后在finally中恢复被挂起的事务TransactionManager.ts。测试还验证了REQUIRES_NEW使用独立连接、外层失败内层仍提交等场景。NEVER 传播确保方法在任何事务上下文之外执行// 使用 em.transactional() class ExternalService { async sendNotification(bookTitle: string) { await this.em.transactional(async (em) { await externalNotificationAPI.notify(New book: ${bookTitle}); }, { propagation: TransactionPropagation.NEVER }); } } // 使用 Transactional() 装饰器 class EmailService { constructor(private readonly em: EntityManager) { } Transactional({ propagation: TransactionPropagation.NEVER }) async sendNewBookEmail(authorEmail: string, bookTitle: string) { // 绝不能运行在事务中 await externalEmailService.send(authorEmail, Your book ${bookTitle} was published); } Transactional() async publishBook(book: Book) { // 这里会抛出错误因为 sendNewBookEmail 要求无事务 await this.sendNewBookEmail(book.author.email, book.title); } } // 正常——无事务 await emailService.sendNewBookEmail(authorexample.com, Clean Code); // 抛出错误——存在事务 await em.transactional(async () { await emailService.sendNewBookEmail(authorexample.com, Clean Code); });适用于绝不能参与事务的操作如外部服务调用、审计日志。当检测到活动事务时源码抛出TransactionStateError.transactionNotAllowed()。NOT_SUPPORTED 传播挂起现有事务以非事务方式执行方法// 使用 em.transactional() await em.transactional(async (em1) { const author new Author(...); author.name John Doe; author.email johnexample.com; em1.persist(author); await em1.transactional(async (em2) { // 以无事务方式执行 const stats await em2.count(Book); return stats; }, { propagation: TransactionPropagation.NOT_SUPPORTED }); // 事务在这里恢复 }); // 使用 Transactional() 装饰器 class ReportService { constructor(private readonly em: EntityManager) { } Transactional({ propagation: TransactionPropagation.NOT_SUPPORTED }) async generateReport() { // 即使从事务上下文调用也在无事务状态下运行 const books await this.em.find(Book, {}); return this.processReport(books); } Transactional() async updateAndReport() { const book await this.em.findOneOrFail(Book, 1); book.views; await this.generateReport(); // 事务被挂起 // 报表生成后事务恢复 } }适用于不需要事务保证的非关键操作如报表、缓存。测试 transaction-propagation.test.ts 验证了即使外层事务失败回滚NOT_SUPPORTED中已提交的数据依然保留。上下文传播Context Propagation使用em.transactional()或Transactional()装饰器时会为事务创建一个新的上下文一个EntityManagerfork并通过回调参数传入。如果你使用全局EntityManager实例或使用useContext: true创建的 fork内层上下文会被自动尊重——其机制与RequestContext类似因此即使在显式事务的回调内部你依然可以放心地使用来自 DI 容器的EntityManager。内层上下文以clear: false选项创建意味着新的身份映射identity map不会清空——准确地说它会被上层上下文中的所有被管理实体填充。这样你可以在事务回调中直接使用同一批实体而无需重新获取。如果想清空身份映射可以给em.transactional()或Transactional()装饰器传入clear: true// 在上层上下文加载 user const user await em.findOneOrFail(User, 1); await em.transactional(() { // 在回调内部它同样可用 user.isActive true; }); // 这里变更已经 flush因为 em.transactional() 会在提交前 flush 内部 EntityManager警告如果要创建多个事务并并行运行应当使用全新的 fork 或clear: true选项否则上下文之间会互相干扰。使用默认的clear: false时实体实例会在多个事务间共享可能导致意外结果例如已删除实体被重新插入。这也正是 EntityManager.ts 中并发使用注释所强调的clear: true相当于显式 fork 后调用事务方法的隔离保障。同样地事务回调内部所做的变更会传播回上层上下文因此你可以用同一个EntityManager实例在回调内外工作const parent await em.findOneOrFail(Parent, 1, { populate: [children], }); console.log(parent.children.count()); // 0 await em.transactional(async () { em.create(Child, { parent }); }); console.log(parent.children.count()); // 1这一行为由 TransactionManager.ts 中的mergeEntitiesToParent()实现事务 fork 中身份映射的实体会在 flush 后被合并回父级EntityManager父级为空时直接整体搬移以跳过em.merge开销否则逐实体合并、避免用未初始化的引用覆盖已完整加载的实体。异常处理Exception Handling使用隐式事务时若em.flush()过程中发生异常事务会自动回滚。使用显式事务时异常发生后应立即回滚事务如前文示例所示这一过程可以由em.transactional()这类控制抽象优雅地处理。捕获异常时一般应当重新抛出如果你打算从某些异常中恢复应在更早的 catch 块中显式捕获它们但不要忘记回滚事务。其他异常处理最佳实践同样适用例如记录日志与重新抛出二选一不要同时做等等。异常处理的一个关键副作用回滚之后所有此前被管理managed或已移除removed的EntityManager实例都会变成游离detached状态。游离对象的状态是回滚时刻的状态其状态并不会被回滚因此这些对象与数据库已经不同步。应用可以继续使用这些游离对象但必须清楚其状态可能不再准确。如果希望在异常发生后开始新的工作单元应当使用新的EntityManager。只需调用em.fork()即可获得一个身份映射已清空的干净副本。并发控制为什么需要它如果事务串行执行一次一个就不存在事务并发问题。然而一旦允许并发事务交错执行很容易遇到以下经典问题**丢失更新lost update**问题**脏读dirty read**问题**错误汇总incorrect summary**问题为了缓解这些问题MikroORM 原生支持**悲观锁Pessimistic Locking与乐观锁Optimistic Locking**两种策略让你能够对应用中的实体进行非常细粒度的锁控制。乐观锁Optimistic Locking数据库事务足以控制单次请求内的并发。但数据库事务不应跨越请求即所谓的用户思考时间。因此跨越多个请求的**长事务business transaction**必然涉及多个数据库事务此时并发控制的责任就部分落到了应用自己身上。MikroORM 通过**版本字段version field**集成了自动乐观锁支持。任何需要在长事务中防止并发修改的实体都可以拥有一个版本字段——类型为简单数字mapping type: integer或时间戳mapping type: datetime。当长对话结束时持久化对该实体的修改时实体的版本会与数据库中的版本进行比较若不一致则抛出OptimisticLockError提示该实体已被其他人修改。声明版本字段的方式如下以 integer 为例export class User { // ... Property({ version: true }) version!: number; // ... }也可以使用 datetime 类型映射为 SQL 的 timestamp 或 datetimeexport class User { // ... Property({ version: true }) version!: Date; // ... }不过应优先使用版本数字而非时间戳在高度并发的环境中时间戳可能因数据库平台的时间精度限制而冲突数字则不存在该问题。当em.flush()期间检测到版本冲突会抛出OptimisticLockError同时活动事务被回滚或标记为回滚。该异常可以被捕获并处理常见的应对方式是向用户展示冲突信息或者在新事务中刷新/重新加载对象后重试。考虑到从展示编辑表单到真正修改实体的间隔最坏情况下可能长达应用会话超时时间。如果实体在此期间被修改你希望在读取实体时就能提前知道即将发生乐观锁异常可以在em.findOne()时校验版本const theEntityId 1; const expectedVersion 184; try { const entity await orm.em.findOne(User, theEntityId, { lockMode: LockMode.OPTIMISTIC, lockVersion: expectedVersion }); // do the work await orm.em.flush(); } catch (e) { console.log(Sorry, but someone else has already changed this entity. Please apply the changes again!); }也可以使用em.lock()检查const theEntityId 1; const expectedVersion 184; const entity await orm.em.findOne(User, theEntityId); try { // 校验版本 await orm.em.lock(entity, LockMode.OPTIMISTIC, expectedVersion); } catch (e) { console.log(Sorry, but someone else has already changed this entity. Please apply the changes again!); }从源码看乐观锁校验位于 UnitOfWork.ts若元数据中没有版本属性则抛出OptimisticLockError.notVersioned(meta)随后会比较实体当前版本与期望版本不一致时抛出OptimisticLockError.lockFailedVersionMismatch()。并发检查Concurrency Checks与自动处理的版本字段不同并发检查concurrency checks允许你标记特定属性参与并发检查——用法类似版本字段但这次你需要自行负责显式更新这些字段。当你不修改任何并发检查字段就尝试更新实体时会抛出OptimisticLockError更新完成后同一机制会检查更新是否成功失败时同样抛出该类型错误Entity() export class ConcurrencyCheckUser { // 所有主键默认都属于并发检查的一部分 PrimaryKey({ length: 100 }) firstName: string; // 所有主键默认都属于并发检查的一部分 PrimaryKey({ length: 100 }) lastName: string; Property({ concurrencyCheck: true }) age: number; Property({ nullable: true }) other?: string; }并发检查的实际 SQL 行为可以在 tests/features/concurrency-checks/concurrency-checks.postgre.test.ts 中看到UPDATE 语句的where条件会带上并发检查字段的当前值如where first_name Jakub and last_name Smith and age 20更新后通过受影响行数判断是否发生并发冲突。测试还验证了通过orm.em.nativeUpdate()模拟并发修改后em.flush()会抛出OptimisticLockError并携带冲突实体e.getEntity()。重要实现注意事项乐观锁工作流很容易因比较了错误的版本而出错。假设 Alice 和 Bob 在编辑同一篇博客文章Alice 读到博客标题为 Foo乐观锁版本为 1GET 请求Bob 读到博客标题为 Foo乐观锁版本为 1GET 请求Bob 把标题更新为 Bar乐观锁版本升级为 2表单的 POST 请求Alice 把标题更新为 Baz……表单的 POST 请求在最后阶段Alice 的标题真正应用前必须重新从数据库读取这篇博客文章。此时你需要检查文章是否仍是版本 1在本场景中已经不是。要正确使用乐观锁你必须把版本作为一个额外的隐藏字段加入表单或者更安全地放入 session。否则你无法确认版本是否仍是 Alice 发起 GET 请求时所读到的那一个。如果做不到就可能出现你本想用乐观锁防止的丢失更新。前端示例代码const res await fetch(api.example.com/book/123); const book res.json(); console.log(book.version); // 打印当前版本 // 用户做了一些修改然后调用 PUT handler const changes { title: new title }; await fetch(api.example.com/book/123, { method: PUT, body: { ...changes, version: book.version, }, });对应的 API 端点// GET /book/:id async findOne(req, res) { const book await this.em.findOne(Book, req.query.id); res.json(book); } // PUT /book/:id async update(req, res) { const book await this.em.findOne(Book, req.query.id, { lockMode: LockMode.OPTIMISTIC, lockVersion: req.body.version }); wrap(book).assign(req.body); await this.em.flush(); res.json(book); }整个流程是前端应用从 API 加载实体响应中包含版本属性 → 用户做修改并携带版本字段向 API 发出 PUT 请求 → API 的 PUT handler 读取版本并传给em.findOne()。悲观锁Pessimistic LockingMikroORM 在数据库层面支持悲观锁。它不在 MikroORM 内部实现悲观锁而是使用供应商特定与 ANSI-SQL 命令来获取行级锁。每个实体都可以参与悲观锁无需任何特殊元数据。使用悲观锁需要活动事务如果尝试在无事务运行时获取悲观锁MikroORM 会抛出异常源码中lockPessimistic()会先检查isInTransaction()否则抛出ValidationError.transactionRequired()见 UnitOfWork.ts。MikroORM 目前支持 6 种悲观锁模式模式PostgresMySQLLockMode.PESSIMISTIC_READfor sharelock in share modeLockMode.PESSIMISTIC_WRITEfor updatefor updateLockMode.PESSIMISTIC_PARTIAL_WRITEfor update skip lockedfor update skip lockedLockMode.PESSIMISTIC_WRITE_OR_FAILfor update nowaitfor update nowaitLockMode.PESSIMISTIC_PARTIAL_READfor share skip lockedlock in share mode skip lockedLockMode.PESSIMISTIC_READ_OR_FAILfor share nowaitlock in share mode nowait这 6 种模式与LockMode枚举一一对应见 enums.tsPESSIMISTIC_READ为共享锁FOR SHARE、PESSIMISTIC_WRITE为排他锁FOR UPDATEPARTIAL_*变体跳过已加锁行SKIP LOCKED*_OR_FAIL变体在被锁时立即失败NOWAIT。你可以在三种不同场景下使用悲观锁使用em.findOne(className, id, { lockMode: LockMode.PESSIMISTIC_WRITE })或em.findOne(className, id, { lockMode: LockMode.PESSIMISTIC_READ })使用em.lock(entity, LockMode.PESSIMISTIC_WRITE)或em.lock(entity, LockMode.PESSIMISTIC_READ)使用QueryBuilder.setLockMode(LockMode.PESSIMISTIC_WRITE)或QueryBuilder.setLockMode(LockMode.PESSIMISTIC_READ)可选地你可以通过lockTableAliases选项传入想要加锁的表别名列表const res await em.find(User, { name: Jon }, { populate: [identities], strategy: LoadStrategy.JOINED, lockMode: LockMode.PESSIMISTIC_READ, lockTableAliases: [u0], }); // select ... // from user as u0 // left join identity as i1 on u0.id i1.user_id // where u0.name Jon // for update of u0 skip locked当使用 JOINED 加载策略时lockTableAliases让你精确控制只锁定特定表如上面的u0避免对整个结果集加锁。隔离级别Isolation Levels可以设置事务隔离级别await orm.em.transactional(async em { // ... }, { isolationLevel: IsolationLevel.READ_UNCOMMITTED });可用的隔离级别定义于 enums.tsIsolationLevel.READ_UNCOMMITTED——允许脏读、不可重复读与幻读IsolationLevel.READ_COMMITTED——防止脏读不可重复读与幻读仍可能发生IsolationLevel.SNAPSHOT——快照隔离每个事务看到数据库的一致快照MSSQLIsolationLevel.REPEATABLE_READ——防止脏读与不可重复读幻读仍可能发生IsolationLevel.SERIALIZABLE——完全隔离事务如同串行执行禁用事务Disabling Transactions可以禁用事务既可以通过全局的disableTransactions配置选项也可以在em.transactional()调用时局部禁用// 只有最外层事务会被开启 await orm.em.transactional(async em { // 但内部的 em.transactional 与 em.begin 调用都将是 no-op await em.transactional(...); }, { disableTransactions: true });或者在创建新 fork 时禁用事务const em await orm.em.fork({ disableTransactions: true }); await em.transactional(...); // no-op await em.begin(...); // no-op await em.commit(...); // commit 仍然会调用 flush从 EntityManager.ts 的源码可以看到transactional()在#disableTransactions为真时直接执行回调而不开启事务begin()与rollback()同样直接返回no-op而commit()仍会执行flush()EntityManager.ts。fork 时disableTransactions选项的传递逻辑位于 EntityManager.ts。相关配置还支持ignoreNestedTransactions选项用于仅禁用嵌套事务。延伸阅读Unit of Work理解 flush 与事务聚合的底层机制Identity Map理解RequestContext与上下文传播的关联使用 MongoDBMongoDB 驱动下的事务支持迁移指南事务 API 在版本升级中的变化事务传播集成测试七种传播模式的完整测试用例并发检查测试乐观锁与并发检查字段的 SQL 级验证赞分享后端【免费下载链接】mikro-ormTypeScript ORM for Node.js based on Data Mapper, Unit of Work and Identity Map patterns. Supports MongoDB, MySQL, MariaDB, MS SQL Server, PostgreSQL and SQLite/libSQL databases.项目地址https://gitcode.com/gh_mirrors/mi/mikro-orm点击查看免费下载相关推荐MikroORM 事务界定与并发控制实战从 flush 到乐观锁与悲观锁MikroORM 事务界定与并发控制实战从 flush 到乐观锁与悲观锁 本文以 MikroORM 官方博客《Handling Transactions an后端MikroORM 5.9 事务与并发控制完全指南从隐式事务到乐观/悲观锁MikroORM 5.9 事务与并发控制完全指南从隐式事务到乐观/悲观锁 事务与并发控制是 MikroORM 这类基于 Unit of Work工作单元与后端Doctrine ORM 事务与并发控制实战事务界定、异常处理与乐观/悲观锁Doctrine ORM 事务与并发控制实战事务界定、异常处理与乐观/悲观锁 本文以 Doctrine ORM当前仓库 gh_mirrors/or/orm数据库ORM后端上一篇closure-compiler性能分析工具找出代码中的性能瓶颈下一篇rn-diff-purge背后的故事从rn-diff到社区标准工具的演变创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考
网站建设高端定制企业官网