我想做SELECT .. FOR UPDATE
来锁定表中的一行,以便可以原子方式进行更新,因为可能会发生相同类型的并发请求。
我一直在做一些测试,但是不清楚FOR UPDATE
是否可以在pg-promise.task
中工作。我试图避免使用pg-promise.tx
,因为这将需要更多的逻辑和可能的递归,由于用例将是高吞吐量,因此我都希望避免这两种情况。
更新:
经过更多的研究和测试,我发现将task
与SELECT .. FOR UPDATE
一起使用会给我带来意想不到的结果。下面的代码说明。
submitUserOnline:(pgdb, u_uuid, socketConnectionList, user_websock) =>{
return new Promise((resolve,reject) =>{
// pgdb.tx({mode} t => {
pgdb.task( t => {
return t.one('SELECT * FROM users WHERE user_uuid = $1', [u_uuid]
.then(user =>{
// nothing happens here right now, but may in future, including for completeness. May cancel request based off some comparisons.
return t.any('SELECT * FROM users_online WHERE user_uuid = $1 FOR UPDATE',[u_uuid]
.then(result =>{
if(result.length > 0){
console.error('duplicate connection found ' + user_websock.uuid);
if(socketConnectionList[result[0].web_sock_uuid] !== undefined){
console.error('drop connection' + result[0].web_sock_uuid);
socketConnectionList[result[0].web_sock_uuid].wsc.close(4020, 'USER_RECONN');
}
console.error('duplicate connection found - update next ' + user_websock.uuid);
return t.none('UPDATE users_online SET web_sock_uuid = $1 WHERE user_uuid = $2', [user_websock, u_uuid])
.then(res =>{
console.error('UPDATE res: ' + res);
})
.catch(err =>{
console.error('UPDATE err: ' + err);});
}else{
// not reached in test case
console.error('no duplicate found ' + user_websock.uuid);
return t.none(INSERT INTO users_online.....ect ect
}
});
})
.then(res =>{
console.error('>>>task/tx res: ' + res);
resolve({msg: "OK"});
})
.catch(err =>{
console.error('>>>task/tx err: ' + err);
if(err.code ==== '40001'){// recursion for when called as 'tx'
console.error('>>>task/tx err - call recurse');
module.exports.submitUserOnline(pgdb, u_uuid, socketConnectionList, user_websock)
.then(res =>{
console.error('>>>task/tx err - call recurse - res ' + res);
resolve({msg: "OK"});
})
.catch( err =>{
console.error('>>>task/tx err - call recurse - err: ' + err);
reject({msg:"FAILED"});
});
}
});
});
}
const mode = new TransactionMode({
tiLevel: isolationLevel.serializable,
readOnly: false,
deferrable: true
});
submitUserOnline
由websocket处理程序调用。在我的测试用例中,我有一个10个元素的数组(相同的user_uuid),它会触发for循环中的所有客户端连接。基本上,它建立了与服务器Websocket的连接,该套接字会检查特定用户的users_online
表,如果该用户已经在表中,则终止陈旧的连接并更新表中的web_sock_uuid
,这就是问题所在(有时它有时不起作用,运行10个并发连接可显示问题)。当用户行web_sock_uuid
被设置为UPDATE
时,其他并发连接似乎在SELECT .. FOR UPDATE(SLFU)
上正确地阻塞了,当执行UPDATE
时,then()
并不总是在下一个SLFU
发布之前运行。这似乎以待处理SLFU
的形式自我呈现,返回先前web_sock_uuid
之前行的旧UPDATE
。在一个实例中,同一陈旧的“ web_sock_uuid”已连续返回4次。
如果我从切换task
方法将tx
的方法,这需要递归调用,上面的代码按预期方式工作,尽管它需要许多recersions。
答案 0 :(得分:0)
pg-promise
tasks
只是共享连接。它们不会隐式创建事务。
要在Postgres中使用锁,您需要创建事务,因为在事务中创建的所有锁都在事务结束时释放。如果您未明确创建事务,则每个查询将是其自己的单独事务。
当然,您不必使用tx
方法。您可以使用task
并自己管理交易。
答案 1 :(得分:0)
我很早就想出了这一点,但当时我忘记发回邮件了。 对我来说,解决方案是使用tx
方法,不提供任何模式选项。会做更多的解释,但是确切的细节已经从我的脑海中消失了。