【问题标题】:Timeout acquiring a connection. The pool is probably full. error when updating 10000 rows获取连接超时。游泳池可能已经满了。更新 10000 行时出错
【发布时间】:2019-06-17 15:59:49
【问题描述】:

当我尝试使用 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 可以处理这种其他方式,即每分钟执行 10000 次查询的大型网站都不会出现问题。问题不在服务器上,因为服务器有 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' }

【问题讨论】:

  • 听起来好像 Node.js 或 Knex 为每一行打开一个新连接。
  • @a_horse_with_no_name 那么你建议如何解决这个问题
  • 蓝鸟地图系列以限制并发承诺。您目前基本上同时启动了数千个连接。
  • @Gangstead 你能举个例子吗?使用上面的代码
  • @Gangstead 我曾经承诺在 1 秒后解决每个更新查询,但这不起作用,就像我要更新成千上万的数据一样,这将永远存在,是吗无论如何,在没有承诺的情况下进行查询。所以它会在不到一秒的时间内更新所有行

标签: node.js postgresql knex.js


【解决方案1】:

使用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 连接。

【讨论】:

  • 我检测到这个错误死锁,过了一会儿我得到池可能已满错误
  • 你能给我解释一下 concurrency: 5 是什么意思,它的作用是什么
  • 我已经尝试过你的解决方案,它可以工作,但我现在看到 postgreSQL pids CPU 使用率一直是 98%,尤其是这个函数每 2 分钟循环一次,服务器 CPU 是 10-20%,这是正常的但是使用这种方法postgreSQL CPU总是很高。
  • 每 2 分钟有一万多个单独更新,CPU 使用率会很高。正如 MikaS 所说,您必须在单个 (knex.raw(...)) 更新语句中执行此操作,或批量插入临时表并运行更新。批量更新语句的可能起点:stackoverflow.com/a/40555000/1637003
  • 我创建了一个名为 temp_table 的表,并在 psql 中创建了一个函数,该函数在 INSERT 时会更新真实表 ('users') 中的数据,但我收到此错误错误:检测到死锁。我停止了这个表上所有正在运行的函数,但是我从 psql 中的函数中得到了这个错误。怎么样?
【解决方案2】:

Knex 并不是真正适合这种大型浴缸更新的工具。特别是在您使用它的方式上,它的性能特别差。

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

因此,无论如何,所有更新都以串行方式发送到数据库服务器,并且这些更新的并发性为 0。

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

虽然这不应该导致死锁......所以你的代码中可能还有其他一些问题。

如果在查询中出现单个错误时不还原所有数据并不重要,您可以尝试在多个事务中使用较小的批次...实际上我不明白为什么需要这种数据更新如果单行有问题可以重新发送/记录,则在事务中完成。

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

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

如果您通过单个事务进行大量更新,那么它确实不太可能导致高 CPU 使用率。您应该登录到该 SQL 服务器并检查在该工作负载期间它正在执行什么样的查询......也许您偶然在不同的事务中多次并行运行相同的更新代码,这也可以解释死锁问题。

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

这样您就可以构建一批更新查询并将它们添加为主查询https://knexjs.org/#Builder-with 的预查询,然后所有这些查询都在数据库中作为原子操作运行,因此不需要事务来确保整批或什么都不做进去。

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 2019-04-13
    • 1970-01-01
    • 2023-03-14
    • 2020-09-07
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多