Skip to main content
Version: v6 - stable

事务

Sequelize 默认情况下不使用 transactions。但是,对于 Sequelize 的生产就绪使用,你绝对应该将 Sequelize 配置为使用事务。

¥Sequelize does not use transactions by default. However, for production-ready usage of Sequelize, you should definitely configure Sequelize to use transactions.

Sequelize 支持两种使用事务的方式:

¥Sequelize supports two ways of using transactions:

  1. 非托管事务:提交和回滚事务应由用户手动完成(通过调用适当的 Sequelize 方法)。

    ¥Unmanaged transactions: Committing and rolling back the transaction should be done manually by the user (by calling the appropriate Sequelize methods).

  2. 托管事务:如果抛出任何错误,Sequelize 将自动回滚事务,否则提交事务。另外,如果启用了 CLS(连续本地存储),则事务回调内的所有查询都将自动接收事务对象。

    ¥Managed transactions: Sequelize will automatically rollback the transaction if any error is thrown, or commit the transaction otherwise. Also, if CLS (Continuation Local Storage) is enabled, all queries within the transaction callback will automatically receive the transaction object.

非管理事务

¥Unmanaged transactions

让我们从一个例子开始:

¥Let's start with an example:

// First, we start a transaction from your connection and save it into a variable
const t = await sequelize.transaction();

try {
// Then, we do some calls passing this transaction as an option:

const user = await User.create(
{
firstName: 'Bart',
lastName: 'Simpson',
},
{ transaction: t },
);

await user.addSibling(
{
firstName: 'Lisa',
lastName: 'Simpson',
},
{ transaction: t },
);

// If the execution reaches this line, no errors were thrown.
// We commit the transaction.
await t.commit();
} catch (error) {
// If the execution reaches this line, an error was thrown.
// We rollback the transaction.
await t.rollback();
}

如上所示,非托管事务方法要求你在必要时手动提交和回滚事务。

¥As shown above, the unmanaged transaction approach requires that you commit and rollback the transaction manually, when necessary.

托管事务

¥Managed transactions

托管事务自动处理提交或回滚事务。你可以通过将回调传递给 sequelize.transaction 来启动托管事务。该回调可以是 async(通常是)。

¥Managed transactions handle committing or rolling back the transaction automatically. You start a managed transaction by passing a callback to sequelize.transaction. This callback can be async (and usually is).

在这种情况下会发生以下情况:

¥The following will happen in this case:

  • Sequelize 会自动启动一个事务并获取一个事务对象 t

    ¥Sequelize will automatically start a transaction and obtain a transaction object t

  • 然后,Sequelize 将执行你提供的回调,并将 t 传递给其中

    ¥Then, Sequelize will execute the callback you provided, passing t into it

  • 如果你的回调抛出错误,Sequelize 将自动回滚事务

    ¥If your callback throws an error, Sequelize will automatically rollback the transaction

  • 如果回调成功,Sequelize 将自动提交事务

    ¥If your callback succeeds, Sequelize will automatically commit the transaction

  • 只有这样 sequelize.transaction 调用才会结算:

    ¥Only then the sequelize.transaction call will settle:

    • 要么通过回调的解析来解决

      ¥Either resolving with the resolution of your callback

    • 或者,如果你的回调抛出,则拒绝并抛出错误

      ¥Or, if your callback throws, rejecting with the thrown error

示例代码:

¥Example code:

try {
const result = await sequelize.transaction(async t => {
const user = await User.create(
{
firstName: 'Abraham',
lastName: 'Lincoln',
},
{ transaction: t },
);

await user.setShooter(
{
firstName: 'John',
lastName: 'Boothe',
},
{ transaction: t },
);

return user;
});

// If the execution reaches this line, the transaction has been committed successfully
// `result` is whatever was returned from the transaction callback (the `user`, in this case)
} catch (error) {
// If the execution reaches this line, an error occurred.
// The transaction has already been rolled back automatically by Sequelize!
}

请注意,t.commit()t.rollback() 没有直接调用(这是正确的)。

¥Note that t.commit() and t.rollback() were not called directly (which is correct).

抛出错误以回滚

¥Throw errors to rollback

使用托管事务时,切勿手动提交或回滚事务。如果所有查询都成功(在不抛出任何错误的意义上),但你仍然想回滚事务,则应该自己抛出错误:

¥When using the managed transaction you should never commit or rollback the transaction manually. If all queries are successful (in the sense of not throwing any error), but you still want to rollback the transaction, you should throw an error yourself:

await sequelize.transaction(async t => {
const user = await User.create(
{
firstName: 'Abraham',
lastName: 'Lincoln',
},
{ transaction: t },
);

// Woops, the query was successful but we still want to roll back!
// We throw an error manually, so that Sequelize handles everything automatically.
throw new Error();
});

自动将事务传递给所有查询

¥Automatically pass transactions to all queries

在上面的示例中,事务仍然是手动传递的,通过传递 { transaction: t } 作为第二个参数。要自动将事务传递给所有查询,你必须安装 cls-hooked (CLS) 模块并在你自己的代码中实例化命名空间:

¥In the examples above, the transaction is still manually passed, by passing { transaction: t } as the second argument. To automatically pass the transaction to all queries you must install the cls-hooked (CLS) module and instantiate a namespace in your own code:

const cls = require('cls-hooked');
const namespace = cls.createNamespace('my-very-own-namespace');

要启用 CLS,你必须使用 sequelize 构造函数的静态方法告诉 sequelize 要使用哪个命名空间:

¥To enable CLS you must tell sequelize which namespace to use by using a static method of the sequelize constructor:

const Sequelize = require('sequelize');
Sequelize.useCLS(namespace);

new Sequelize(....);

请注意,useCLS() 方法位于构造函数上,而不是位于 Sequelize 实例上。这意味着所有实例将共享相同的命名空间,并且 CLS 是全有或全无的 - 你不能仅在某些情况下启用它。

¥Notice, that the useCLS() method is on the constructor, not on an instance of sequelize. This means that all instances will share the same namespace, and that CLS is all-or-nothing - you cannot enable it only for some instances.

CLS 的工作原理类似于用于回调的线程本地存储。这在实践中意味着不同的回调链可以通过使用 CLS 命名空间来访问局部变量。启用 CLS 后,sequelize 将在创建新事务时在命名空间上设置 transaction 属性。由于回调链中设置的变量是该链私有的,因此多个并发事务可以同时存在:

¥CLS works like a thread-local storage for callbacks. What this means in practice is that different callback chains can access local variables by using the CLS namespace. When CLS is enabled sequelize will set the transaction property on the namespace when a new transaction is created. Since variables set within a callback chain are private to that chain several concurrent transactions can exist at the same time:

sequelize.transaction(t1 => {
namespace.get('transaction') === t1; // true
});

sequelize.transaction(t2 => {
namespace.get('transaction') === t2; // true
});

在大多数情况下,你不需要直接访问 namespace.get('transaction'),因为所有查询都会自动在命名空间上查找事务:

¥In most case you won't need to access namespace.get('transaction') directly, since all queries will automatically look for a transaction on the namespace:

sequelize.transaction(t1 => {
// With CLS enabled, the user will be created inside the transaction
return User.create({ name: 'Alice' });
});

并发/局部事务

¥Concurrent/Partial transactions

你可以在一系列查询中包含并发事务,或者将其中一些事务排除在任何事务之外。使用 transaction 选项控制查询属于哪个事务:

¥You can have concurrent transactions within a sequence of queries or have some of them excluded from any transactions. Use the transaction option to control which transaction a query belongs to:

注意:SQLite 不支持同时处理多个事务。

¥Note: SQLite does not support more than one transaction at the same time.

启用 CLS 后

¥With CLS enabled

sequelize.transaction(t1 => {
return sequelize.transaction(t2 => {
// With CLS enabled, queries here will by default use t2.
// Pass in the `transaction` option to define/alter the transaction they belong to.
return Promise.all([
User.create({ name: 'Bob' }, { transaction: null }),
User.create({ name: 'Mallory' }, { transaction: t1 }),
User.create({ name: 'John' }), // this would default to t2
]);
});
});

传递选项

¥Passing options

sequelize.transaction 方法接受选项。

¥The sequelize.transaction method accepts options.

对于非托管事务,只需使用 sequelize.transaction(options)

¥For unmanaged transactions, just use sequelize.transaction(options).

对于托管事务,请使用 sequelize.transaction(options, callback)

¥For managed transactions, use sequelize.transaction(options, callback).

隔离级别

¥Isolation levels

启动事务时可能使用的隔离级别:

¥The possible isolations levels to use when starting a transaction:

const { Transaction } = require('sequelize');

// The following are valid isolation levels:
Transaction.ISOLATION_LEVELS.READ_UNCOMMITTED; // "READ UNCOMMITTED"
Transaction.ISOLATION_LEVELS.READ_COMMITTED; // "READ COMMITTED"
Transaction.ISOLATION_LEVELS.REPEATABLE_READ; // "REPEATABLE READ"
Transaction.ISOLATION_LEVELS.SERIALIZABLE; // "SERIALIZABLE"

默认情况下,sequelize 使用数据库的隔离级别。如果你想使用不同的隔离级别,请传入所需的级别作为第一个参数:

¥By default, sequelize uses the isolation level of the database. If you want to use a different isolation level, pass in the desired level as the first argument:

const { Transaction } = require('sequelize');

await sequelize.transaction(
{
isolationLevel: Transaction.ISOLATION_LEVELS.SERIALIZABLE,
},
async t => {
// Your code
},
);

你还可以使用 Sequelize 构造函数中的选项全局覆盖 isolationLevel 设置:

¥You can also overwrite the isolationLevel setting globally with an option in the Sequelize constructor:

const { Sequelize, Transaction } = require('sequelize');

const sequelize = new Sequelize('sqlite::memory:', {
isolationLevel: Transaction.ISOLATION_LEVELS.SERIALIZABLE,
});

MSSQL 注意事项:由于指定的 isolationLevel 直接传递到 tedious,因此不会记录 SET ISOLATION LEVEL 查询。

¥Note for MSSQL: The SET ISOLATION LEVEL queries are not logged since the specified isolationLevel is passed directly to tedious.

与其他 sequelize 方法一起使用

¥Usage with other sequelize methods

transaction 选项与大多数其他选项配合使用,这些选项通常是方法的第一个参数。

¥The transaction option goes with most other options, which are usually the first argument of a method.

对于采用值的方法,如 .create.update() 等,transaction 应传递给第二个参数中的选项。

¥For methods that take values, like .create, .update(), etc. transaction should be passed to the option in the second argument.

如果不确定,请参阅你正在使用的方法的 API 文档来确定签名。

¥If unsure, refer to the API documentation for the method you are using to be sure of the signature.

示例:

¥Examples:

await User.create({ name: 'Foo Bar' }, { transaction: t });

await User.findAll({
where: {
name: 'Foo Bar',
},
transaction: t,
});

afterCommit 钩子

¥The afterCommit hook

transaction 对象允许跟踪它是否以及何时提交。

¥A transaction object allows tracking if and when it is committed.

afterCommit 钩子可以添加到托管和非托管事务对象:

¥An afterCommit hook can be added to both managed and unmanaged transaction objects:

// Managed transaction:
await sequelize.transaction(async t => {
t.afterCommit(() => {
// Your logic
});
});

// Unmanaged transaction:
const t = await sequelize.transaction();
t.afterCommit(() => {
// Your logic
});
await t.commit();

传递给 afterCommit 的回调可以是 async。在这种情况下:

¥The callback passed to afterCommit can be async. In this case:

  • 对于托管事务:sequelize.transaction 调用将在结算前等待;

    ¥For a managed transaction: the sequelize.transaction call will wait for it before settling;

  • 对于非托管事务:t.commit 调用将在结算之前等待它。

    ¥For an unmanaged transaction: the t.commit call will wait for it before settling.

注意:

¥Notes:

  • 如果事务回滚,则不会引发 afterCommit 钩子;

    ¥The afterCommit hook is not raised if the transaction is rolled back;

  • afterCommit 钩子不会修改事务的返回值(与大多数钩子不同)

    ¥The afterCommit hook does not modify the return value of the transaction (unlike most hooks)

你可以将 afterCommit 钩子与模型钩子结合使用,以了解实例何时保存并在事务外可用

¥You can use the afterCommit hook in conjunction with model hooks to know when a instance is saved and available outside of a transaction

User.afterSave((instance, options) => {
if (options.transaction) {
// Save done within a transaction, wait until transaction is committed to
// notify listeners the instance has been saved
options.transaction.afterCommit(() => /* Notify */)
return;
}
// Save done outside a transaction, safe for callers to fetch the updated model
// Notify
});

¥Locks

transaction 内的查询可以使用锁来执行:

¥Queries within a transaction can be performed with locks:

return User.findAll({
limit: 1,
lock: true,
transaction: t1,
});

事务中的查询可以跳过锁定的行:

¥Queries within a transaction can skip locked rows:

return User.findAll({
limit: 1,
lock: true,
skipLocked: true,
transaction: t2,
});