【问题标题】:does sql statement ensure atomicity in postgressql语句是否确保postgres中的原子性
【发布时间】:2023-03-19 15:08:01
【问题描述】:

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

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

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

我的解决方案

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

queryBuilder().readAndUpdateValueInTheDatabase();  

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

【问题讨论】:

  • 您需要决定要实现的变体。任何一种变体都是可能的。 (1) 阻塞。一旦第一个客户端读取一行,第二个客户端将等待并在第一个客户端完成写入并释放锁后开始读取该行。如果等待时间过长,则需要实现重试逻辑。 (2) 乐观。许多客户端可以在不锁定的情况下读取同一行。当客户端写入时,它会检查同时没有人更改行并实现解决冲突的逻辑。

标签: postgresql plpgsql knex.js acid


【解决方案1】:

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

【讨论】:

  • 提高隔离级别会阻止我所描述的这种赛车情况吗?我的意思是,读取一个值本身就是一个事务。由于两条语句是独立执行的,postgres 如何知道读取是否与将来的写入相关?
  • 提高隔离级别会导致性能问题吗?设置后,整个数据库中查询中的所有事务是否会以相同的隔离级别运行?
  • 另外,在序列化失败时,它是尝试重做事务还是只是取消它?
  • 如果您在不同的交易中阅读而不是使用您阅读的信息,那么您做错了。是的,它会对性能产生影响。它不会重试,因为它不知道您在查看新数据后会做出什么决定。如果决定是在客户端做出的,那么只有客户端可以修改/重复它。
  • 我在上面引用:“如果你在不同的交易中阅读而不是使用你阅读的信息,那么你做错了”.....什么是正确的方法做吗?进行相同的交易会是一个好方法吗?并且会使用原始查询 knex 语句做一些 plpgsql 来解决问题吗?这是否也意味着提高isolation 级别也不能解决问题?
【解决方案2】:

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

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

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

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

    锁定行以防止并发修改,并保持锁定直到事务结束。

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

如果您期望经常发生冲突,第一种解决方案通常会更好,而如果冲突很少,则第二种解决方案会更好。

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

【讨论】:

  • 感谢您的详细回答,我会阅读更多关于此的内容并在短时间内回复此答案。我认为第一个解决方案更好,但是,您认为如果说 10,000 个不同的事务同时出现,并且锁由单个事务持有,那么是否存在诸如 postgres 缓冲区已满之类的问题或其他什么(我不确定,但是,他们在锁定期间是否获得了任何类型的缓冲区分配?)
  • 您不必担心缓冲区溢出。如果你有 10000 个并发事务,你还有其他问题(系统过载),需要一个事务池。
  • 没错,我的好奇心是从理论的角度产生的。
  • 我读到了隔离级别,似乎这是使用 BEGIN ISOLATION LEVEL REPEATABLE READ 为每个事务设置的,是否有命令为每个事务启用此功能?
  • 链接完全不相关,你可以使用或不使用函数,随你喜欢。关键是你必须预料到一个序列化错误,如果你得到一个重复事务。
【解决方案3】:

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

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

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

【讨论】:

  • 但是,如果 COMMIT 作为 ROLLBACK 被拒绝,这是否意味着不读取过时的值?这是否也意味着读/写都被取消或重新开始?
  • 事务完成时没有任何“延迟”。提交的更改立即可用。
  • @a_horse_with_no_name ,将其作为同一个交易是一个好方法吗?并且会使用原始查询 knex 语句做一些 plpgsql 来解决问题吗?这是否也意味着提高隔离级别也不能解决问题?
  • 关于这个答案的几乎所有内容都是错误的,对不起。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2020-10-04
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多