使用读提交隔离级别,SELECT 能否在一个事务内获得不同的结果?

问题描述 投票:0回答:3

这里是 postgresql 9.6 文档关于读提交隔离级别的完整段落:

已提交读是 PostgreSQL 中的默认隔离级别。当一个 事务使用此隔离级别,一个 SELECT 查询(不带 FOR UPDATE/SHARE 子句)只能看到查询开始之前提交的数据; 它永远不会看到未提交的数据或在期间提交的更改 通过并发事务执行查询。实际上,一个 SELECT 查询 看到查询开始那一刻的数据库快照 跑步。但是,SELECT 确实会看到先前执行的更新的效果 在它自己的事务中,即使它们尚未提交。 另请注意,两个连续的 SELECT 命令可以看到不同的数据, 即使它们在单个事务中,如果其他 事务在第一个 SELECT 开始之后和之前提交更改 第二个 SELECT 开始。


所以基本上:

SELECT 查询只能看到查询开始之前提交的数据,而永远看不到查询执行期间提交的更改 并发交易。

但最后一句指出:

另请注意,两个连续的 SELECT 命令可以看到不同的数据, 即使它们在单个事务中,如果其他 事务在第一个 SELECT 开始之后和之前提交更改 第二个 SELECT 开始。

对我来说这看起来很矛盾。有人可以详细说明一下吗?两个 SELECT 查询究竟如何在一个事务中看到不同的数据?交易不是隔离的吗?

sql postgresql transactions
3个回答
4
投票

是的,确实如此。为了避免这种情况,您需要使用更高的隔离级别:“可重复读”。 如果您需要完全隔离事务,甚至可以“可序列化”。 请记住,更高的隔离度会带来更高的性能成本。

在这里您可以找到详细的解释:https://www.postgresql.org/docs/9.1/static/transaction-iso.html

这是一个例子,它是如何发生的:

  1. Connection1 打开事务并从 Table1 中读取一行 “选择*”
  2. Connection2 更新同一行并提交

  3. Connection1 再次读取同一行并获取更新的数据

隔离级别是“读已提交”,因此实际上提交的所有内容都对其他连接可见。

如果由于某种原因无法使用更高的隔离级别,有一种方法可以防止发生这种“意外”更新:您的 Connection1 可以使用“select ... for update”来代替。这将有效地锁定该行,直到 Connection1 的事务提交或回滚。因此 Connection2 将等待此提交或回滚才能更新该行。


2
投票

不存在矛盾。事务中两个连续的 SELECT 语句可能会获取不同的结果。考虑这一点 - 您开始交易,然后发行
从雇员中选择*; 你得到 2 条记录。 另一个会话将记录插入 emp 并提交。 在第一个会话中,您再次发出 从雇员中选择*; 您获得 3 条记录。这是 READ COMMITTED 隔离级别下的预期行为。

示例代码

tmp=# begin ; 
BEGIN
tmp=# select * from emp;
 id 
----
  1
  2
(2 rows)

tmp=# select * from emp;
 id 
----
  1
  2
  3
(3 rows)

tmp=# commit;

0
投票

在提出这个问题 6 年后在这里添加答案,因为我来到这里试图理解为什么 Postgres 文档似乎自相矛盾。

这里造成混乱的根源在于使用了querytransaction这两个词。

文档指出,当

SELECT
query 启动时,它只会看到在 query 开始之前提交的数据。请注意,它仅指
SELECT
查询本身,而不是整个 交易。一笔交易可能包含多个查询。

因此,假设您在具有读提交隔离级别的事务中运行下面的查询:

SELECT name FROM users;

假设

users
表非常大,并且您的查询非常复杂,因此需要一段时间才能完成。当
SELECT
运行时,其他一些事务会执行此操作:

BEGIN;
INSERT INTO users(name) VALUES ('John');
COMMIT;

Postgres 文档所说的是,您的第一个

SELECT
永远不会在结果中包含
'John'
,因为查询在第二个事务提交之前开始。如果您开始新查询(即使您处于同一个交易中),那么您将在结果中看到
'John'

如果在事务期间无法让

'John'
显示在任何
SELECT
中,则需要使用更高的隔离级别(以降低性能为代价)。

© www.soinside.com 2019 - 2024. All rights reserved.