sql 语句是否确保 postgres 中的原子性
does sql statement ensure atomicity in postgres
我的程序中有一个使用多用户支持的简单错误。我正在使用 knex
构建 sql 查询,并且我有一个描述场景的伪代码:
const value = queryBuilder().readDataFromTheDatabase();//executes this
//do some other work and get value
queryBuilder.writeValueToTheDatabase(updateValue(value));
这段代码被用于某种中间件功能。正如您所看到的,这是一个可能的 竞争条件 即当多个用户访问该事物时,当他们尝试以大致相同的时间执行此操作时,其中一个会获得过时的值.
我的解决方案
所以,我认为可能的解决方案是创建一个 queryBuilder 语句:
queryBuilder().readAndUpdateValueInTheDatabase();
所以,我可能不得不使用一点 plpgsql
。我想知道这个解决方案是否足够。该语句会自动执行吗?即,当一个请求读取但未完成其写入时,另一个请求是等待读取和写入还是等待写入但读取陈旧值?
PostgreSQL 中的事务在访问 table 时使用乐观锁定模型,而其他一些 DBMS 使用悲观锁定 (IBM Db2) 或二次锁定模型 (MS SQL服务器)。
乐观锁定对您正在处理的数据进行快照,并且在事务结束之前对快照进行修改。当事务完成时,快照修改被推迟到真实数据库(table 行),但如果其他用户在快照捕获和提交之间进行了更改,则提交无法应用并且COMMIT 作为 ROLLBACK 被拒绝。
您可以尝试提高 ISOLATION LEVEL(REPEATABLE READ 或 SERIALIZABLE)以避免麻烦。
我认为您在这里寻找的是隔离性,而不是原子性。您可以将所有事务设置为最高隔离级别,可序列化(高于通常的默认级别)。在该级别,如果事务读取(并且可能依赖)的数据发生更改,那么当它尝试提交时可能会出现序列化失败错误。我说“可能”,因为系统可以断定情况与提交后发生的数据更改一致,在这种情况下允许提交。
为避免此类设置出现竞争条件,您必须运行在同一个数据库事务中读取和写入。
有两种方法可以做到这一点:
使用默认的READ COMMITTED
隔离级别并在读取时锁定行:
SELECT ... FROM ... FOR NO KEY UPDATE;
锁定行以防止并发修改,并且锁一直保持到事务结束。
使用REPEATABLE READ
隔离级别并且不要锁定任何东西。然后,如果有人同时修改了该行,您的 UPDATE
将收到序列化错误 (SQLSTATE 40001)。在这种情况下,您可以回滚事务并在新的 REPEATABLE READ
事务中重试。
如果您预计冲突频繁,第一种解决方案通常更好,而如果冲突很少见,第二种解决方案更好。
请注意,在这两种情况下,您都应使数据库事务尽可能短,以降低发生冲突的风险。
我的程序中有一个使用多用户支持的简单错误。我正在使用 knex
构建 sql 查询,并且我有一个描述场景的伪代码:
const value = queryBuilder().readDataFromTheDatabase();//executes this
//do some other work and get value
queryBuilder.writeValueToTheDatabase(updateValue(value));
这段代码被用于某种中间件功能。正如您所看到的,这是一个可能的 竞争条件 即当多个用户访问该事物时,当他们尝试以大致相同的时间执行此操作时,其中一个会获得过时的值.
我的解决方案
所以,我认为可能的解决方案是创建一个 queryBuilder 语句:
queryBuilder().readAndUpdateValueInTheDatabase();
所以,我可能不得不使用一点 plpgsql
。我想知道这个解决方案是否足够。该语句会自动执行吗?即,当一个请求读取但未完成其写入时,另一个请求是等待读取和写入还是等待写入但读取陈旧值?
PostgreSQL 中的事务在访问 table 时使用乐观锁定模型,而其他一些 DBMS 使用悲观锁定 (IBM Db2) 或二次锁定模型 (MS SQL服务器)。
乐观锁定对您正在处理的数据进行快照,并且在事务结束之前对快照进行修改。当事务完成时,快照修改被推迟到真实数据库(table 行),但如果其他用户在快照捕获和提交之间进行了更改,则提交无法应用并且COMMIT 作为 ROLLBACK 被拒绝。
您可以尝试提高 ISOLATION LEVEL(REPEATABLE READ 或 SERIALIZABLE)以避免麻烦。
我认为您在这里寻找的是隔离性,而不是原子性。您可以将所有事务设置为最高隔离级别,可序列化(高于通常的默认级别)。在该级别,如果事务读取(并且可能依赖)的数据发生更改,那么当它尝试提交时可能会出现序列化失败错误。我说“可能”,因为系统可以断定情况与提交后发生的数据更改一致,在这种情况下允许提交。
为避免此类设置出现竞争条件,您必须运行在同一个数据库事务中读取和写入。
有两种方法可以做到这一点:
使用默认的
READ COMMITTED
隔离级别并在读取时锁定行:SELECT ... FROM ... FOR NO KEY UPDATE;
锁定行以防止并发修改,并且锁一直保持到事务结束。
使用
REPEATABLE READ
隔离级别并且不要锁定任何东西。然后,如果有人同时修改了该行,您的UPDATE
将收到序列化错误 (SQLSTATE 40001)。在这种情况下,您可以回滚事务并在新的REPEATABLE READ
事务中重试。
如果您预计冲突频繁,第一种解决方案通常更好,而如果冲突很少见,第二种解决方案更好。
请注意,在这两种情况下,您都应使数据库事务尽可能短,以降低发生冲突的风险。