knex 中的批量更新

Posted

技术标签:

【中文标题】knex 中的批量更新【英文标题】:Batch update in knex 【发布时间】:2017-03-25 10:18:35 【问题描述】:

我想使用Knex.js执行批量更新

例如:

'UPDATE foo SET [theValues] WHERE idFoo = 1'
'UPDATE foo SET [theValues] WHERE idFoo = 2'

有价值观:

 name: "FooName1", checked: true  // to `idFoo = 1`
 name: "FooName2", checked: false  // to `idFoo = 2`

我之前使用的是node-mysql,它允许多个语句。在使用它时,我只是构建了一个多语句查询字符串,并在一次运行中通过线路发送它。

我不确定如何使用 Knex 实现相同的效果。我可以将batchInsert 视为我可以使用的API 方法,但就batchUpdate 而言,什么都没有。

注意:

我可以进行异步迭代并分别更新每一行。这很糟糕,因为这意味着从服务器到数据库会有很多往返

我可以使用 Knex 的 raw() 东西,并且可能做一些类似于我对 node-mysql 所做的事情。然而,这违背了作为 DB 抽象层的整个 knex 目的(它引入了强 DB 耦合)

所以我想使用“knex-y”来做到这一点。

欢迎任何想法。

【问题讨论】:

为什么不做并行异步操作呢?它会消耗更多的 RAM,因为您要为每次更新注册回调,但从网卡的角度来看,它看起来就像一个大更新流。 @slebetman 听起来不错 - 我会试一试,如果看起来不错,我会发布答案 @slebetman 1.5 年后重新审视这个问题。那时,我采用了您的解决方案,而不是我接受的解决方案,并且效果很好。如果你来写一个答案,我会接受你的。 【参考方案1】:

假设您有一组给定表的有效键/值:

// abstract transactional batch update
function batchUpdate(table, collection) 
  return knex.transaction(trx => 
    const queries = collection.map(tuple =>
      knex(table)
        .where('id', tuple.id)
        .update(tuple)
        .transacting(trx)
    );
    return Promise.all(queries)
      .then(trx.commit)    
      .catch(trx.rollback);
  );

叫它

batchUpdate('user', [...]);

不幸的是,您是否受制于非常规的列名?不用担心,我得到了你的家人:

function batchUpdate(options, collection) 
  return knex.transaction(trx => 
    const queries = collection.map(tuple =>
      knex(options.table)
        .where(options.column, tuple[options.column])
        .update(tuple)
        .transacting(trx)
    );
    return Promise.all(queries)
      .then(trx.commit)    
      .catch(trx.rollback);
  );

叫它

batchUpdate( table: 'user', column: 'user_id' , [...]);

现代语法版本:

const batchUpdate( table, column , collection) => 
  const trx = await knex.transaction(); 
  try 
    await Promise.all(collection.map(tuple => 
      knex(table)
        .where(column, tuple[column])
        .update(tuple)
        .transacting(trx)
      )
    );
    await trx.commit();
   catch (error) 
    await trx.rollback();
  

【讨论】:

这是一个不错的解决方案,只是好奇,如果我想在提交后返回所有 id 该如何自定义 在下面添加了我的 sn-p 以返回 ID 值数组。希望对您有所帮助!【参考方案2】:

该解决方案对我来说非常有用!我只包含一个 ID 参数,以使其在具有自定义 ID 标签的表中动态化。 Chenhai,这是我的 sn-p,包括为交易返回单个 ID 值数组的方法:

function batchUpdate(table, id, collection) 
   return knex.transaction((trx) => 
       const queries = collection.map(async (tuple) => 
       const [tupleId] = await knex(table)
        .where(`$id`, tuple[id])
        .update(tuple)
        .transacting(trx)
        .returning(id);

       return tupleId;
   );

   return Promise.all(queries).then(trx.commit).catch(trx.rollback);
   );

你可以使用 response = await batchUpdate("table_name", "custom_table_id", [array of rows to update]) 获取返回的 ID 数组。

【讨论】:

您的collection.map 中不需要asyncawait 。只需返回knex.updatePromise.all 接受一个 Promise 数组。【参考方案3】:

我需要在事务中执行批量更新(我不希望部分更新以防出现问题)。 我已经通过下一个方法解决了:

// I wrap knex as 'connection'
return connection.transaction(trx => 
    const queries = [];
    users.forEach(user => 
        const query = connection('users')
            .where('id', user.id)
            .update(
                lastActivity: user.lastActivity,
                points: user.points,
            )
            .transacting(trx); // This makes every update be in the same transaction
        queries.push(query);
    );

    Promise.all(queries) // Once every query is written
        .then(trx.commit) // We try to execute all of them
        .catch(trx.rollback); // And rollback in case any of them goes wrong
);

【讨论】:

当使用 mssql 时,如果你尝试 Promise.all(queries),乏味的(NodeJS mssql 库)会报错Can't acquire connection for the request. There is another request in progress.,Knex 会跟进Can't rollback transaction. There is a request in progress.。这是因为 Promise.all() 并行执行所有查询。为了解决这个问题,所有的承诺都必须一个接一个地解决。您可以使用代码in this *** question 来做到这一点。 Also see this bug report from Tedious 这个答案对我帮助很大,但我在数据未写入数据库时​​遇到了困难;原来我需要用箭头函数替换.then(trx.commit).catch(trx.rollback).then(() => trx.commit).catch(() => trx.rollback) @RedShift,我收到错误 Can't acquire connection for the request. There is another request in progress。检查了您提供的链接。你能提供简单的例子吗? 用户未定义..您从哪里获得用户?【参考方案4】:

您很清楚每种方法的优缺点。我会推荐一个原始查询,通过几个异步更新批量更新。是的,您可以并行运行它们,但是您的瓶颈变成了数据库运行每次更新所需的时间。详情可见here。

以下是使用 knex.raw 进行批量更新插入的示例。假设 records 是一个对象数组(我们要更新的每一行都有一个 obj),其值是与您要更新的数据库中的列对齐的属性名称:

var knex = require('knex'),
    _ = require('underscore');

function bulkUpdate (records) 
      var updateQuery = [
          'INSERT INTO mytable (primaryKeyCol, col2, colN) VALUES',
          _.map(records, () => '(?)').join(','),
          'ON DUPLICATE KEY UPDATE',
          'col2 = VALUES(col2),',
          'colN = VALUES(colN)'
      ].join(' '),

      vals = [];

      _(records).map(record => 
          vals.push(_(record).values());
      );

      return knex.raw(updateQuery, vals);
 

This 回答很好地解释了两种方法之间的运行时关系。

编辑:

有人要求我展示records 在这个例子中的样子。

var records = [
   primaryKeyCol: 123, col2: 'foo', colN: 'bar' ,
   // some other record, same props 
];

请注意,如果您的 record 具有比您在查询中指定的属性更多的属性,则不能这样做:

  _(records).map(record => 
      vals.push(_(record).values());
  );

因为您将向每条记录的查询传递太多值,并且 knex 将无法将每条记录的属性值与查询中的 ? 字符匹配。相反,您需要将要插入的每条记录上的值显式推送到数组中,如下所示:

  // assume a record has additional property `type` that you dont want to
  // insert into the database
  // example:  primaryKeyCol: 123, col2: 'foo', colN: 'bar', type: 'baz' 
  _(records).map(record => 
      vals.push(record.primaryKeyCol);
      vals.push(record.col2);
      vals.push(record.colN);
  );

上述显式引用的重复方式较少,但这只是一个示例。希望这会有所帮助!

【讨论】:

您好,您能提供一个“记录”的示例结构吗?只是为了确保我做的是正确的谢谢! @IamL 在编辑中回答了您的问题。让我知道这是否对您有帮助。 :) 如果是,请投票,如果不是,我可以进一步解释! 如果我有一个不同的例子 primaryKeyCol: 123, col2: status: 'available, offline: false, etc .... props' 并且 primaryKeyCol 必须是我表中的主键,我不能更新电子邮件或我知道它是唯一的但它没有唯一约束。我怎么更新这样的东西 @PatrickMotard 当我尝试使用您的代码 Error: Expected 1191 bindings, saw 397 at replaceRawArrBindings 执行此操作时出现此错误

以上是关于knex 中的批量更新的主要内容,如果未能解决你的问题,请参考以下文章

PLSQL中批量更新数据

为啥批量插入/更新更快?批量更新如何工作?

批量更新后更新托管对象上下文中的托管对象

C# 中的批量更新

Jooq 使用 MySql 中的记录批量更新

批量更新zabbix中的主机名