我希望使用PostgreSQL事务隔离来确保乐观并发控制模式的数据正确性,在这种模式中,冲突的事务会自动重新尝试,而不是我的应用程序对数据库行和表进行预先锁定。
实现这一点的一种常见方法是,web应用程序通过中间件层在代码块或重放HTTP请求中执行特定次数的重放HTTP请求,也称为HTTP重放。下面是一个用于金字塔和Python应用程序web的中间件的例子。
我没有找到任何关于Node.js及其PostgreSQL驱动程序如何处理两个并发事务的情况的好信息,其中一个由于读写冲突而无法处理。PostgreSQL将回滚其中一个事务,但这是如何向应用程序发出信号的?在Python中,PSQL驱动程序会在这种情况下引发psycopg2.extensions.TransactionRollbackError。对于其他SQL数据库驱动程序,这里有一些它们将引发的异常。
当您将SQL事务隔离级别设置为SERIALIZABLE时,这种行为更常见,因为您倾向于在负载下得到更多冲突,所以我想优雅地处理它,而不是将HTTP 500提供给用户。
我的问题是:
发布于 2020-10-28 16:20:18
以下是在使用使用图书馆 (如TypeORM)的库时如何处理并发性:
/**
* Check error code to determine if we should retry a transaction.
*
* See https://www.postgresql.org/docs/10/errcodes-appendix.html and
* https://stackoverflow.com/a/16409293/749644
*/
function shouldRetryTransaction(err: unknown) {
const code = typeof err === 'object' ? String((err as any).code) : null
return code === '40001' || code === '40P01';
}
/**
* Using a repeatable read transaction throws an error with the code 40001
* "serialization failure due to concurrent update" if the user was
* updated by another concurrent transaction.
*/
async function updateUser(data: unknown) {
try {
return await this.userRepo.manager.transaction(
'REPEATABLE READ',
async manager => {
const user = manager.findOne(User, id);
// Modify user
// ...
// Save the user
await manager.save(user);
}
);
} catch (err) {
if (shouldRetryTransaction(err)) {
// retry logic
} else {
throw err;
}
}
}对于重试事务,我建议使用一个库,比如抽象重试逻辑的async-retry。
你会注意到这个模式对于简单的东西是很好的,但是如果你想传递manager (例如)。因此,事务可以在其他服务中重用),这样就会变得非常麻烦。我建议使用利用延续本地存储来传播事务的typeorm-transactional-cls-hooked库。
以下是您如何为快速应用程序重播事务:
/**
* Request replay middleware
*/
import retry from 'async-retry';
function replayOnTransactionError(fn: (req, res, next) => unknown) {
return (req, res, next) => {
retry(bail => {
try {
// Call the actual handler
await fn(req, res, next);
} catch (err) {
if (!shouldRetryTransaction(err)) {
// Bail out if we're not supposed to retry anymore
return bail(err);
}
// Rethrow error to continue retrying
throw err;
}
}, {
factor: 2,
retries: 3,
minTimeout: 30,
});
}
}
app.put('/users/:id', replayOnTransactionError(async (req, res, next) => {
// ...
}))https://stackoverflow.com/questions/60339223
复制相似问题