Как правильно использовать модуль postgresql node.js?

95

Я пишу приложение node.js на Heroku и использую модуль pg . Я не могу найти «правильный» способ получить объект клиента для каждого запроса, который мне нужен для запроса базы данных.

В документации используется такой код:

pg.connect(conString, function(err, client) {
  // Use the client to do things here
});

Но ведь вам не нужно вызывать pg.connectвнутри каждой функции, которая использует базу данных, верно? Я видел другой код, который делает это:

var conString = process.env.DATABASE_URL || "tcp://postgres:1234@localhost/postgres";
var client = new pg.Client(conString);
client.connect();
// client is a global so you can use it anywhere now

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

Филип
источник

Ответы:

158

Я автор node-postgres . Во-первых, я прошу прощения, что в документации не указан правильный вариант: это моя вина. Постараюсь улучшить. Я только что написал Gist, чтобы объяснить это, потому что разговор стал слишком длинным для Twitter.

Использование pg.connect- это путь в веб-среде.

Сервер PostgreSQL может обрабатывать только 1 запрос за раз за одно соединение. Это означает, что если у вас есть 1 глобальный new pg.Client()сервер, подключенный к вашему бэкэнду, все ваше приложение будет узким местом в зависимости от того, насколько быстро postgres может отвечать на запросы. Он буквально выстраивает все в очередь, ставя каждый запрос в очередь. Да, это асинхронно, и это нормально ... но не лучше ли умножить пропускную способность в 10 раз? Используйте, чтобы pg.connect установить что- pg.defaults.poolSizeнибудь вменяемое (мы делаем 25-100, пока не уверены в правильности числа).

new pg.Clientкогда вы знаете, что делаете. Когда вам по какой-то причине нужен один-единственный долгоживущий клиент или вам нужно очень тщательно контролировать жизненный цикл. Хороший пример этого - использование LISTEN/NOTIFY. Слушающий клиент должен быть поблизости и подключен, а не использоваться совместно, чтобы он мог правильно обрабатывать NOTIFYсообщения. Другим примером может быть открытие одноразового клиента для уничтожения зависшего или в сценариях командной строки.

Одна очень полезная вещь - централизовать весь доступ к вашей базе данных в вашем приложении в один файл. Не мешайте pg.connectзвонкам или новым клиентам. Пусть такой файл db.jsвыглядит примерно так:

module.exports = {
   query: function(text, values, cb) {
      pg.connect(function(err, client, done) {
        client.query(text, values, function(err, result) {
          done();
          cb(err, result);
        })
      });
   }
}

Таким образом, вы можете изменить свою реализацию pg.connectна настраиваемый пул клиентов или что-то еще, и вам нужно будет изменить что-то только в одном месте.

Взгляните на модуль node-pg-query, который делает именно это.

Brianc
источник
2
Извините, я новичок в СУБД и все еще не могу понять это, но почему мы не хотим "засорять" вызовы pg.connect? Это для простоты или из соображений производительности? Например, я вызываю pg.connect один раз на каждом из маршрутов, имеющихся в моем базовом приложении (все с одним и тем же conString). Это нормально? Интуитивно кажется, что он создает новое соединение с той же базой данных всякий раз, когда я его вызываю (чего я не хочу), но использует ли он объединенные соединения внутри? Спасибо.
user1164937
Потрясающие. Почему вы используете одно соединение для каждого запроса, а не одно для каждого запроса? Я искал подходящий способ совместного использования соединения для нескольких запросов в запросе и рассматривал res.locals, прежде чем найти здесь ваш ответ.
Джо Лапп
2
Ой, погоди. Похоже, ваше решение здесь не поддерживает транзакции.
Joe Lapp
Это должно быть постоянно связано с гитхабом.
Райан Уиллис,
1
Обратите внимание, что pg.connect был удален после v7 node-postgres aka pg. См. Stackoverflow.com/questions/45174120/pg-connect-not-a-function
Colin D
24

Я автор pg-prom , который упрощает использование node-postgres с помощью обещаний.

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

Индивидуальный запрос в pg-обещании сводится к тому, что имеет отношение к вашей бизнес-логике:

db.any('SELECT * FROM users WHERE status = $1', ['active'])
    .then(data => {
        console.log('DATA:', data);
    })
    .catch(error => {
        console.log('ERROR:', error);
    });

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

const pgp = require('pg-promise')(/*options*/);

const cn = {
    host: 'localhost', // server name or IP address;
    port: 5432,
    database: 'myDatabase',
    user: 'myUser',
    password: 'myPassword'
};
// alternative:
// const cn = 'postgres://username:password@host:port/database';

const db = pgp(cn); // database instance;

Вы можете найти много других примеров в учебном пособии « Обучение на примерах» или на домашней странице проекта .

Виталий-Т
источник
Привет, Heroku принимает только SSL-соединения. В pgэтом указывается pg.defaults.ssl = true;. Как это сделать pg-promise?
ocram
@ocram github.com/vitaly-t/pg-promise/wiki/… , или вы можете указать SSL в параметрах подключения: github.com/vitaly-t/pg-promise/wiki/Connection-Syntax
vitaly-t
Я новичок в большинстве из них: javascript, promises, postgres и т. Д., И это именно то, что мне нужно. Спасибо!!
Райан Родемойер,
1
@ocram Я только что получил это благодаря работеpgp.pg.defaults.ssl = true;
CharlieC
создаст ли это несколько подключений для автоматического повышения пропускной способности postgres, когда мы передадим в postgres несколько запросов?
sundar
5

Бассейн - это то, что вам нужно.

const { Pool } = require('pg');

    const pool = new Pool({
      connectionString: DATABASE_URL,
      ssl: false,
      max: 20,
      idleTimeoutMillis: 30000,
      connectionTimeoutMillis: 2000,
    });
    module.exports = {
        query: (text, params) => pool.query(text, params)
      }

его можно использовать как db.query('<BEGIN,COMMIT,ROLLBACK,your query,anything')

Amar
источник
1

Лучше создать пул pg глобально, и каждый раз, когда вам нужно выполнить операцию с базой данных, используйте клиента, а затем возвращайте его в пул. Как только все операции с базами данных будут выполнены, завершите пул, используяpool.end()

Образец кода -

let pool = new pg.Pool(dbConfig);
pool.connect(function(err, client, done) {

if (err) {
    console.error('Error connecting to pg server' + err.stack);
    callback(err);
} else {
    console.log('Connection established with pg db server');

    client.query("select * from employee", (err, res) => {

            if (err) {
                console.error('Error executing query on pg db' + err.stack);
                callback(err);
            } else {
                console.log('Got query results : ' + res.rows.length);


               async.each(res.rows, function(empRecord) {   
                        console.log(empRecord.name);
                });
            }
            client.release();

        });
}

});  

Для получения дополнительной информации вы можете обратиться к моему сообщению в блоге - Источник

Аникет Такур
источник
0

Как видно из документации , действительны оба варианта, поэтому выберите тот, который вам больше нравится. Как и вы, я бы выбрал второй вариант.

алессиоалекс
источник
А как насчет повторного подключения, когда соединение разрывается? Это делается автоматически? Вики-страница по обработке ошибок ... пуста github.com/brianc/node-postgres/wiki/Error-handling
alltom
Я спросил об этом отдельно: stackoverflow.com/questions/15619456/…
alltom
-1

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

function runQuery(queryString, callback) {
  // connect to postgres database
  pg.connect(postgresDatabase.url,function(err,client,done) {
    // if error, stop here
    if (err) {console.error(err); done(); callback(); return;}
    // execute queryString
    client.query(queryString,function(err,result) {
      // if error, stop here
      if (err) {console.error(err+'\nQuery: '+queryString); done(); callback(); return;}
      // callback to close connection
      done();
      // callback with results
      callback(result.rows);
    });
  });
}

Тогда вы могли бы использовать, назвав его так:

runQuery("SELECT * FROM table", function(result) {
  // Whatever you need to do with 'result'
}
JM-AGMS
источник
Это даже не освобождает соединение с пулом. Это очень быстро опустошит бассейн. Базовый пример на node-postgresстранице подходит лучше этого.
vitaly-t
-2

Вот как я это делаю, своего рода «все вышеперечисленное»

Promise = require 'bluebird'
pg = module.exports = require 'pg'

Promise.promisifyAll pg.Client.prototype
Promise.promisifyAll pg.Client
Promise.promisifyAll pg.Connection.prototype
Promise.promisifyAll pg.Connection
Promise.promisifyAll pg.Query.prototype
Promise.promisifyAll pg.Query
Promise.promisifyAll pg

connectionString = process.env.DATABASE_URL

module.exports.queryAsync = (sql, values) ->
  pg.connectAsync connectionString
  .spread (connection, release) ->
    connection.queryAsync sql, values
    .then (result) ->
      console.log result.rows[0]
    .finally ->
      release()
Дуэйн Филдс
источник
1
Таким образом, у вас не будет ни управления подключением, ни поддержки транзакций, ни поддержки задач. В чем тогда смысл?
vitaly-t
1
что это за язык? кофе? berk
caub