【问题标题】:How to repeat SQL insertion until successful with pg-promise?如何使用 pg-promise 重复 SQL 插入直到成功?
【发布时间】:2017-10-06 22:09:44
【问题描述】:

在我的程序中,我将一些数据插入到一个表中并取回它的 id,我需要确保我将该 id 输入到另一个具有唯一随机生成字符串的表中。但是,如果尝试插入已经存在的随机字符串而插入失败,我该如何重复插入直到成功?

我正在使用pg-promise 与 postgreSQL 对话。我可以运行这样的程序,将数据插入到两个表中,因为随机字符串不存在:

   db.none(
            `
            WITH insert_post AS
            (
                INSERT INTO table_one(text) VALUES('abcd123')
                RETURNING id
            )
            INSERT INTO table_two(id, randstr)
                    VALUES((SELECT id FROM insert_post), '${randStrFn()}')
            `
        )
    .then(() => console.log("Success"))
    .catch(err => console.log(err));

我不确定是否有任何基于 SQL/JS/pg-promise 的简单解决方案可供我使用。

【问题讨论】:

  • 只是想知道 - 为什么你使用随机字符串而不是 ID/sequence/UUID?
  • 也许您想要实现的目标与您使用的库无关。听起来您只需要将 ON CONFLICT DO NOTHING 附加到您的 INSERT 查询或类似查询中。无论如何,这是一个与 SQL 相关的问题,而不是与库相关的问题。
  • @Lashane 随机字符串将是前端用户将使用的 id 的别名。
  • @vitaly-t 我似乎找不到适合初学者的ON CONFLICT DO NOTHING 解释。阅读this,我不确定如何使用它,因此如果失败我可以重新尝试插入。

标签: sql node.js postgresql promise pg-promise


【解决方案1】:

我会鼓励问题的作者为他的问题寻求一个纯 SQL 解决方案,因为在性能方面它会比其他任何东西都更有效。

但由于问题是关于如何使用pg-promise 重新运行查询,除了已经发布的示例之外,我将提供一个示例,除非每次尝试都获取和释放连接,以及适当的数据完整性。

db.tx(t => {
    // BEGIN;
    return t.one('INSERT INTO table_one(text) VALUES($1) RETURNING id', 'abcd123', a => +a.id)
        .then(id => {
            var f = attempts => t.none('INSERT INTO table_two(id, randstr) VALUES($1, randStrFn())', id)
                .catch(error => {
                    if (--attempts) {
                        return f(attempts); // try again
                    }
                    throw error; // give up
                });
            return f(3); // try up to 3 times
        });
})
    .then(data => {
        // COMMIT;
        // success, data = null
    })
    .catch(error => {
        // ROLLBACK;
    });

由于您尝试重新运行依赖查询,因此不应让第一个查询保持成功,如果您对第二个查询的所有尝试都失败了,您应该回滚所有更改,即使用事务 - 方法 @ 987654322@,如代码所示。

这就是为什么我们将您的 WITH 查询拆分到事务中,以确保这种完整性。

更新

下面是一个更好的版本。因为事务内部的错误需要隔离,为了避免破坏事务堆栈,每次尝试都应该在自己的SAVEPOINT内部,这意味着使用另一个事务级别:

db.tx(t => {
    // BEGIN;
    return t.one('INSERT INTO table_one(name) VALUES($1) RETURNING id', 'abcd123', a => +a.id)
        .then(id => {
            var f = attempts => t.tx(sp => {
                // SAVEPOINT level_1;
                return sp.none('INSERT INTO table_two(id, randstr) VALUES($1, randStrFn())', id);
            })
                .catch(error => {
                    // ROLLBACK TO SAVEPOINT level_1;
                    if (--attempts) {
                        return f(attempts); // try again
                    }
                    throw error; // give up
                });
            return f(3); // try up to 3 times
        });
})
    .then(data => {
        // 1) RELEASE SAVEPOINT level_1;
        // 2) COMMIT;
    })
    .catch(error => {
        // ROLLBACK;
    });

我还建议使用pg-monitor,这样您就可以看到并理解下面发生的事情,以及实际上正在执行的查询。


附:我是pg-promise的作者。

【讨论】:

  • 非常感谢您的回答和制作 pg-promise 库。虽然它按预期工作并且在 3 次尝试后无法进入第二张桌子时回滚,但它似乎仍在运行最后一个 then(success)
  • @RobertC.Holland 我不明白这怎么可能。自原始帖子以来,我已多次更新答案,因此请确保您已准确复制最新示例,而不会跳过任何内容。一定不能进决赛.then,没门!我猜,您在某处跳过了 return 之一 - 检查一下;)
  • 你是对的,我的错,可能在之前复制时犯了一些错误。再次感谢您的帮助。
  • @RobertC.Holland 您可能想切换到更新版本,这样会更好;)
【解决方案2】:

最简单的方法是将它放入一个方法中,然后在 catch 中重新调用它:

const insertPost = (post, numRetries) => {
    return
       db.none(
                `
                WITH insert_post AS
                (
                    INSERT INTO table_one(text) VALUES('abcd123')
                    RETURNING id
                )
                INSERT INTO table_two(id, randstr)
                        VALUES((SELECT id FROM insert_post), '${randStrFn()}')
                `
            )
        .then(() => console.log("Success"))
        .catch(err => {
            console.log(err)
            if (numRetries < 3) {
              return self.insertPost(post, numRetries + 1);
            }
            throw err;
        });

}

【讨论】:

  • 这在技术上是正确的,但效率很低。如果性能对他很重要,作者应该为他的问题寻求一个纯 SQL 的解决方案。
  • 没有说明这将是最有效但最简单的处理方法。就我个人而言,我会有所不同,但没有更多关于问题的详细信息,我选择了最简单的方法。
  • 尝试在数据库对象的顶层执行此操作会产生另一个可怕的影响,因为它将尝试为每个查询分配和释放连接。它应该只是一个连接。我很快就会发布一个更好的例子...... ;)
  • 哦,我同意你的看法。我不会“按原样”使用此代码,因为它有太多性能问题。
猜你喜欢
  • 2019-11-12
  • 2021-10-07
  • 2018-01-08
  • 1970-01-01
  • 2020-03-04
  • 2016-07-14
  • 2016-09-15
  • 2017-11-15
  • 2021-08-14
相关资源
最近更新 更多