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
中不需要async
或await
。只需返回knex.update
。 Promise.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 中的批量更新的主要内容,如果未能解决你的问题,请参考以下文章