[英]Race condition for delete and insert in postgres
我正在开发一个节点项目,我在其中导入了用于数据库操作的 pg 库。 我有一个 Kafka 队列,我从中获取事件并将它们存储在数据库中。 我正在从 kafka 获取订单,每次更新订单时都会生成一个新事件,我需要删除旧的订单详细信息并将其替换为新的。
下面是代码
async function saveOrders(orders: Array<TransactionOnOrders>) {
const client = await pool.connect()
try {
await client.query('BEGIN')
if (orders.length) {
const deleted = await deleteOrders(client, orders[0].orderId)
logger.debug(`deleted rowCount ${deleted.rowCount} ${orders[0].orderId}`)
}
const queries = orders.map(ord => saveTransactionOnOrders(client, ord))
await Promise.all(queries)
await client.query('COMMIT')
} catch (e) {
await client.query('ROLLBACK')
throw e
} finally {
client.release()
}
}
订单更新非常频繁,我们收到了很多事件,造成了竞争条件,导致记录不被删除和插入额外的记录。 例如:假设我们收到一个 order123 的事件并且交易正在进行中,直到它完成 order123 的另一个事件的时间被接收,因此删除查询返回 0 行受影响,插入查询插入另一行,导致 2 行。应该只有一个记录存在。
我试图更改无法正常工作并导致错误的隔离级别
await client.query('BEGIN TRANSACTION ISOLATION LEVEL REPEATABLE READ')
await client.query('BEGIN TRANSACTION ISOLATION LEVEL SERIALIZABLE')
我在这里做错了什么还是有更好的方法来处理上述情况?
如果您更新行而不是删除它们并重新创建它们,这可能会更容易。 在这种情况下,您可以依靠行锁来防止并发更新。
使用INSERT ... ON CONFLICT
来“插入”传入的行。 这是原子的,没有竞争条件。
正如其他人所建议的那样,这里的理想选择是使用INSERT ... ON CONFLICT
以原子方式执行此操作。 如果没有看到deleteOrders
和saveTransactionOrders
的内容,我就saveTransactionOrders
。
如果这不是一个选项,您应该使用SERIALIZABLE
作为隔离级别。 然后您会收到一些序列化错误,但可以安全地重试。 如果您使用了 @databases ( https://www.atdatabases.org/docs/pg-guide-transactions ),您只需传递retrySerializationFailures: true
即可重试:
async function saveOrders(orders: Array<TransactionOnOrders>) {
await pool.tx(async client =>
if (orders.length) {
const deleted = await deleteOrders(client, orders[0].orderId)
logger.debug(`deleted rowCount ${deleted.rowCount} ${orders[0].orderId}`)
}
const queries = orders.map(ord => saveTransactionOnOrders(client, ord))
await Promise.all(queries)
}, {
isolationLevel: IsolationLevel.SERIALIZABLE,
retrySerializationFailures: true,
})
}
@databases 处理启动事务,并在异步回调结束时提交/回滚。 它还重试序列化失败。
如果您正在处理大量事件,您可能会由于序列化失败的频率很高而遇到性能问题,因此会重试。
您可以在 node.js 中使用“锁”来确保一次只有一个进程更新一组给定的订单。 https://www.atdatabases.org/docs/lock应该使实现起来相当简单。 但这只会锁定单个 node.js 进程上的并发事务,因此您仍然需要事务处理来处理多个 node.js 进程。
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.