• knex事务


    1.数据库事务

    在执行SQL语句时,某些业务要求,一系列操作必须全部执行,而不能仅执行一部分。例如,一个转账操作:

    从id=1的账户给id=2的账户转账100元

    -- 第一步:将id=1的A账户余额减去100
    UPDATE accounts SET balance = balance - 100 WHERE id = 1;
    
    -- 第二步:将id=2的B账户余额加上100
    UPDATE accounts SET balance = balance + 100 WHERE id = 2;
    
    • 1
    • 2
    • 3
    • 4
    • 5

    这两条SQL语句必须全部执行,或者,由于某些原因,如果第一条语句成功,第二条语句失败,就必须全部撤销。

    这种把多条语句作为一个整体进行操作的功能,被称为数据库事务。数据库事务可以确保该事务范围内的所有操作都可以全部成功或者全部失败。如果事务失败,那么效果就和没有执行这些SQL一样,不会对数据库数据有任何改动。

    可见,数据库事务具有ACID这4个特性(参考地址):

    • A:Atomic,原子性,将所有SQL作为原子工作单元执行,要么全部执行,要么全部不执行;
    • C:Consistent,一致性,事务完成后,所有数据的状态都是一致的,即A账户只要减去了100,B账户则必定加上了100;
    • I:Isolation,隔离性,如果有多个事务并发执行,每个事务作出的修改必须与其他事务隔离;
    • D:Duration,持久性,即事务完成后,对数据库数据的修改被持久化存储。

    对于MySQL数据库,要手动把多条SQL语句作为一个事务执行,可以使用BEGIN开启一个事务,使用COMMIT提交一个事务,这种事务被称为显式事务,例如,把上述的转账操作作为一个显式事务:

    BEGIN;
    UPDATE accounts SET balance = balance - 100 WHERE id = 1;
    UPDATE accounts SET balance = balance + 100 WHERE id = 2;
    COMMIT;
    
    • 1
    • 2
    • 3
    • 4

    而对于nodejs使用knex框架,在不直接编写SQL语句的情况下,可以使用knex的数据库事务方法knex.transaction

    2.knex事务

    事务是关系数据库的一个重要功能,因为它们允许从故障中正确恢复并即使在系统发生故障时也能保持数据库的一致性。事务中的所有查询都在同一数据库连接上执行,并将整组查询作为单个工作单元运行。任何失败都意味着数据库会将在该连接上执行的任何查询回滚到事务前状态。

    knex中通过将处理函数传递到 knex.transaction 中进行处理,该处理函数接受一个参数——该参数是一个对象,并可以通过两种方式使用:

    1. 作为promise aware的kenx连接
    2. 作为一个对象传递到查询进行事务处理,并最终调用提交或回滚。

    两种方法的使用示例分别为:

    // Using trx as a query builder:
    knex.transaction(function(trx) {
    
      const books = [
        {title: 'Canterbury Tales'},
        {title: 'Moby Dick'},
        {title: 'Hamlet'}
      ];
    
      return trx
        .insert({name: 'Old Books'}, 'id')
        .into('catalogues')
        .then(function(ids) {
          books.forEach((book) => book.catalogue_id = ids[0]);
          return trx('books').insert(books);
        });
    })
    .then(function(inserts) {
      console.log(inserts.length + ' new books saved.');
    })
    .catch(function(error) {
      // If we get here, that means that 
      // neither the 'Old Books' catalogues insert,
      // nor any of the books inserts will have taken place.
      console.error(error);
    });
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26

    或通过如下方式使用:

    // Using trx as a transaction object:
    knex.transaction(function(trx) {
    
      const books = [
        {title: 'Canterbury Tales'},
        {title: 'Moby Dick'},
        {title: 'Hamlet'}
      ];
    
      knex.insert({name: 'Old Books'}, 'id')
        .into('catalogues')
        .transacting(trx)
        .then(function(ids) {
          books.forEach((book) => book.catalogue_id = ids[0]);
          return knex('books').insert(books).transacting(trx);
        })
        .then(trx.commit)
        .catch(trx.rollback);
    })
    .then(function(inserts) {
      console.log(inserts.length + ' new books saved.');
    })
    .catch(function(error) {
      // If we get here, that means that 
      // neither the 'Old Books' catalogues insert,
      // nor any of the books inserts will have taken place.
      console.error(error);
    });
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28

    上述示例都可以通下列async/await方法来使用:

    try {
      await knex.transaction(async trx => {
    
        const books = [
          {title: 'Canterbury Tales'},
          {title: 'Moby Dick'},
          {title: 'Hamlet'}
        ];
        
        const ids = await trx('catalogues')
          .insert({
            name: 'Old Books'
          }, 'id')
    
        books.forEach((book) => book.catalogue_id = ids[0])
        const inserts = await trx('books').insert(books)
        
        console.log(inserts.length + ' new books saved.')
      })
    } catch (error) {
      // If we get here, that means that neither the 'Old Books' catalogues insert,
      // nor any of the books inserts will have taken place.
      console.error(error);
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24

    或者通过下列方式使用:

    // Using trx as a transaction object:
    const trx = await knex.transaction();
    
    const books = [
      {title: 'Canterbury Tales'},
      {title: 'Moby Dick'},
      {title: 'Hamlet'}
    ];
    
    trx('catalogues')
      .insert({name: 'Old Books'}, 'id')
      .then(function(ids) {
        books.forEach((book) => book.catalogue_id = ids[0]);
        return trx('books').insert(books);
      })
      .then(trx.commit)
      .catch(trx.rollback);
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17

    3.knex事务模型

    如果您需要为事务指定隔离级别,可以使用配置参数isolationLevel(oracle 和 sqlite 不支持),选项包括:read uncommitted, read committed, repeatable read, snapshot (mssql only), serializable

    使用方法如下:

    // Simple read skew example
    const isolationLevel = 'read committed';
    const trx = await knex.transaction({isolationLevel});
    const result1 = await trx(tableName).select();
    await knex(tableName).insert({ id: 1, value: 1 });
    const result2 = await trx(tableName).select();
    await trx.commit();
    // result1 may or may not deep equal result2 depending on isolation level
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8

    你也可以通过readOnly参数,将事务模型设置为只读(当前仅支持mysql, postgres, and redshift),使用方法如下:

    const trx = await knex.transaction({ readOnly: true });
    // 💥 Cannot `INSERT` while inside a `READ ONLY` transaction
    const result = await trx(tableName).insert({ id: 1, foo: 'bar' });
    
    • 1
    • 2
    • 3
  • 相关阅读:
    P6773 [NOI2020] 命运(dp、线段树合并)
    Less is more VS 精一 [生活感悟]
    华为机试真题 Java 实现【补种未成活胡杨】
    2022年11月7日--11月13日(ue4 tf视频教程+cesium for ue源码抄写)
    无法信任 ASP.NET Core 中的本地 HTTPS 证书
    Nginx跨域解决方案
    Tornado框架路由系统介绍及(IOloop.current().start())启动源码分析
    iPhone删除所有照片的高效三部曲
    芯科科技第二代平台的所有蓝牙片上系统均可支持蓝牙技术联盟的新功能和新标准
    Qt页面布局
  • 原文地址:https://blog.csdn.net/qq_29517595/article/details/134462714