Knex: 如何在一次运行中执行多个查询?

创建于 2014-04-29  ·  40评论  ·  资料来源: knex/knex

我面临一个问题,我想执行多个以“;”分隔的查询由单个 exec 执行,这可能吗?

我失败的测试代码如下所示:

      var query = '' +
        'UPDATE "records_raw" ' +
        'SET "title" = ? ' +
        'WHERE "id" = ?' +
        ';' +
        'UPDATE "records_raw" ' +
        'SET "title" = ? ' +
        'WHERE "id" = ?' +
        ';';

      var bindings = [
        "x", "1",
        "y", "2"
      ];

      knex.raw(query, bindings).exec(function(err, result) {
        assert.isNotError(err);
      });

错误:

Error(cannot insert multiple commands into a prepared statement, ...

有没有办法为此类查询禁用准备好的语句?

feature request

最有用的评论

任何更新 ?
现在可以进行 multiQuery 吗?
如果是这样,使用什么语法?

所有40条评论

不,您必须将它们作为两个语句执行。

嗯,这是一个有点限制的解决方案

您可以尝试在查询上调用toString()并执行它,但我真的不推荐它。

knex.raw(knex.raw(query, bindings) + '').exec(function(err, result) {
  assert.isNotError(err);
});

好吧,我会看看如何处理,基本上我只想关闭准备好的语句并保持具有查询字符串和绑定的能力,但我不知道是否可能,我没有找到与这些相关的文档。

反正我试过了

knex.raw(query, bindings) + ''

但它抛出 - Object 对象没有方法“克隆”。

如果你想尝试 0.6.0 分支,我知道它会在那里工作,当我完成一些事情的测试后应该会发布。

但是,为什么需要使用这样的单个字符串执行两个查询语句? 这不是违背了使用包的目的吗?

就个人而言,我需要一次制作多个INSERT ... ON DUPLICATE KEY UPDATE 。 我无法使用多个值进行插入,因为我想知道哪一行已更新以及哪一行已创建(检查受影响的行值)。

在我们发现 Knex 限制(这导致大部分时间基本上使用原始查询)之后,我一直在为 node.js 使用各种查询构建器。 最后我自己编写了 sql builder - QSql Demo 。 它尚未完成或生产就绪,但基本上总结了我的想法。

非常酷的演示,我一直计划在下一个版本之后添加类似的东西 - 好奇,你有没有尝试过 0.6 分支?

我想知道这里是否有任何合作空间,其中 QSQL 可能能够满足对更强大的查询构建器的需求,而 Knex 则处理连接池和平滑不同方言之间的问题。

无论如何,干得漂亮,我会好好看看的!

好吧,不,我们没有尝试 0.6,因为我们有很多代码在一个地方使用 knex,然后在其他地方执行一些原始查询。 经过一番研究,我决定用可以维护并且可以立即添加功能的东西替换整个堆栈。

嗯,我认为肯定有合作的空间,但是首先要稳定QSql需要一些时间。 我的意思是定义一个很好的 API 来匹配 99% 的需求和用例(如您所见,有些构造仍然有点丑陋)并实现适当的抽象以便可以添加其他后端。

也许能够传递一组查询,然后结果也是一个数组? 像这样简单的东西会很酷,可以使用 async.parallel 之类的东西来做到这一点

@niftylettuce Bluebird 已经带有

这不是可以通过标记来实现吗

    multipleStatements: true

在一个配置的连接块中:
例如

    connection: {
        host: 'localhost',
        user: 'superSecret',
        password: 'notGonnaTell',
         port: 8889,
        database: 'babelverse_core',
        multipleStatements: true
    }

好的,如果这里有人感兴趣 - 我正在即将进行的重构中研究一些新事物,以使这成为可能。

我想知道理想的 api 是什么……我们想要一个单链吗?

knex
  .update('records_raw')
  .set({title: x}).where({id: 1})
  .end()
  .update('records_raw')
  .set({title: y}).where({id: 2})
  .spread((resultA, resultB) => {

  })

或者更像:

knex.multiQuery([
  knex.update('records_raw').set({title: x}).where({id: 1})
  knex.update('records_raw').set({title: y}).where({id: 2})
]).spread((resultA, resultB) => {

})

还考虑通过在分号上拆分来使knex.raw案例自动工作的可能性。

在原始查询上拆分分号将是一个很好的开始。

我现在遇到了一个问题,我的删除在插入之前没有及时完成,所以我收到了重复的键错误。

我正在做类似的事情

knex("mytable")
.where({
  id: 32423
})
.del()
.then( ->
  knex("mytable")
  .insert()
 ..... 

你明白要点了..

del 没有及时完成。

@tgriesser我的投票是给 knex.multiQuery

@tgriesser关于在单个查询中执行多个语句的能力的任何更新? 我会选择:

.update('records_raw')
.set({title: x}).where({id: 1})
。结尾()
.update('records_raw')
.set({title: y}).where({id: 2})
.then(函数(结果){
结果[0] //结果1
结果[1] // 结果 2
})
.catch(函数(错误){
控制台日志(错误);
});

+1

为了对分号进行适当的拆分,我们需要使用特定于方言的解析器来解析整个原始查询。 具体来说,如果字符串包含; ,则应忽略它们。 下面是两个难以解析的有效 SQL 示例。

-- generic SQL
SELECT * FROM book WHERE title = 'Lord of the Rings; The Fellowship of the Ring';
-- MySQL specific
CREATE PROCEDURE dorepeat(p1 INT)
  BEGIN
    SET <strong i="9">@x</strong> = 0;
    REPEAT SET <strong i="10">@x</strong> = <strong i="11">@x</strong> + 1; UNTIL <strong i="12">@x</strong> > p1 END REPEAT;
  END

另请注意,在第一种情况下,我们最终会得到 2 个语句,因此代码需要考虑拆分的空结果。

老实说,总的来说,我认为这是一个坏主意。 SQL 注入的机会太多了,而没有正确拆分查询。

此外,在执行多条语句时,您几乎总是希望在同一个事务中进行。 Knex 已经用.transacting(function(transaction) { /* code */ })语法很好地解决了这个问题。 在不需要事务的罕见情况下,您可以简单地使用 bluebird 加入您的 knex 语句并获取结果。

因此,由于这些问题,我认为这不应该发生。

:-1:

此外,在执行多条语句时,您几乎总是希望在同一个事务中进行。 Knex 已经用.transacting(function(transaction) { /* code */ })语法很好地解决了这个问题。 在不需要事务的罕见情况下,您可以简单地使用 bluebird 加入您的 knex 语句并获取结果。

我可以根据经验告诉您,在某些情况下,这两种方法和将多个值作为一个命令批处理到服务器之间存在_巨大_性能差异。

我知道很难干净地实施,这就是为什么还没有完成的原因,但是这个问题极大地限制了我在项目中使用 knex。

我有一个类似的问题,并且能够解决它。 看这边。 https://github.com/tgriesser/knex/issues/1075

我没有看到此功能的任何用例。 很想关闭这张票,因为不会解决。 如果一个取决于在同一连接上运行的查询的顺序,那么您可能想要使用事务。

我看到的使这种“多重查询”用法成为我们想要的东西的一件事是,在某些情况下,它是潜在的性能优势。 将所有查询和结果与服务器进行通信所需的往返次数较少。

但这并不是说我有一个可衡量的用例。 仅从过去的经验/记忆中说出来......

@jurko-gospodnetic 当您没有连接池时可能会出现这种情况。 使用池发送多个查询基本上只是将数据放入已创建的 TCP 套接字。 此外,如果您的性能取决于 TCP 缓冲区没有被填满,那么 knex 对您来说已经太慢了 :) 在这种情况下,最好直接使用驱动程序。

@jurko-gospodnetic 于 5 月 20 日发表评论

我看到的使这种“多重查询”用法成为我们想要的东西的一件事是,在某些情况下,它是潜在的性能优势。 将所有查询和结果与服务器进行通信所需的往返次数较少。

由于这个特殊原因,我在企业级项目上使用node-mysql (mysqljs) 。 出于多种原因,我们希望将整个项目迁移到 knex.js

通过利用node-mysql (mysqljs)驱动程序的多语句查询功能,我利用此类操作的性能有了相当大的改进。

如果 Knex 不支持这一点,这对我来说可能是一个阻碍。 所以我会恢复这个线程来问这个问题。

Knex.js 现在是否支持一个语句中的多个查询?

@nicholaswmin你能更具体地说出“显着的性能改进”是什么意思吗? 如何以这种方式发送多个查询比仅通过同一连接发送多个查询更有效? 任何基准?

@elhigu没有基准,但马上我可以这样说:

对 DB 的一次调用(包含所有语句)消除了 server-db 网络往返。

另一方面,通过同一个连接发送多个查询也不是灵丹妙药。 此解决方案仅适用于非事务性流程,请参阅此问题

@nicholaswmin您是否发送了大量的小查询并且大部分被忽略或结果很小? 在那种情况下,我认为差异可能很明显,因为它允许驱动程序将多个查询打包到每个 TCP 数据包,否则每个 TCP 数据包将主要具有标头和非常少量的有效负载。

即使从网络流量中,也应该能够轻松衡量这种性能差异。

我不知道驱动程序是否支持从单独的connection.query调用向 DB 发送多个查询,而无需先等待先前调用的结果。 如果他们这样做,发送多个connection.query或 mysql 驱动程序支持的单个多查询之间的 TCP 流量应该没有太大区别。

@elhigu它们确实是小查询,但需要它们的结果,以便它们可以在事务/查询链的更下游使用。

有什么办法可以让我看到通过调试或类似选项发送的 TCP 数据包吗? 发送的不是查询本身,而是实际的数据包。

一个用例:

在我的系统中更新用户的数据时,我想计算该用户的审计跟踪(已更改的内容)。

// # PSEUDOCODE

// get current data of user
getUserData();
// set data of user
setUserData()
// get new data of user
getUserData()
// compute the audit trail by comparing the difference between before-set/after-set datums
computeAuditTrail(previousData, newData);

上述每个调用都会执行多个数据库调用,因此您可以想象这些是很多网络往返。

如#1806 所述,我可以通过对不需要顺序的查询使用Promise.all()来解决此问题(主要是getData/setData的数据库调用不需要顺序)。

这将适用于非事务性流,因为可以在池中的不同连接上发送查询。 一旦我使用事务来执行上述流程,它就会变慢(大约 4 倍),因为查询是在单个连接上发送的。

作为旁注,我使用的是 MSSQL。

Wireshark 是非常常见的跨平台工具,用于分析网络流量。

我不相信有任何方法可以通过 node.js 看到那个级别。

一种方法可能是使用 iptraf 或任何东西来测量发送/接收的数据量,当发送相同数量的查询并打包在一个查询中或单独传递给驱动程序时。

我很想看到multiQuery语法。

任何更新 ?
现在可以进行 multiQuery 吗?
如果是这样,使用什么语法?

var knex = require("knex");
var _ = require("lodash");
var Promise = require("bluebird");

var knex = require('knex')({
    client: 'sqlite3',
    connection: {
        filename: "./data.sqlite"
    }
});

// Create Schema
let createScript = `

CREATE TABLE Class ( 
    Id                   integer NOT NULL  ,
    Name                 varchar(100)   ,
    CONSTRAINT Pk_Classes_Id PRIMARY KEY ( Id )
 );

CREATE TABLE Material ( 
    Id                   integer NOT NULL  ,
    Description          varchar(500)   ,
    CONSTRAINT Pk_Material_Id PRIMARY KEY ( Id )
 )

-- ... and so on (leave off the last semi or remove it later) ... 

`;

let statementPromises = _.map(createScript.split(';'), (statement) => {
    return knex.raw(statement);
});

Promise.all(statementPromises).then(function() {
    console.log('Schema generated. Populating...');

     // ...

那只是一个循环,而不是将多个请求合二为一,而且速度很慢

@mscheffer问题是:

我面临一个问题,我想执行多个以“;”分隔的查询由单个 exec 执行,这可能吗?

我的回答解决了这个问题。 如果您有更快更好的解决方案,请提供。

@VictorioBerra;拆分在一般情况下不起作用,因为字符串文字和注释中可能存在; 。 如果您正在阅读数据转储,我会说最简单的方法是将 SQL 转储代码通过管道传输到 sqlite shell。 遗憾的是,这不适用于内存数据库,因为它们只是单个连接。

同样在您的情况下,您在模板字符串中创建 SQL 字符串然后拆分它,使用knex.schema.*构建器有效地相同,但更安全。

据我所知,没有办法实现在单个命令中为所有方言驱动程序运行多个查询的功能。 我认为是 mysql 和 oracledb 或 mssql,它们具有这种支持,并且使用 mysql 您仍然只能获得最后一个查询的结果作为响应(尽管这可能没问题)。

如果您使用多个查询修改数据,请记住使用事务,以便确保它们连续运行!

使用原始 SQL 的多查询执行
我遇到了类似的情况,我们的一个 knex 迁移需要连续多次运行CREATE DATABASE 。 我希望用分号分隔查询,但 knex 抛出了语法错误。 解决方案是在您的连接对象中包含"multipleStatements": true ,因此最终结果可能如下所示:

"host": "192.168.x.x",
"user": "userLogin",
"password": "userPassword",
"database": "schemaToUse",
"multipleStatements": true

之后,您可以像这样使用原始语句:

return knex.raw("CREATE DATABASE schema0; CREATE DATABASE schema1; CREATE DATABASE schema2")
   .then((result) => {
   })
   .catch((error) => {
   });

使用 KNEX 查询构建器执行多查询
如果您需要使用 knex 查询构建器而不是自己编写原始 SQL,那么您必须将Knex.QueryBuilder的结果转换为字符串,然后将多个查询连接在一起。 下面是一个例子:

// Suppose that we wanted to add 100 currency for all players in multiple games
const addCurrency = 100;
const updateCurrency = { currency: "currency + " + addCurrency };
const queries = [
   knex.table("game0.stats").update(updateCurrency),
   knex.table("game1.stats").update(updateCurrency),
   knex.table("game2.stats").update(updateCurrency),
];
const multiQuery = queries.join(";");
console.log(multiQuery);
return knex.raw(multiQuery)
   .then((result) => {
   })
   .catch((error) => {
   });

这也有助于在编写需要连续执行的冗余查询时减少人为错误。 再次使用这样的东西,我建议将它包装在交易中。

我认为这可以关闭,因为这主要受数据库驱动程序的限制。 至少需要适当的功能请求。

@AksharaKarikalan即使您执行多个子查询并在它们之间进行减法,您仍然只进行单个查询。 所以我删除了与这个问题无关的评论。 Stackoverflow 是 knex 使用请求的正确位置。

此页面是否有帮助?
0 / 5 - 0 等级