sql语句是否确保postgres中的原子性

问题描述

我的程序中有一个使用多用户支持的简单错误。我正在使用 knex 来构建 sql 查询,并且我有一个描述场景的伪代码

const value = queryBuilder().readDataFromTheDatabase();//executes this
//do some other work and get value
queryBuilder.writeValuetoTheDatabase(updateValue(value));

这段代码用于某种中间件功能。正如您所看到的,这是一种可能的竞争条件,即当多个用户访问该事物时,其中一个用户尝试在大致相同的时间内执行此操作时会得到一个陈旧的值。 >

我的解决方

所以,我认为一个可能的解决方案是创建一个 queryBuilder 语句:

queryBuilder().readAndUpdateValueInTheDatabase();  

所以,我可能不得不使用一点plpgsql。我想知道这个解决方案是否足够。语句会自动执行吗?即当一个请求读取但没有完成他的写入时,另一个请求是等待读取和写入还是只是等待写入但读取过时的值?

解决方法

我认为您在这里寻找的是隔离性,而不是原子性。您可以将所有事务设置为最高隔离级别、可序列化(高于通常的默认级别)。在该级别下,如果事务读取(并且可能依赖)的数据发生更改,则当它尝试提交时,可能会出现序列化失败错误。我说“可能”,因为系统可以得出结论,这种情况与提交后发生的数据更改一致,在这种情况下,允许提交。

,

为了避免这种设置的竞争条件,您必须在同一个数据库事务中同时运行读取和写入。

有两种方法可以做到这一点:

  1. 使用默认的 READ COMMITTED 隔离级别并在读取行时锁定它们:

    SELECT ... FROM ... FOR NO KEY UPDATE;
    

    这会针对并发修改锁定行,并且该锁定会一直保持到事务结束。

  2. 使用 REPEATABLE READ 隔离级别,不要锁定任何东西。如果有人同时修改了该行,那么您的 UPDATE 将收到序列化错误 (SQLSTATE 40001)。在这种情况下,您可以回滚事务并在新的 REPEATABLE READ 事务中重试。

如果您预计冲突频繁,则第一种解决方案通常更好,而如果冲突很少,则第二种解决方案更好。

请注意,在这两种情况下,您都应尽可能缩短数据库事务,以降低发生冲突的风险。

,

PostgreSQL 中的事务在访问表时使用乐观锁模型,而其他一些 DBMS 使用悲观锁(IBM Db2)或双锁模型(MS SQL Server)。

乐观锁定快照您正在处理的数据,并在快照上完成修改,直到事务结束。当事务完成时,快照修改被推迟到真实数据库(表行)上,但如果其他用户在快照捕获和提交之间进行了更改,则提交无法应用,并且 COMMIT 被拒绝,因为回滚。

您可以尝试提高 ISOLATION LEVEL(REPEATABLE READ 或 SERIALIZABLE)以避免麻烦。