使用“可重复读取”应该可以产生幻像读取,但是如何?我需要一个教学CS学生的例子。
我认为我必须在非索引字段x上创建一个“SELECT ... WHERE x< = 888”,上限888不存在,然后在另一个连接上插入一个新行,其值恰好在下面888。
除非它不起作用。我需要一张非常大的桌子吗?或其他什么?
答案 0 :(得分:15)
埃里克
我来自测试它的行数非常多。
你永远不会在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。
嗯......对你的第一个问题很遗憾。
答案 1 :(得分:8)
"幻像阅读"在MySQL上,RR隔离级别隐藏得很深,但仍然可以重现它。以下是步骤:
create table ab(一个int主键,b int);
TX1:
开始;
从ab中选择*; //空集
答案 2 :(得分:4)
为隔离级别重现InnoDB引擎的幻像读取的可能性REPEATABLE READ是值得怀疑的,因为InnoDB使用Multiversion concurrency control - 对于每一行,MVCC引擎在插入和删除行时都知道事务编号,并且可以重现行更新的历史记录。
因此,所有后续的SELECT语句都将在事务开始时显示表的状态,但同一个事务插入,删除或更新的行除外。不会出现其他事务提交的新行,因为它们的插入事务编号大于此事务的插入事务编号,并且行的范围无关紧要。
我能够为Apache Derby数据库重现PHANTOM READS的隔离级别REPEATABLE READ,因为它不使用多版本并发控制(在编写此答案的时刻版本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应该再看一行;
答案 3 :(得分:2)
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;
答案 4 :(得分:0)
可能会发生幻像读取,因为不存在范围锁,那么一个例子是(伪代码):
线程1
Transaction 1 Update TableN set X=2 where X=1 wait(s1) Select TableN where X=1 Commit
线程2
Transaction 2: insert into tableN(id, X) values(55,1) commit; notify(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);
BEGIN TRANSACTION;
Update TableN set X=2 where X=1
SELECT SLEEP(30) FROM DUAL;
select TableN from where X=1;
COMMIT;
--In other thread, before 20 secs;
BEGIN TRANSACTION;
insert into TableN(id, X) values(55,1);
COMMIT;
答案 5 :(得分:0)
为了补充Dani的好答案,您可以使用Microsoft Sql Server向学生展示这种行为。
Sql Server按文档here声明的可重复读隔离级别显示幻像读取。
Postgres订阅与InnoDb相同的概念,如here所述。使用Postgres时,在可重复读取时不会发生幻像读取,因此也不适合您的说教目的。
Sql Server提供另一个隔离级别,即快照,它可以执行MySql InnoDb和Postgres在可重复读取中执行的操作(这是一个无锁,基于版本的可重复读取实现,没有幻像读取,但不可序列化)。
虽然您确实需要Windows机器,但Sql Server Express是免费的。您还可以获得一个Windows Azure帐户,并在线显示Sql Azure的行为。