sequelize 事务——迹忆客-ag捕鱼王app官网
默认情况下,sequelize 不使用事务。 但是,对于 sequelize 的生产环境使用,你绝对应该将 sequelize 配置为使用事务。
sequelize 支持两种使用事务的方式:
- 非托管事务: 提交和回滚事务应由用户手动完成(通过调用适当的 sequelize 方法)。
- 托管事务: 如果引发任何错误,sequelize 将自动回滚事务,否则将提交事务。 另外,如果启用了cls(连续本地存储),则事务回调中的所有查询将自动接收事务对象。
非托管事务
让我们从一个例子开始:
// 首先,我们开始一个事务并将其保存到变量中
const t = await sequelize.transaction();
try {
// 然后,我们进行一些调用以将此事务作为参数传递:
const user = await user.create({
firstname: 'bart',
lastname: 'simpson'
}, { transaction: t });
await user.addsibling({
firstname: 'lisa',
lastname: 'simpson'
}, { transaction: t });
// 如果执行到此行,且没有引发任何错误.
// 我们提交事务.
await t.commit();
} catch (error) {
// 如果执行到达此行,则抛出错误.
// 我们回滚事务.
await t.rollback();
}
如上所示,非托管事务 方法要求你在必要时手动提交和回滚事务。
托管事务
托管事务会自动处理提交或回滚事务。 通过将回调传递给 sequelize.transaction
来启动托管事务。 这个回调可以是 async(通常是)的。
在这种情况下,将发生以下情况:
- sequelize 将自动开始事务并获得事务对象 t
- 然后,sequelize 将执行你提供的回调,并在其中传递 t
- 如果你的回调抛出错误,sequelize 将自动回滚事务
- 如果你的回调成功,sequelize 将自动提交事务
- 只有这样,sequelize.transaction 调用才会解决:
- 解决你的回调的决议
- 或者,如果你的回调引发错误,则拒绝并抛出错误
示例代码:
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;
});
// 如果执行到此行,则表示事务已成功提交,`result`是事务返回的结果
// `result` 就是从事务回调中返回的结果(在这种情况下为 `user`)
} catch (error) {
// 如果执行到此,则发生错误.
// 该事务已由 sequelize 自动回滚!
}
注意,t.commit()
和 t.rollback()
没有被直接调用。
抛出错误以回滚
使用托管事务时,你 不应 手动提交或回滚事务。 如果所有查询都成功(就不引发任何错误而言),但是你仍然想回滚事务,那么你应该自己引发一个错误:
await sequelize.transaction(async t => {
const user = await user.create({
firstname: 'abraham',
lastname: 'lincoln'
}, { transaction: t });
// 查询成功,但我们仍要回滚!
// 我们手动引发错误,以便 sequelize 自动处理所有内容.
throw new error();
});
自动将事务传递给所有查询
在上面的示例中,仍然通过传递 { transaction: t }
作为第二个参数来手动传递事务。 要将事务自动传递给所有查询,你必须安装 cls-hooked (cls) 模块,并在自己的代码中实例化命名空间:
const cls = require('cls-hooked');
const namespace = cls.createnamespace('my-very-own-namespace');
要启用 cls,你必须通过使用 sequelize 构造函数的静态方法来告诉 sequelize 使用哪个命名空间:
const sequelize = require('sequelize');
sequelize.usecls(namespace);
new sequelize(....);
注意,usecls() 方法在 构建器 上,而不在 sequelize 实例上。 这意味着所有实例将共享相同的命名空间,并且 cls 是全有或全无 - 你不能仅对某些实例启用它。
cls 的工作方式类似于用于回调的线程本地存储。 实际上,这意味着不同的回调链可以使用 cls 命名空间访问局部变量。 启用 cls 时,sequelize 将在创建新事务时在命名空间上设置 transaction 属性。 由于在回调链中设置的变量是该链的私有变量,因此可以同时存在多个并发事务:
sequelize.transaction((t1) => {
namespace.get('transaction') === t1; // true
});
sequelize.transaction((t2) => {
namespace.get('transaction') === t2; // true
});
在大多数情况下,你不需要直接访问 namespace.get('transaction')
,因为所有查询都会自动在命名空间上查找事务:
sequelize.transaction((t1) => {
// 启用 cls 后,将在事务内部创建用户
return user.create({ name: 'alice' });
});
并发/部分事务
你可以在一系列查询中进行并发事务,也可以将某些事务排除在任何事务之外。 使用 transaction 参数来控制查询属于哪个事务:
注意: sqlite 不支持同时多个事务。
启用 cls
sequelize.transaction((t1) => {
return sequelize.transaction((t2) => {
// 启用 cls 后,此处的查询默认情况下将使用 t2.
// 传递 `transaction` 参数以定义/更改它们所属的事务.
return promise.all([
user.create({ name: 'bob' }, { transaction: null }),
user.create({ name: 'mallory' }, { transaction: t1 }),
user.create({ name: 'john' }) // 这将默认为 t2
]);
});
});
传递参数
sequelize.transaction
方法接受参数。
对于非托管事务,只需使用 sequelize.transaction(options)
。
对于托管交易,请使用 sequelize.transaction(options, callback)
。
隔离级别
启动事务时可能使用的隔离级别:
const { transaction } = require('sequelize');
// 以下是有效的隔离级别:
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 使用数据库的隔离级别。 如果要使用其他隔离级别,请传入所需的级别作为第一个参数:
const { transaction } = require('sequelize');
await sequelize.transaction({
isolationlevel: transaction.isolation_levels.serializable
}, async (t) => {
// 你的代码
});
你还可以使用 sequelize 构造函数中的一个参数来全局覆盖 isolationlevel 设置:
const { sequelize, transaction } = require('sequelize');
const sequelize = new sequelize('sqlite::memory:', {
isolationlevel: transaction.isolation_levels.serializable
});
mssql 注意 : 因为指定的 isolationlevel 被直接传递给 tedious ,所以没有记录 set isolation level 查询。
与其他 sequelize 方法一起使用
transaction
参数与大多数其他参数一起使用,通常是方法的第一个参数。
对于带有值的方法,例如 .create,.update()
等。transaction
应该传递给第二个参数。
如果不确定,请参考你使用的方法的 api 文档以确保正确。
示例:
await user.create({ name: 'foo bar' }, { transaction: t });
await user.findall({
where: {
name: 'foo bar'
},
transaction: t
});
aftercommit
hook
一个 transaction 对象允许跟踪它是否以及何时被提交。
可以将 aftercommit hook 添加到托管和非托管事务对象中:
// 托管事务:
await sequelize.transaction(async (t) => {
t.aftercommit(() => {
// 你的代码
});
});
// 非托管事务:
const t = await sequelize.transaction();
t.aftercommit(() => {
// 你的代码
});
await t.commit();
传递给 aftercommit
的回调可以是 async
。 在这种情况下:
- 对于托管交易:sequelize.transaction 调用将在完成之前等待它;
- 对于非托管交易:t.commit 调用将在完成之前等待它。
注意:
- 如果事务回滚,则不会引发 aftercommit hook;
- aftercommit hook 不修改事务的返回值(与大多数 hook 不同)
你可以将 aftercommit hook 与模型 hook 结合使用,以了解何时保存实例并在事务外部可用
user.aftersave((instance, options) => {
if (options.transaction) {
// 在事务中保存完成,
// 等待事务提交以通知侦听器实例已保存
options.transaction.aftercommit(() => /* 通知 */)
return;
}
// 在事务外保存完成,使调用者可以安全地获取更新的模型
// 通知
});
锁
可以使用锁执行 transaction 中的查询:
return user.findall({
limit: 1,
lock: true,
transaction: t1
});
事务中的查询可以跳过锁定的行:
return user.findall({
limit: 1,
lock: true,
skiplocked: true,
transaction: t2
});