数据库事务

页面摘要: 数据库事务将多个操作组合在一起,使它们作为一个单元成功提交或回滚。实验性的 strapi.db.transaction 辅助函数暴露了供包裹服务调用使用的 trx、commit 和 rollback 工具。

WARNING

这是一个实验性功能,未来版本中可能会发生变化。

Strapi 5 提供了一个 API,可将一组操作包裹在事务中,以确保数据的完整性。

事务是作为单一单元一起执行的一组操作。如果任何操作失败,整个事务失败,数据将回滚到其先前状态。如果所有操作都成功,事务被提交,数据将永久保存到数据库。

用法

通过将处理函数传入 strapi.db.transaction 来处理事务:

await strapi.db.transaction(async ({ trx, rollback, commit, onCommit, onRollback }) => {
  // It will implicitly use the transaction
  const article = await strapi.documents('api::article.article').create({
    data: { title: 'My Article', slug: 'my-article' },
  });

  await strapi.documents('api::log.log').create({
    data: { action: 'article_created', targetId: article.documentId },
  });
});

事务处理函数执行后,如果所有操作都成功,事务将被提交。如果任何操作抛出异常,事务将回滚,数据恢复到其先前状态。

NOTE

在 strapi.db.transaction 块内执行的每个 文档服务 API、strapi.db.query 和 strapi.entityService 操作都会隐式使用事务。Strapi 使用 AsyncLocalStorage 来传播事务上下文,因此无需将这些 API 显式传入事务对象。

事务处理函数的属性

处理函数接收一个包含以下属性的对象:

属性描述
trx事务对象。可用于在事务内执行 knex 查询。
commit用于提交事务的函数。
rollback用于回滚事务的函数。
onCommit用于注册在事务提交后执行的回调的函数。
onRollback用于注册在事务回滚后执行的回调的函数。

嵌套事务

事务可以嵌套。当事务嵌套时,内部事务会在外部事务提交或回滚时一并提交或回滚。

await strapi.db.transaction(async () => {
  // It will implicitly use the transaction
  await strapi.documents('api::article.article').create({
    data: { title: 'My Article', slug: 'my-article' },
  });

  // Nested transactions will implicitly use the outer transaction
  await strapi.db.transaction(async () => {
    await strapi.documents('api::category.category').create({
      data: { name: 'Tech' },
    });
  });
});

onCommit 与 onRollback

onCommit 和 onRollback 钩子可用于在事务提交或回滚后执行代码。

await strapi.db.transaction(async ({ onCommit, onRollback }) => {
  // It will implicitly use the transaction
  const user = await strapi.documents('api::user.user').create({
    data: { username: 'johndoe', email: 'john@example.com' },
  });

  onCommit(() => {
    // This will be executed after the transaction is committed,
    // e.g., send a welcome email
  });

  onRollback(() => {
    // This will be executed after the transaction is rolled back,
    // e.g., log the failure
  });
});

使用 knex 查询

事务也可以与 knex 查询一起使用,但在这些情况下必须显式调用 .transacting(trx)。

await strapi.db.transaction(async ({ trx, rollback, commit }) => {
  await knex('users').where('id', 1).update({ name: 'foo' }).transacting(trx);
});

何时使用事务

当事务的多个操作应一起执行且彼此执行相互依赖时,应使用事务。例如,在创建新用户时,应在数据库中创建该用户,并向用户发送欢迎邮件。如果邮件发送失败,则不应在数据库中创建该用户。

何时不应使用事务

不应将事务用于彼此不依赖的操作,因为这可能导致性能下降。

事务的潜在问题

在事务内执行多个操作可能导致锁定,从而阻止其他进程的事务执行,直到原事务完成。

此外,如果事务未正确提交或回滚,可能会停滞。

例如,如果打开了事务,但你的代码中存在未关闭它的路径,该事务将无限期保持打开状态,并可能导致不稳定,直到服务器重启并强制关闭连接。这些问题可能难以调试,因此在必要情况下使用事务时务必谨慎。