RError.com

RError.com Logo RError.com Logo

RError.com Navigation

  • 主页

Mobile menu

Close
  • 主页
  • 系统&网络
    • 热门问题
    • 最新问题
    • 标签
  • Ubuntu
    • 热门问题
    • 最新问题
    • 标签
  • 帮助
主页 / 问题 / 1606785
Accepted
Евгений Юрьевич
Евгений Юрьевич
Asked:2025-02-12 21:52:31 +0000 UTC2025-02-12 21:52:31 +0000 UTC 2025-02-12 21:52:31 +0000 UTC

Postgres SELECT 查询在“读取已提交”隔离中看不到已提交事务的数据

  • 772

当我在不同的事务中并行执行查询时,我遇到了 Postgres 的奇怪行为。 Postgres 15.3,数据库上的事务隔离级别 -读提交。请求如下:

with inserting as (
            insert into device(device_guid, platform, created_at)
            values (
                '5f0ca10f-a257-44ac-940e-a808c63a9914'::uuid,
                'WEB',
                NOW()
            )
            ON CONFLICT (device_guid) DO NOTHING
            returning *
        )

        select id, device_guid, platform, created_at, 'INSERT' as db_operation from inserting
                UNION ALL
        select id, device_guid, platform, created_at, 'GET' as db_operation from device where device_guid = '5f0ca10f-a257-44ac-940e-a808c63a9914'::uuid

(device_guid 上有一个唯一索引)

预期会出现以下行为:

  1. 输入是device_guid和一些数据(在示例中为'5f0ca10f-a257-44ac-940e-a808c63a9914','WEB',NOW())
  2. 尝试在 CTE 中插入传递的 device_guid。如果这样的device_guid已经存在,那么我们只需退出CTE进入下一步(选择)。如果表中没有这样的 device_guid,那么我们将其插入表中,并将其插入到 CTE 中
  3. 接下来,我们从物理表设备和插入临时 CTE 表中进行选择。这里 db_operation 会告诉您该行是从哪里获取的,是从 CTE(INSERT)获取的,还是该行已经在设备表中(GET)。
  4. 如果执行查询两次,一切都会按预期工作:第一次,出现一行 db_operation = 'INSERT',第二次,出现一行 db_operation = 'GET'。

但是,当两个事务同时开始使用相同的 device_guid 执行此查询时,一切都会中断。我期望以下行为是正确的:

  1. 事务 A比事务 B稍早开始。
  2. 字符串 A尝试将一行插入到具有唯一索引的表中。这意味着事务 A将对该行采取排他锁,并对索引采取共享锁,直到事务提交为止。
  3. 线程 B看到线程 A已获取锁,则只需等待锁被释放。
  4. Thr-A插入成功并继续进行两次选择。但自从如果事务还未提交,则不会移除锁。
  5. Tr-ya A完成其选择和整个查询脚本并提交事务,释放锁。
  6. Tr-ya B发现锁被移除,尝试使用相同的 device_guid 向设备中插入一行,发生冲突,它悄悄地跳过冲突,并使用 select 转到块中
  7. 在这个阶段,字符串A应该已经被提交(或者更准确地说,已经在第5点),这意味着从设备表中选择应该已经看到字符串A插入的行。这保证了我们的读取已提交隔离级别。
  8. 这里带有 union all 的选择块应该返回一行,其中 db_operation = 'GET',即物理表设备中已有的行,由tr-ya A放置在那里

但事实上,第 8 点返回0 行..

该错误最初是在 spring data jdbc 和 jooq 中发现的,但后来我在标准 psql 中多次重现了该错误。这可以通过在begin 中包装 SQL 脚本来完成; ... 犯罪;并在两个独立的 psql 控制台中运行它。但只能在一次写入开始; ...(没有提交;),然后在第二个控制台中使用begin 执行整个脚本; ... 犯罪; 如果您查看锁,您会发现第二个事务将被第一个事务阻止,并且锁在 device_guid 字段的 unicode 索引上可见。但是一旦你输入提交;在第一个控制台中,第二个控制台将完成其交易并返回 0 行。

看上去出现这种情况的原因是,在准备事务A的提交时,首先会释放索引和行上的锁,然后才提交整个事务A。在释放锁和提交之间的这段时间内,事务 B设法执行其代码,而从未看到事务 A提交的行。第三天,我一直在试图了解我是否期待了错误的行为,或者 Postgres 是否表现异常。审查了有关阻塞、CTE 和隔离级别的文档。看来一切都正如我所料的那样。如有任何信息我将不胜感激!

база-данных
  • 1 1 个回答
  • 129 Views

1 个回答

  • Voted
  1. Best Answer
    Victor Sotskov
    2025-02-17T16:58:23Z2025-02-17T16:58:23Z

    使其正常工作的一个选项是在INSERT INTO时使用检查xmax字段的查询(https://postgrespro.ru/docs/postgrespro/9.5/ddl-system-columns)

    with inserting as (
        insert into device(device_guid, platform, created_at)
        values (
            '5f0ca10f-a257-44ac-940e-a808c63a9914'::uuid,
            'WEB',
            NOW()
        )
        ON CONFLICT (device_guid) DO UPDATE
        SET platform = EXCLUDED.platform
        returning *, CASE WHEN xmax = 0 THEN 'INSERT' ELSE 'GET' END AS 
        db_operation
    )
    SELECT * FROM inserting;
    

    如果xmax = 0,则该行已插入但尚未修改。否则,更改交易的 ID 将被写入那里。

    解释分析(插入):

    CTE Scan on inserting  (cost=0.01..0.04 rows=1 width=200) (actual time=0.167..0.172 rows=1 loops=1)
    CTE inserting
    ->  Insert on device  (cost=0.00..0.01 rows=1 width=168) (actual time=0.162..0.165 rows=1 loops=1)
          Conflict Resolution: UPDATE
          Conflict Arbiter Indexes: device_device_guid_uq_idx
          Tuples Inserted: 1
          Conflicting Tuples: 0
          ->  Result  (cost=0.00..0.01 rows=1 width=168) (actual  time=0.030..0.031 rows=1 loops=1)
    Planning Time: 0.135 ms
    Execution Time: 0.313 ms
    

    解释分析(GET)

    CTE Scan on inserting  (cost=0.01..0.04 rows=1 width=200) (actual time=0.093..0.094 rows=1 loops=1)
    CTE inserting
    ->  Insert on device  (cost=0.00..0.01 rows=1 width=168) (actual time=0.090..0.091 rows=1 loops=1)
          Conflict Resolution: UPDATE
          Conflict Arbiter Indexes: device_device_guid_uq_idx
          Tuples Inserted: 0
          Conflicting Tuples: 1
          ->  Result  (cost=0.00..0.01 rows=1 width=168) (actual time=0.009..0.009 rows=1 loops=1)
    Planning Time: 0.059 ms
    Execution Time: 0.153 ms
    
    • 1

相关问题

  • 如何禁用 postgresql 服务器的自动启动

  • DB2 分层路径

  • 使用 peewee 更新列中所需的数组

  • 从 .dump 文件恢复数据库

  • 数据库架构是否有效?

  • 类型不兼容错误 (SQLSTATE 42P08)

Sidebar

Stats

  • 问题 10021
  • Answers 30001
  • 最佳答案 8000
  • 用户 6900
  • 常问
  • 回答
  • Marko Smith

    我看不懂措辞

    • 1 个回答
  • Marko Smith

    请求的模块“del”不提供名为“default”的导出

    • 3 个回答
  • Marko Smith

    "!+tab" 在 HTML 的 vs 代码中不起作用

    • 5 个回答
  • Marko Smith

    我正在尝试解决“猜词”的问题。Python

    • 2 个回答
  • Marko Smith

    可以使用哪些命令将当前指针移动到指定的提交而不更改工作目录中的文件?

    • 1 个回答
  • Marko Smith

    Python解析野莓

    • 1 个回答
  • Marko Smith

    问题:“警告:检查最新版本的 pip 时出错。”

    • 2 个回答
  • Marko Smith

    帮助编写一个用值填充变量的循环。解决这个问题

    • 2 个回答
  • Marko Smith

    尽管依赖数组为空,但在渲染上调用了 2 次 useEffect

    • 2 个回答
  • Marko Smith

    数据不通过 Telegram.WebApp.sendData 发送

    • 1 个回答
  • Martin Hope
    Alexandr_TT 2020年新年大赛! 2020-12-20 18:20:21 +0000 UTC
  • Martin Hope
    Alexandr_TT 圣诞树动画 2020-12-23 00:38:08 +0000 UTC
  • Martin Hope
    Air 究竟是什么标识了网站访问者? 2020-11-03 15:49:20 +0000 UTC
  • Martin Hope
    Qwertiy 号码显示 9223372036854775807 2020-07-11 18:16:49 +0000 UTC
  • Martin Hope
    user216109 如何为黑客设下陷阱,或充分击退攻击? 2020-05-10 02:22:52 +0000 UTC
  • Martin Hope
    Qwertiy 并变成3个无穷大 2020-11-06 07:15:57 +0000 UTC
  • Martin Hope
    koks_rs 什么是样板代码? 2020-10-27 15:43:19 +0000 UTC
  • Martin Hope
    Sirop4ik 向 git 提交发布的正确方法是什么? 2020-10-05 00:02:00 +0000 UTC
  • Martin Hope
    faoxis 为什么在这么多示例中函数都称为 foo? 2020-08-15 04:42:49 +0000 UTC
  • Martin Hope
    Pavel Mayorov 如何从事件或回调函数中返回值?或者至少等他们完成。 2020-08-11 16:49:28 +0000 UTC

热门标签

javascript python java php c# c++ html android jquery mysql

Explore

  • 主页
  • 问题
    • 热门问题
    • 最新问题
  • 标签
  • 帮助

Footer

RError.com

关于我们

  • 关于我们
  • 联系我们

Legal Stuff

  • Privacy Policy

帮助

© 2023 RError.com All Rights Reserve   沪ICP备12040472号-5