【问题标题】:Transaction Isolation Across Multiple Tables using PostgreSQL MVCC使用 PostgreSQL MVCC 跨多个表的事务隔离
【发布时间】:2017-07-08 06:10:30
【问题描述】:

问题总结

这是一个关于 SQL 事务中查询的可序列化的问题。

具体来说,我使用的是 PostgreSQL。可以假设我使用的是最新版本的 PostgreSQL。根据我的阅读,我相信用于支持我正在尝试做的事情的技术被称为“多版本并发控制”或“MVCC”。

总结一下:如果我有一个主表,并且有多个外键链接表连接到该主表,我如何保证,对于表中的给定键,以及任何数字在一个事务中使用该键的 SELECT 语句,每个语句都从任何链接表中进行 SELECT,我将获取启动事务时存在的数据?

其他问题

这个问题类似,但范围更广,问题和答案并没有专门与 PostgreSQL 相关: Transaction isolation and reading from multiple tables on SQL Server Express and SQL Server 2005

示例

假设我有 3 张桌子:

bricks
    brickworks (primary key)
    completion_time (primary key)
    has_been_sold

brick_colors
    brickworks (primary key, foreign key pointing to "bricks")
    completion_time (primary key, foreign key pointing to "bricks")
    quadrant (primary key)
    color

brick_weight
    brickworks (primary key, foreign key pointing to "bricks")
    completion_time (primary key, foreign key pointing to "bricks")
    weight

砖厂一次生产一块砖。它在其 4 个象限中的每个象限中制作可能具有不同颜色的砖块。

稍后有人分析积木以确定它们的颜色组合,并将结果写入brick_colors 表。

其他人分析积木以确定其重量,并将结果写入brick_weight 表。

在任何给定时间,现有的砖块可能有也可能没有记录的颜色,可能有也可能没有记录的重量。


应用程序存在,并且该应用程序收到有人想要购买特定砖块的消息(此时应用程序已通过其砖块工厂/completion_time 复合键知道)。

应用程序想要在它开始查询的确切时间选择砖块的所有已知属性。

如果在 MID-TRANSACTION 中添加颜色或重量信息,应用程序不想知道它。

应用程序想要执行 SEPARATE QUERIES(而不是具有多个 JOIN 到外键链接表的 SELECT,这可能会因为 brick_colors 表而返回多行)。


这个例子故意简单;如果我的示例包括 10 个外键链接表,并且它们中的许多或全部可以为同一个主键返回多行(就像 brick_colors 在上面的例子)。

尝试的解决方案

这是我到目前为止的想法:

BEGIN TRANSACTION ISOLATION LEVEL SERIALIZABLE READ ONLY ;

-- All this statement accomplishes is telling the database what rows should be returned from the present point-in-time in future queries within the transaction
SELECT DISTINCT true
FROM bricks b
LEFT JOIN brick_colors bc ON bc.brickworks = b.brickworks AND bc.completion_time = b.completion_time
LEFT JOIN brick_weight bw ON bw.brickworks = b.brickworks AND bw.completion_time = b.completion_time
WHERE b.brickworks = 'Brick-o-Matic' AND b.completion_time = '2017-02-01T07:35:00.000Z' ;

SELECT * FROM brick_colors WHERE b.brickworks = 'Brick-o-Matic' AND b.completion_time = '2017-02-01T07:35:00.000Z' ;
SELECT * FROM brick_weight WHERE b.brickworks = 'Brick-o-Matic' AND b.completion_time = '2017-02-01T07:35:00.000Z' ;

COMMIT ;

仅出于确保可序列化的目的而将第一个 SELECT 与 JOIN 一起使用似乎很浪费。

还有其他方法吗?

参考文献

PostgreSQL Concurrency Control

PostgreSQL Transcation Isolation

PostgreSQL SET TRANSACTION statement

【问题讨论】:

    标签: postgresql mvcc


    【解决方案1】:

    这是你问题的本质:

    我如何保证,对于......任意数量的 SELECT 语句 .....在一笔交易中......我将得到数据,因为它存在于 我开始交易的时间


    这正是Repeatable Read Isolation Level 所保证的:

    Repeatable Read 隔离级别仅查看之前提交的数据 交易开始;它永远不会看到未提交的数据或 并发在事务执行期间提交的更改 交易。(但是,查询确实看到了之前的效果 更新在它自己的事务中执行,即使它们不是 尚未承诺。)这是一个比 此隔离级别的 SQL 标准,并防止所有 表 13-1 中描述的现象。如上所述,这是 标准特别允许,仅描述了最低限度 每个隔离级别必须提供的保护。

    这个级别不同于已提交读,因为在一个查询中 可重复读取事务在开始时看到快照 事务,而不是在当前查询开始时 交易。因此,在一个单一的连续 SELECT 命令 交易看到相同的数据,即他们看不到由 在自己的事务开始后提交的其他事务。


    一个实际的例子 - 假设我们有 2 个简单的表格:

    CREATE TABLE t1( x int );
    INSERT INTO t1 VALUES (1),(2),(3);
    CREATE TABLE t2( y int );
    INSERT INTO t2 VALUES (1),(2),(3);
    

    许多表、它们的结构、主键、外键等在这里并不重要。

    让我们打开第一个会话,启动可重复的读取隔离级别,然后运行两个简单且单独的 SELECT 语句:

    test=# START TRANSACTION ISOLATION LEVEL REPEATABLE READ;
    START TRANSACTION
    test=# SELECT * FROM t1;
     x
    ---
     1
     2
     3
    (3 wiersze)
    
    
    test=# SELECT * FROM t2;
     y
    ---
     1
     2
     3
    (3 wiersze)
    

    请注意,START TRANSACTION 命令会自动禁用会话中的自动提交模式。


    现在在另一个会话中(启用默认自动提交模式)将一些记录插入t1

    test2=# INSERT INTO t1 VALUES(10),(11);
    

    新值被插入并自动提交(因为自动提交已开启)。


    现在回到第一个会话并再次运行 SELECT: test=# select * from t1;

     x
    ---
     1
     2
     3
    (3 wiersze)
    

    如您所见,会话 1(具有活动的可重复读取事务)在事务开始后看不到任何提交的更改。


    让我们在表t2 上做同样的实验 - 转到第二个会话并发出:

    test2=# DELETE FROM t2 WHERE y = 2;
    DELETE 1
    

    现在回到第一个会话并再次运行 SELECT:

    test=# SELECT * FROM t2;
     y
    ---
     1
     2
     3
    (3 wiersze)
    

    如您所见,session1(具有活动的可重复读取事务)在事务开始后看不到任何提交的更改。


    现在,在 session1 中,完成事务发出 COMMIT,然后 SELECT:

    test=# SELECT * FROM t1;
     x
    ---
     1
     2
     3
    (3 wiersze)
    
    test=# SELECT * FROM t2;
     y
    ---
     1
     2
     3
    (3 wiersze)
    
    test=# COMMIT;
    COMMIT
    
    test=# select * from t1;
     x
    ----
      1
      2
      3
     10
     11
    (5 wierszy)
    
    
    test=# select * from t2;
     y
    ---
     1
     3
    (2 wiersze)
    

    如您所见,当可重复读取事务启动并处于活动状态时,您可以多次运行多个单独的 select 语句,并且所有这些 select 语句都看到与事务开始时相同的稳定数据快照,无论在其他会话中提交的任何数据。

    【讨论】:

    • 谢谢!我发现我在我的问题中做出了错误的假设。我认为你完全正确,“可重复阅读”是这里的方式。我现在也同意表的数量以及它们如何链接是无关紧要的——我只是因为我的错误假设才认为它是相关的。我要提出另一个问题,你的回答帮助我找到了这个问题。再次感谢您的详细回复!
    猜你喜欢
    • 1970-01-01
    • 2014-03-07
    • 1970-01-01
    • 2022-10-14
    • 2018-02-05
    • 2017-07-08
    • 1970-01-01
    • 2018-10-23
    • 2015-03-26
    相关资源
    最近更新 更多