【发布时间】:2011-07-23 15:04:34
【问题描述】:
使用“可重复读取”应该可以产生幻读,但是如何实现呢?我需要它作为教授 CS 学生的示例。
我认为我必须在非索引字段 x 上创建一个“SELECT ... WHERE x
除非它不起作用。我需要一张很大的桌子吗?还是别的什么?
【问题讨论】:
标签: mysql sql database isolation-level
使用“可重复读取”应该可以产生幻读,但是如何实现呢?我需要它作为教授 CS 学生的示例。
我认为我必须在非索引字段 x 上创建一个“SELECT ... WHERE x
除非它不起作用。我需要一张很大的桌子吗?还是别的什么?
【问题讨论】:
标签: mysql sql database isolation-level
MySQL 中 RR 隔离级别的“幻读”隐藏得很深,但仍然可以重现它。步骤如下:
创建表 ab(a int primary key, b int);
Tx1:
开始;
从 ab 中选择 *; // 空集
【讨论】:
埃里克,
我只是通过大量行来测试它。
您将永远不会在 InnoDB mysql 上找到具有已提交读取或更受限制的隔离级别的幻象。文档中有解释:
REPEATABLE READ:对于一致性读取,与 READ COMMITTED 隔离级别有一个重要区别:同一事务中的所有一致性读取读取第一次读取建立的快照。此约定意味着如果您在同一事务中发出多个普通(非锁定)SELECT 语句,则这些 SELECT 语句彼此之间也是一致的。请参阅第 13.6.8.2 节,“一致的非锁定读取”。
但您也不能在已提交读隔离级别中找到幻像:这是必要的,因为必须阻止“幻像行”才能使 MySQL 复制和恢复正常工作。
更多详细信息:http://dev.mysql.com/doc/refman/5.1/en/set-transaction.html
我认为您需要转移到另一个数据库品牌才能向您的学生展示幻影。我同时使用MSSQLSERVER 和Oracle。
嗯……很遗憾你的第一个问题。
【讨论】:
InnoDB 应该像其他人写的那样防止幻读。
但是 InnoDB 有一个不同的与锁定相关的奇怪行为。当查询获取锁时,它总是获取行的最新版本上的锁。所以试试下面的
CREATE TABLE foo (i INT PRIMARY KEY, val INT);
INSERT INTO foo (i, val) VALUES (1, 10), (2, 20), (3, 30);
然后在两个并发会话中(打开两个终端窗口):
-- window 1 -- window 2
START TRANSACTION;
START TRANSACTION;
SELECT * FROM foo;
UPDATE foo SET val=35 WHERE i=3;
SELECT * FROM foo;
这应该在两个 SELECT 中显示 val = 10, 20, 30,因为 REPEATABLE-READ 意味着第二个窗口只看到其事务开始时存在的数据。
但是:
SELECT * FROM foo FOR UPDATE;
第二个窗口等待获取第 3 行的锁。
COMMIT;
现在第二个窗口中的 SELECT 完成,并显示 val = 10、20、35 的行,因为锁定该行会导致 SELECT 看到最近提交的版本。 InnoDB 中的锁定操作就像它们在 READ-COMMITTED 下运行一样,无论事务的隔离级别如何。
你甚至可以来回切换:
SELECT * FROM foo;
SELECT * FROM foo FOR UPDATE;
SELECT * FROM foo;
SELECT * FROM foo FOR UPDATE;
【讨论】:
对于隔离级别 REPEATABLE READ 为 InnoDB 引擎重现幻读的可能性值得怀疑,因为 InnoDB 使用Multiversion concurrency control - 对于每一行,MVCC 引擎在插入和删除行时知道事务编号,并且可以重现行更新的历史记录。
因此,所有后续的 SELECT 语句将在事务开始时显示表的状态,但由同一事务插入、删除或更新的行除外。不会出现其他事务提交的新行,因为它们的插入事务数会比这个事务大,而行的范围在这里没有问题。
我能够为 Apache Derby 数据库的隔离级别 REPEATABLE READ 重现 PHANTOM READS,因为它不使用多版本并发控制(在撰写此答案时为 10.8.2.2 版)。
要重现,请设置适当的事务级别(在 ij - Derby 的 SQL 客户端中):
-- Set autocommit off
autocommit off;
-- Set isolation level corresponding to ANSI REPEATABLE READ
set isolation rs;
T1:
SELECT * FROM TableN;
T2:
INSERT INTO TableN VALUES(55, 1);
COMMIT;
再次T1:
SELECT * FROM TableN;
现在 T1 应该再看到一行;
【讨论】:
可能会发生幻读,因为不存在范围锁,那么一个例子是(伪代码):
线程1
交易一 更新 TableN 设置 X=2 其中 X=1 等待(s1) 选择 X=1 的 TableN 犯罪线程2
交易2: 插入表N(id,X)值(55,1) 犯罪; 通知(s1)在维基百科中还有另一个幻读示例:Phantom Reads|wikipedia
这里重要的是事务同步,你可以使用同步点。
编辑 使用 mysql 睡眠功能的示例(未测试):
--on thread 1
Create TableN(id int, x int);
insert into TableN(id, X) values(1,1);
insert into TableN(id, X) values(2,1);
insert into TableN(id, X) values(3,1);
开始交易; 更新 TableN 设置 X=2 其中 X=1 从 DUAL 中选择 SLEEP(30); 从中选择 X=1 的 TableN; 犯罪;
--在其他线程中,20秒前;
开始交易; 插入 TableN(id, X) 值(55,1);
提交;
【讨论】:
为了补充 Dani 的好答案,您可以使用 Microsoft Sql Server 向您的学生展示这种行为。
Sql Server 在文档here 声明的可重复读取隔离级别中显示幻读。
Postgres 订阅与 InnoDb 相同的概念,如 here 所述。使用 Postgres,在可重复读取中不会发生幻读,因此也不适合您的教学目的。
Sql Server 提供了另一种隔离级别,即快照,它执行 MySql InnoDb 和 Postgres 在可重复读取中所做的工作(这是一种无锁、基于版本的可重复读取实现,无需幻读,但不可序列化)。
Sql Server Express 是免费的,但您确实需要一台 Windows 机器。您还可以为自己获取一个 Windows Azure 帐户,并在线使用 Sql Azure 展示该行为。
【讨论】: