跳至主要內容

事物

微信公众号:储凡About 21 min

事物

数据库事务是访问并可能操作各种数据项的一个数据库操作序列, 这些操作要么全部执行、要么全部不执行,是一个不可分割的工作单位。 事务由事务开始与事务结束之间执行的全部数据库操作组成。

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事物API方法
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()方法,查看源码:

托管事务API
托管事务API

可以通过传入回调来启动托管事务。这里的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框架都能自动执行。

对于托管事务,处理流程一般如下:

  1. Sequelize会自动开启事务,并获取到事务对象t
  2. Sequelize会执行回调函数中的逻辑,并且回调函数的参数就是前面获取的事务对象t
  3. 如果提供给transaction函数的回调函数出现异常,则Sequelize会自动回滚事务
  4. 如果回调函数业务执行成功,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参数
transaction函数的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框架中事务部分就介绍完了, 但这里只是介绍用法,事务还有很多零碎的知识点,例如:事务隔离级别分类、存储引擎等,可自行结合不用的数据库学习。