这是一个关于 SQL 事务中查询的可序列化性的问题。
具体来说,我正在使用 PostgreSQL。可以假设我使用的是最新版本的 PostgreSQL。根据我所读到的内容,我相信用于支持我正在尝试做的事情的技术被称为“多版本并发控制”或“MVCC”。
总结一下:如果我有一个主表,并且有多个外键链接表连接到该主表,那么对于表中的给定键以及任意数量的 SELECT 语句,我如何保证这一点在一个事务中使用该键,每个事务都从任何链接表中进行选择,我将获得启动事务时存在的数据?
这个问题类似,但范围更广,并且问题和答案与 PostgreSQL 没有具体关系: TransactionisolationandreadingfrommultipletablesonSQLServerExpressandSQLServer2005
假设我有 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
Run Code Online (Sandbox Code Playgroud)
砖厂一次生产一块砖。它制作的砖块在其 4 个象限的每个象限中可能具有不同的颜色。
后来有人分析了砖块以确定它们的颜色组合,并将结果写入brick_colors 表。
其他人分析砖块以确定其重量,并将结果写入brick_weight 表。
在任何给定时间,现有的砖可能有也可能没有记录颜色,并且可能有也可能没有记录重量。
存在一个应用程序,并且该应用程序收到某人想要购买特定砖块的消息(此时应用程序已通过其 brickworks/completion_time 复合键知道)。
应用程序想要在开始查询的确切时间选择砖块的所有已知属性。
如果在事务中添加颜色或重量信息,则应用程序不想知道它。
应用程序想要执行单独的查询(不是具有多个连接到外键链接表的 SELECT,这可能会由于 brick_colors 表而返回多行)。
这个例子故意简单;如果我的示例包括 10 个外键链接表,并且其中许多或全部可以为同一主键返回多行(就像在例如我上面的例子)。
到目前为止,这是我想到的:
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 ;
Run Code Online (Sandbox Code Playgroud)
仅出于确保可串行性的目的而将第一个 SELECT 与 JOIN 一起使用似乎很浪费。
还有其他方法可以做到这一点吗?
这就是你问题的本质:
我如何保证,对于……任意数量的 SELECT 语句……在一个事务内……我将获得启动事务时存在的数据?
这正是可重复读隔离级别所保证的:
可重复读隔离级别只能看到事务开始之前提交的数据;它永远不会看到未提交的数据或并发事务在事务执行期间提交的更改。(但是,查询确实会看到在其自己的事务中执行的先前更新的效果,即使它们尚未提交。)这比 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);
Run Code Online (Sandbox Code Playgroud)
许多表、它们的结构、主键、外键等在这里并不重要。
让我们打开第一个会话,启动可重复读隔离级别,并运行两个简单且单独的 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)
Run Code Online (Sandbox Code Playgroud)
请注意,该START TRANSACTION
命令会自动禁用会话中的自动提交模式。
现在在另一个会话中(启用默认自动提交模式)将一些记录插入t1
:
test2=# INSERT INTO t1 VALUES(10),(11);
Run Code Online (Sandbox Code Playgroud)
新值已插入并自动提交(因为自动提交已打开)。
现在返回到第一个会话并再次运行 SELECT: test=# select * from t1;
x
---
1
2
3
(3 wiersze)
Run Code Online (Sandbox Code Playgroud)
如您所见,session1(具有活动的可重复读取事务)在事务开始后看不到提交的任何更改。
让我们在表格中进行相同的实验t2
- 进入第二个会话并发出:
test2=# DELETE FROM t2 WHERE y = 2;
DELETE 1
Run Code Online (Sandbox Code Playgroud)
现在返回第一个会话并再次运行 SELECT:
test=# SELECT * FROM t2;
y
---
1
2
3
(3 wiersze)
Run Code Online (Sandbox Code Playgroud)
正如您所看到的,session1(具有活动的可重复读取事务)在事务开始后看不到提交的任何更改。
现在,在 session1 中,发出 COMMIT 来完成事务,然后选择:
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)
Run Code Online (Sandbox Code Playgroud)
如您所见,当可重复读事务启动并处于活动状态时,您可以多次运行许多单独的 select 语句,并且所有这些 select 语句都会看到截至事务开始时相同的稳定数据快照,无论任何提交的数据如何在其他会议中。
归档时间: |
|
查看次数: |
3767 次 |
最近记录: |