Postgres中SQL语句是否保证原子性?knex读写竞态如何解决?
方案可行性与原子性说明
- 你提到的将读写逻辑合并为单次数据库操作的方案,可以解决当前的竞态条件问题,核心规则如下:
- PostgreSQL中单次执行的SQL语句、单次调用的PL/pgSQL函数默认具备原子性,整个读写逻辑要么全部执行成功,要么全部回滚,不会出现读完成、写未完成的中间状态对外暴露。
- 并发场景下的读写可见性,取决于你具体的实现逻辑:
- 如果使用
UPDATE ... RETURNING语法实现(不需要额外编写PL/pgSQL,knex原生支持拼接),执行时会先对目标行加行级写锁,拿到锁后才会执行读、计算、写回的全流程,其他操作同一行的并发请求会被阻塞,直到前一个操作全流程完成、锁释放后才会执行,不会读到过期值。 - 如果是在PL/pgSQL函数中先执行SELECT再执行UPDATE,需要在SELECT语句后加
FOR UPDATE手动加行锁,才能达到和上面一致的效果;如果SELECT不加锁,即使逻辑封装在同一个PL/pgSQL函数中,仍有可能出现读取到旧值后、写回前其他请求已经修改了该行的问题,竞态依然存在。
- 如果使用
适配knex的最优实现建议
无需编写PL/pgSQL即可实现原子的读改写操作,示例代码如下(以更新用户余额场景为例):
// 直接在数据库层完成「原值读取+计算+更新+返回新值」全流程 const updatedBalance = await knex('user_account') .update('balance', knex.raw('balance + ?', [10])) // 计算逻辑直接放在数据库侧执行 .where('user_id', 123) .returning('balance') // 返回更新后的值 .first();
该写法性能优于调用PL/pgSQL函数,且天然避免先读后写的竞态问题。
内容的提问来源于stack exchange,提问作者juztcode
相关产品推荐
相关产品推荐

