Knex: Как выполнить несколько запросов за один прогон?

Созданный на 29 апр. 2014  ·  40Комментарии  ·  Источник: knex/knex

Я столкнулся с проблемой, когда я хотел бы выполнить несколько запросов, разделенных ';' одним исполнителем, возможно ли это?

Мой тестовый код, который не работает, выглядит так:

      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) + ''

но он выдает - объект объекта не имеет метода clone.

Если вы хотите попробовать ветку 0.6.0, я знаю, что она там будет работать, ее следует выпустить, как только я закончу тесты для некоторых вещей.

Но зачем вам нужно выполнять два оператора запроса, используя одну такую ​​строку? Разве это не противоречит цели использования пакета?

Лично мне нужно сделать сразу несколько INSERT ... ON DUPLICATE KEY UPDATE . Я не могу выполнить вставку с несколькими значениями, потому что я хочу знать, какая строка была обновлена ​​и какая строка была создана (проверка значения затронутой строки).

Я играл с различными построителями запросов для node.js после того, как мы обнаружили ограничения Knex (которые в основном приводили к использованию сырых запросов большую часть времени). В конце концов, я просто написал сам sql builder -

Очень крутая демонстрация, я планировал добавить что-то подобное после следующего выпуска - любопытно, вы попробовали ветку 0.6?

Интересно, есть ли здесь место для сотрудничества, где QSQL может удовлетворить потребность в более надежном построителе запросов, в то время как Knex занимается объединением соединений и сглаживанием бородавок между разными диалектами.

В любом случае, хорошая работа, буду просматривать!

Ну нет, мы не пробовали 0.6, так как у нас было много кода, использующего knex в одном месте, а затем выполнявшего несколько необработанных запросов в другом месте. После некоторого исследования я решил заменить весь стек чем-то, что можно поддерживать и где функции могут быть добавлены мгновенно.

Что ж, я думаю, что определенно есть место для сотрудничества, однако сначала потребуется некоторое время, чтобы стабилизировать QSql. Я хочу определить хороший API, который соответствует 99% потребностей и вариантов использования (как вы можете видеть, некоторые конструкции все еще немного уродливы) и реализовать правильную абстракцию, чтобы можно было добавить другие серверные модули.

может быть, возможность передать массив запросов, а результаты тоже будут массивом? что-то такое простое было бы круто, можно было бы использовать что-то вроде async.parallel, чтобы сделать это

@niftylettuce Bluebird уже поставляется с функцией "async.parallel" из коробки. Но проблема не в этом. Нам действительно нужен способ выполнять запросы, разделенные запятыми, за один прогон.

Разве это невозможно, просто отметив

    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()
 ..... 

вы уловили суть ..

Дель не завершается вовремя.

@tgriesser мой голос за knex.multiQuery

@tgriesser есть ли обновления о возможности выполнять несколько операторов в одном запросе? Я бы предпочел:

.update ('records_raw')
.set ({title: x}). где ({id: 1})
.конец()
.update ('records_raw')
.set ({title: y}). где ({id: 2})
.then (функция (результат) {
result [0] // результат 1
result [1] // результат 2
})
.catch (function (err) {
console.log (ошибка);
});

+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 прокомментировал 20 мая

Единственное, что, как я вижу, превращает использование «множественных запросов» в то, что мы хотели бы иметь, - это то, что в некоторых случаях это потенциальный выигрыш в производительности. Для передачи всех запросов и результатов на сервер и с сервера требуется меньше циклов обмена.

По этой конкретной причине я использую node-mysql (mysqljs) в проекте корпоративного уровня. Мы планируем перенести весь проект на knex.js по множеству причин.

В таких операциях наблюдается значительное улучшение производительности, которое я использую, используя функцию запросов с node-mysql (mysqljs) .

Если Knex не поддержит это, это может стать для меня остановкой. Так что я оживлю эту ветку, чтобы спросить именно об этом.

Поддерживаются ли теперь в Knex.js множественные запросы в одном операторе?

@nicholaswmin, можете ли вы более конкретно сказать, что вы имеете в виду под "значительным улучшением производительности"? Как отправка нескольких запросов таким способом значительно эффективнее, чем просто отправка нескольких запросов через одно и то же соединение? Какие-нибудь тесты?

@elhigu Нет тестов, но сразу могу сказать следующее:

Единственный вызов БД (заполненный всеми операторами) устраняет круговые обходы сети server-db.

С другой стороны, отправка нескольких запросов через одно и то же соединение - не панацея. Это решение будет работать только с нетранзакционными потоками, см. Эту проблему

@nicholaswmin Вы отправляете огромное количество небольших запросов и в основном игнорируете или получаете небольшие результаты? В этом случае, я полагаю, разница может быть заметной, поскольку она позволяет драйверу упаковывать несколько запросов к каждому TCP-пакету, тогда как в противном случае каждый TCP-пакет имел бы в основном заголовки и действительно небольшой объем полезной нагрузки.

Такую разницу в производительности можно легко измерить даже по объему сетевого трафика.

Я не знаю, поддерживают ли драйверы отправку нескольких запросов из отдельных вызовов connection.query в БД без предварительного ожидания результатов предыдущих вызовов. Если они это сделают, не должно быть большой разницы в TCP-трафике между отправкой нескольких connection.query или одним мультизапросом, который поддерживается драйвером mysql.

@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.

Одним из способов может быть использование 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 вопрос был:

Я столкнулся с проблемой, когда я хотел бы выполнить несколько запросов, разделенных ';' одним исполнителем, возможно ли это?

Что решает мой ответ. Если у вас есть более быстрое и лучшее решение, предоставьте его.

@VictorioBerra Разделение на ; в общем случае не работает, поскольку внутри строковых литералов и комментариев может быть ; . Если вы читаете дамп данных, я бы сказал, что самый простой способ - передать код дампа SQL в оболочку sqlite. К сожалению, это не работает для баз данных в памяти, потому что это всего лишь одно соединение.

Также в вашем случае, когда вы создаете строки SQL во внутренней строке шаблона, а затем разделяете ее, используя конструкторы knex.schema.* фактически то же самое, но более безопасно.

Насколько мне известно, невозможно реализовать эту функцию выполнения нескольких запросов в одной команде для всех драйверов диалектов. Я думаю, что это были mysql и oracledb или mssql, которые имели такую ​​поддержку, и с mysql вы по-прежнему получаете только результат последнего запроса в качестве ответа (что может быть нормально).

Не забудьте использовать транзакции, если вы изменяете данные с помощью нескольких запросов, чтобы вы могли убедиться, что они выполняются последовательно!

ВЫПОЛНЕНИЕ НЕСКОЛЬКИХ ЗАПРОСОВ С ИСПОЛЬЗОВАНИЕМ RAW 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 QUERY BUILDER
Если вам нужно использовать построитель запросов 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) => {
   });

Это также помогает уменьшить количество человеческих ошибок при написании избыточных запросов, которые необходимо выполнять последовательно. Опять же, с чем-то вроде этого я бы предложил заключить это в транзакцию.

Я думаю, что это можно закрыть, так как это в основном ограничено драйверами db. По крайней мере, необходим правильный запрос функции.

@AksharaKarikalan, даже если вы выполняете несколько подзапросов и вычитаете между ними, вы все равно делаете только один запрос. Поэтому я удалил комментарий, не имеющий отношения к этой проблеме. Stackoverflow - правильное место для запросов на использование knex.

Была ли эта страница полезной?
0 / 5 - 0 рейтинги