Який правильний спосіб використовувати модуль node.js postgresql?


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 . По-перше, перепрошую, документація не дала зрозуміти правильного варіанту: це моя вина. Я спробую це вдосконалити. Я зараз написав « Історію», щоб пояснити це, тому що розмова надто довга для 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, який робить саме це.


2
На жаль, я досить новачок у СУБД, і у мене все ще є проблема з розумінням цього, але чому ми не хочемо "смітити pg.connect" дзвінки? Це для простоти чи з причини ефективності? Наприклад, я закликаю pg.connect один раз у кожному з маршрутів, які є в моєму базовому додатку (усі з однаковим conString). Це нормально? Інтуїтивно відчувається, що він створює нове підключення до тієї ж бази даних, коли я її викликаю (чого я не хочу), але чи використовує він об’єднані зв’язки всередині? Дякую.
user1164937

Дивовижно. Чому ви використовуєте одне підключення для кожного запиту, а не одне для кожного запиту? Я шукав відповідний спосіб поділитися з'єднанням за кількома запитами в межах запиту і розглядав res.locals до того, як знайти тут вашу відповідь.
Джо Лапп,

2
Чекай. Схоже, ваше рішення тут не підтримує транзакції.
Джо Лапп,

Це слід постійно посилати на github.
Райан Вілліс

1
Зверніть увагу, що pg.connect було видалено після v7 node-postgres, також відомого як pg. Дивіться stackoverflow.com/questions/45174120/pg-connect-not-a-function
Колін Д

23

Я є автором pg-promis , який спрощує використання node-postgres за допомогою обіцянок.

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

Індивідуальний запит у pg-promis зводиться до того, що стосується вашої бізнес-логіки:

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 github.com/vitaly-t/pg-promise/wiki/… , або ви можете вказати SSL у параметрах підключення: github.com/vitaly-t/pg-promise/wiki/Connection-Syntax
vitaly-t

Я новачок у більшості з цього: javascript, обіцянки, postgres тощо, і це саме те, що мені потрібно. Дякую!!
Ryan Rodemoyer

1
@ocram Я щойно працюю над цимpgp.pg.defaults.ssl = true;
CharlieC

чи буде це створювати кілька з'єднань для автоматичного покращення пропускної здатності postgres, коли ми надаємо кілька запитів запиту postgres?
субота

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


1

Краще створити pg-пул у всьому світі, і кожного разу, коли вам потрібно робити db-операцію, використовуйте клієнт, а потім відпускайте його назад до пулу. Після того, як всі операції з db завершені, закінчіть використання пулу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

Як видно з документації, обидва варіанти дійсні, тож вибирайте, що вам більше подобається. Як і ви, я піду на другий вибір.


А як щодо повторного підключення, коли з’єднання перестає? Це робиться автоматично? Вікі-сторінка про обробку помилок ... empty 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'
}

Це навіть не звільняє з'єднання назад до пулу. Це дуже швидко виснажить басейн. Основний приклад на 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
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.