事物
事物
数据库事务
是访问并可能操作各种数据项的一个数据库操作序列, 这些操作要么全部执行、要么全部不执行,是一个不可分割的工作单位。 事务由事务开始与事务结束之间执行的全部数据库操作组成。
Sequelize
本身是支持事物的,但默认不使用,可以通过配置使用事务,操作数据库库。
目前,Sequelize
框架支持两种使用事务的方式:
非托管事务
:提交和回滚事务应由用户手动完成(通过调用适当的Sequelize
方法)。托管事务
:如果抛出任何错误,Sequelize
将自动回滚事务,否则提交事务。另外,如果启用了CLS
(连续本地存储),则事务回调内的所有查询都将自动接收事务对象。
可以理解托管事务就是自动提交事务,出现异常后自动回滚,保持数据库一致性。非托管事务则需要手动处理提交、异常处理、回滚这些流程。
事务对象
在使用Sequelize
的事务是,一般需要先获取对象,例如:
// 获取事务
const t = await sequelize.transaction()
将实例上的SQL操作绑定到事务上,处理完业务逻辑后,使用commit()
函数提交事务,例如:
// 事务提交
await t.commit()
当实例操作出现异常,事物执行失败时,被try...catch
捕获到,为保证数据与执行前一致,需要通过rollback()
函数进行回滚,例如:
// 事务回滚
await t.rollback()
非托管事务
查看sequelize.transaction()
API源码:
获取到transaction事物对象后,可以根据提供的commit()
、rollback()
方法手动操作事务。
commit()
: 手动提交事务rollback()
: 手动终止事务,并回滚
同时,LOCK()
函数还支持当前事务锁的一些状态,因为本质上开展事务,是基于数据库事物隔离级别来设置锁的等级。
这里总结下使用非托管事务
、手动提交事务
的通用代码模版:
// 获取事务对象
const t = await sequelize.transaction()
/**
* 异常捕获事务操作
*/
try {
// 将实例操作绑定在事务上执行,一般为多个操作
await User.save({
name: '微信公众号:储凡',
email: 'fairy_vip@2925.com'
}, { transaction: t })
await Space.save({
name: '测试空间',
}, { transaction: t })
// 手动提交事务
await t.commit()
}
catch (error) {
// 出现异常贼说明事务执行失败,则需要手动回滚,对有影响的数据进行处理
await t.rollback()
}
非托管事务方法要求在必要时手动提交和回滚事务。
托管事务
Sequelize
框架还支持托管事务
,与非托管事务
不同,托管事务
能够自动提交事务、自动进行事务回滚。
同样是sequelize.transaction()
方法,查看源码:
可以通过传入回调来启动托管事务。这里的autoCallback
通常是async
函数,即可以在回调函数中处理同步流程。
这里总结下使用托管事务
、自动提交事务
的通用代码模版:
try {
const result = await sequelize.transaction(async (t) => {
await User.save({
name: '微信公众号:储凡',
email: 'fairy_vip@2925.com'
}, { transaction: t })
await Space.save({
name: '测试空间',
}, { transaction: t })
// 返回回调函数中业务逻辑的结果
return 'transaction is OK!'
})
// 当流程执行到这里时,事务操作就已经被成功提交
// 这里的result变量拿到的就是回调函数返回值。代表事物执行成功
}
catch (error) {
// 当流程执行到这个catch时,说明回调函数中的业务执行事务失败,
// 这里的error变量会表明错误原因,同时Sequelize也会自动对事务进行回滚
}
相比于非托管事务
,托管事务
模板中没有手动执行commit()
、rollback()
函数, 这些Sequelize
框架都能自动执行。
对于托管事务,处理流程一般如下:
Sequelize
会自动开启事务,并获取到事务对象t
。Sequelize
会执行回调函数中的逻辑,并且回调函数的参数就是前面获取的事务对象t
- 如果提供给
transaction
函数的回调函数出现异常,则Sequelize
会自动回滚事务 - 如果回调函数业务执行成功,
Sequelize
会自动提交事务,确保一致性操作
使用托管事务时,不需要手动提交事务、回滚事务,如果回调函数中的业务逻辑执行成功后,仍然需要回滚事务,这是只需要在回调函数中 手动抛错即可。这里的本质是transaction方法对回调函数异常捕获,对于托管事务如果出现异常,框架层面会自动回滚事务。例如:
/**
* 托管事务的事务回滚
*/
await sequelize.transaction(async (t) => {
await User.save({
name: '微信公众号:储凡',
email: 'fairy_vip@2925.com'
}, { transaction: t })
// 手动抛错,框架自动回滚事务
throw new Error()
})
并发事务
注意: SQLite数据库 不支持同时处理多个事务
Sequelize
框架还支持在一系列查询中包含并发事务,或者将其中一些事物排除在任何事物外。 在操作模型时,使用transaction
字段来配置具体使用那个事务。例如:
/**
* 并发事务
*/
sequelize.transaction((t1) => {
return sequelize.transaction((t2) => {
// 并发执行事务
return Promise.all([
User.save({
name: '微信公众号:储凡',
email: 'fairy_vip@2925.com'
}, { transaction: null }),
User.save({ name: '事务测试1' }, { transaction: t1 }),
// 开启CLS配置后,未配置事务参数则使用就近的t2事务
User.save({ name: '事务测试2' })
])
})
})
利用Promise.all
对多个异步函数并发操作,就响应时间而言选择处理时间最长的函数作为该Promise.all
的响应时间。 transaction
字段可以配置具体使用那个事务。
隔离级别
对于非托管事务、托管事务,Sequelize框架给出的API是:
- 对于非托管事务,只需使用
sequelize.transaction(options)
- 对于托管事务,请使用
sequelize.transaction(options, callback)
这里的options
是可选参数,查看源码:
当调用transaction
函数,事务被创建时,options
参数就被提供了。
默认情况下,Sequelize
使用数据库的隔离级别。如果您想使用不同的隔离级别,可以在启动事务时,设置隔离级别
,例如:
/**
* 设置事务隔离级别
*/
const { Transaction } = require('sequelize')
await sequelize.transaction({
// 设置隔离级别
isolationLevel: Transaction.ISOLATION_LEVELS.SERIALIZABLE
}, async (t) => {
// 业务代码
})
四种事务隔离级别
枚举如下:
/**
* 事务隔离级别枚举
*/
enum ISOLATION_LEVELS {
READ_UNCOMMITTED = 'READ UNCOMMITTED',
READ_COMMITTED = 'READ COMMITTED',
REPEATABLE_READ = 'REPEATABLE READ',
SERIALIZABLE = 'SERIALIZABLE',
}
除了在使用事务时设置隔离级别外,还可以在创建sequelize对象时,设置全局的事务隔离级别,例如:
/**
* 设置全局的事务隔离级别
*/
const { Sequelize, Transaction } = require('sequelize')
const sequelize = new Sequelize('postgres://user:pass@142vip.cn:5432/142vip', {
isolationLevel: Transaction.ISOLATION_LEVELS.SERIALIZABLE
})
事务锁
Sequelize
框架支持在执行事务时,使用锁或者跳过锁执行,例如:
// 可以使用锁来执行事务
User.findAll({
limit: 1,
lock: true,
transaction: t1
})
// 事务中的查询可以跳过锁定的行
User.findAll({
limit: 1,
lock: true,
skipLocked: true,
transaction: t2
})
钩子函数
Sequelize
框架对事务提供钩子函数afterCommit()
,不论托管事务还是非托管事务, 都可以利用该钩子函数来确认事务是否提交以及什么时候提交,例如:
/**
* 针对托管事务
*/
await sequelize.transaction(async (t) => {
// 监听钩子函数
t.afterCommit(() => {
// 业务逻辑
})
})
/**
* 针对非托管事务
*/
const t = await sequelize.transaction()
// 监听钩子函数
t.afterCommit(() => {
// 业务逻辑
})
await t.commit()
从代码中可以看出:
- 当事务出现异常时被回滚,贼说明事务执行不成功,不会触发
afterCommit()
函数 afterCommit()
钩子函数的执行,不会影响到事务的返回值,可以理解为触发第三方业务流程
到这里,Sequelize框架中事务部分就介绍完了, 但这里只是介绍用法,事务还有很多零碎的知识点,例如:事务隔离级别分类、存储引擎等,可自行结合不用的数据库学习。