超时获取连接。游泳池可能已满。更新10000行时出错

时间:2019-06-17 15:59:49

标签: node.js postgresql knex.js

当我尝试使用knexjs更新5000行时,出现错误超时获取连接。池可能已满。”。

当我查看CPU使用率时。我发现postgres pid总是占用90-98%的CPU使用率,这是不正常的,我在每个kenx上都通过destroy()尝试过,虽然可以,但是它破坏了连接并没有解决

这是我正在使用的代码

const knexDb = knex({ client: 'pg', connection: {
    host : '127.0.0.1',
    user : process.env.DB_USER,
    password : process.env.DB_PASSWORD,
    database : process.env.DB_DATABASE,
    port: process.env.DB_PORT
  }});

arrayWith5ThousandObj.map(data => {
    knexDb('users').where({
      user: data.user,
    })
    .update({
      product: data.product
    })
    .catch(err => console.error('update user products', err))
})

这是一个每1分钟重复一次的循环函数,我也尝试过.finally -> knexDb.destroy(),但是它破坏了连接,并且我得到了无法获取连接的错误。

我想使用knexjs持续更新5000行或更多,例如10,000+,并且我认为PostgreSQL可以处理另一个明智的选择,即大型网站每分钟执行10万个查询的问题而没有问题。问题不在服务器上,因为服务器具有10个CPU和16gb的RAM,因此资源不是问题,我停止了该应用程序以外服务器上所有正在运行的进程。 postgres pid几乎根本不使用CPU。因此,在发生大量查询时会出现问题。是否有大量更新可以使用knexjs一次更新所有10,000+行?

我最近尝试过此解决方案

return knexDb.transaction(trx => {
    const queries = [];
    arrayWith5ThousandObj.forEach(data => {
        const query = knexDb('users')
            .where({
              user: data.user,
            })
            .update({
                product: data.product,
            })
            .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
});

但我收到此错误:

{ error: deadlock detected
   at Connection.parseE (/*********/connection.js:601:11)
   at Connection.parseMessage (/*********/connection.js:398:19)
   at Socket.<anonymous> (/**********/connection.js:120:22)
   at Socket.emit (events.js:189:13)
   at addChunk (_stream_readable.js:284:12)
   at readableAddChunk (_stream_readable.js:265:11)
   at Socket.Readable.push (_stream_readable.js:220:10)
   at TCP.onStreamRead [as onread] (internal/stream_base_commons.js:94:17)
 name: 'error',
 length: 340,
 severity: 'ERROR',
 code: '40P01',
 detail:
  'Process 9811 waits for ShareLock on transaction 443279355; blocked by process 9808.\nProcess 9808 waits for ShareLock on transaction 443279612; blocked by process 9811.',
 hint: 'See server log for query details.',
 position: undefined,
 internalPosition: undefined,
 internalQuery: undefined,
 where: 'while locking tuple (1799,4) in relation "users"',
 schema: undefined,
 table: undefined,
 column: undefined,
 dataType: undefined,
 constraint: undefined,
 file: 'deadlock.c',
 line: '1140',
 routine: 'DeadLockReport' }

2 个答案:

答案 0 :(得分:1)

Knex并不是真正适用于此类大量更新的正确工具。特别是在您使用它的方式上,它表现得特别糟糕。

初始化5k查询构建器时,将同时编译并执行所有构建器,但是使用事务时,所有查询都通过单个连接发送。

因此,无论如何,所有更新都将串行发送到DB服务器,并且这些更新的并发性为0。

因此,将编译5000个knex对象,并将5000个带有绑定的SQL查询发送到DB驱动程序,然后由驱动程序对它们进行缓冲,并将它们一一发送到服务器。

尽管那不会导致死锁...所以您的代码中可能还会存在其他一些问题。

如果在查询中只有一个错误时所有数据都不会恢复,则可以尝试在多个事务中使用较小的批处理...实际上我不理解为什么需要这种数据更新如果单行有问题,可以重新发送/记录,则在事务中完成。

我最好的建议是设置数据库服务器的批处理大小,连接池大小和连接限制,以匹配您要推送到服务器的工作量。

  

始终查看postgreSQL pids CPU使用率98%

如果您通过单个事务进行大量更新,则实际上不可能导致CPU使用率很高。您应该登录到该SQL Server并检查该SQL Server在该工作负载期间正在执行哪种查询...也许您是偶然地在同一事务中以不同的事务多次并行执行相同的更新代码,这也可以解释死锁问题。

对于SQL而言,批处理更新存在很多问题,因为单个更新语句只能更新一行。在单个查询中运行多个更新的一种方法是使用CTE查询https://www.postgresql.org/docs/current/queries-with.html

这样,您可以构建批更新查询并将其作为主查询https://knexjs.org/#Builder-with的前置查询,然后将所有这些查询作为原子操作在数据库中运行,因此不需要事务来确保整个批处理否则什么都没进。

答案 1 :(得分:0)

使用bluebird.map控制并发性:

knex.transaction((trx) => {
    Bluebird.map(arrayWith5ThousandObj, (data) => {
            return trx('users')
                .where({
                    user: data.user,
                })
                .update({
                    product: data.product,
                }))

    }, { concurrency: 5 })
    .then(trx.commit);
})
.then(() => console.log('all done'));

在您的初始解决方案中,您一次生成5000个诺言,所有诺言都会尝试一次连接到数据库。该解决方案将确保最多有X个并发承诺,并且不使用延迟,您可以为解决方案微调数量。 Knex默认为max 10连接。