“ SELECT FOR UPDATE”在pg-promise任务中工作吗?

时间:2019-02-02 01:34:58

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

我想做SELECT .. FOR UPDATE来锁定表中的一行,以便可以原子方式进行更新,因为可能会发生相同类型的并发请求。

我一直在做一些测试,但是不清楚FOR UPDATE是否可以在pg-promise.task中工作。我试图避免使用pg-promise.tx,因为这将需要更多的逻辑和可能的递归,由于用例将是高吞吐量,因此我都希望避免这两种情况。

更新: 经过更多的研究和测试,我发现将taskSELECT .. 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。

2 个答案:

答案 0 :(得分:0)

pg-promise tasks只是共享连接。它们不会隐式创建事务。

要在Postgres中使用锁,您需要创建事务,因为在事务中创建的所有锁都在事务结束时释放。如果您未明确创建事务,则每个查询将是其自己的单独事务。

当然,您不必使用tx方法。您可以使用task并自己管理交易。

答案 1 :(得分:0)

我很早就想出了这一点,但当时我忘记发回邮件了。 对我来说,解决方案是使用tx方法,不提供任何模式选项。会做更多的解释,但是确切的细节已经从我的脑海中消失了。