如何产生“幻读”在可重复读取中? (MySQL)
使用“可重复读”,应该可以产生幻读,但是如何产生呢?我需要它作为教授计算机科学学生的示例。
我认为我必须在非索引字段 x 上创建一个“SELECT ... WHERE x<=888”,上限 888 不存在,然后在另一个连接上插入一个值略低于 888 的新行
。它不起作用。我需要一张非常大的桌子吗?或者其他什么?
Using "repeatable read", it should be possible to produce a phantom read, but how? I need it for an example teaching CS-students.
I think that I must make a "SELECT ... WHERE x<=888" on a non-indexed field x, with an upperlimit 888 not present, and then on another connection insert a new row with a value just below 888.
Except it doesn't work. Do I need a very large table? Or something else?
如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。
绑定邮箱获取回复消息
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
发布评论
评论(9)
MySQL RR 隔离级别上的“幻读”隐藏得很深,但仍然可以重现。步骤如下:
创建表 ab(a int 主键, b int);
Tx1:
开始;
从ab中选择*; // 空集
开始;
插入 ab 值(1,1);
犯罪;
从ab中选择*; // 空集,预期的幻像读取丢失。
更新 ab 集 b = 2,其中 a = 1; // 1 行受影响。
从ab中选择*; // 1 行。 幻象读到这里!!!
提交;
The "phantom read" in MySQL on RR isolation level is hidden deep, but still can reproduce it. Here are the steps:
create table ab(a int primary key, b int);
Tx1:
begin;
select * from ab; // empty set
begin;
insert into ab values(1,1);
commit;
select * from ab; // empty set, expected phantom read missing.
update ab set b = 2 where a = 1; // 1 row affected.
select * from ab; // 1 row. phantom read here!!!!
commit;
埃里克,
我刚刚用大量的行进行了测试。
您永远不会在 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。
嗯……你的第一个问题很遗憾。
Erik,
I come just from test it with a very large number of rows.
You will never found phantoms on InnoDB mysql with read commited or more restricted isolation level. It is explained on documentation:
REPEATABLE READ: For consistent reads, there is an important difference from the READ COMMITTED isolation level: All consistent reads within the same transaction read the snapshot established by the first read. This convention means that if you issue several plain (nonlocking) SELECT statements within the same transaction, these SELECT statements are consistent also with respect to each other. See Section 13.6.8.2, “Consistent Nonlocking Reads”.
But you can't also found phantoms in read commited isolation level: This is necessary because “phantom rows” must be blocked for MySQL replication and recovery to work.
More detailed information: http://dev.mysql.com/doc/refman/5.1/en/set-transaction.html
I think you will need to move to another database brand to show phantoms to your students. I use both MSSQLSERVER and Oracle.
Well ... its a pity for your first question.
正如其他人所写的那样,InnoDB 应该防止幻读。
但 InnoDB 有一个与锁定相关的不同奇怪行为。当查询获取锁时,它始终获取该行的最新版本的锁。因此,请在两个并发会话中尝试以下“
Then”(打开两个终端窗口):
这应该在两个 SELECT 中显示 val = 10, 20, 30,因为 REPEATABLE-READ 意味着第二个窗口只能看到事务启动时存在的数据。
但是:
第二个窗口等待获取第 3 行上的锁。
现在第二个窗口中的 SELECT 完成,并显示 val = 10, 20, 35 的行,因为锁定该行会导致 SELECT 看到最新提交的版本。 InnoDB 中的锁定操作就像在 READ-COMMITTED 下运行一样,无论事务的隔离级别如何。
您甚至可以来回切换:
InnoDB should protect against phantom reads, as others have written.
But InnoDB has a different weird behavior related to locking. When a query acquires a lock, it always acquires the lock on the most recent version of the row. So try the following
Then in two concurrent sessions (open two terminal windows):
This should show val = 10, 20, 30 in both SELECTs, since REPEATABLE-READ means the second window sees only the data as it existed when its transaction started.
However:
The second window waits to acquire the lock on row 3.
Now the SELECT in the second window finishes, and shows rows with val = 10, 20, 35, because locking the row causes the SELECT to see the most recent committed version. Locking operations in InnoDB act like they are run under READ-COMMITTED, regardless of the transaction's isolation level.
You can even switch back and forth:
对于隔离级别 REPEATABLE READ 的 InnoDB 引擎重现幻读的可能性是值得怀疑的,因为 InnoDB 使用 多版本并发控制 - 对于每一行,MVCC 引擎都知道插入和删除行时的事务号,并且可以重现行更新的历史记录。
因此,所有后续的 SELECT 语句都将显示事务开始时表的状态,但由同一事务插入、删除或更新的行除外。不会出现其他事务提交的新行,因为它们的插入事务编号会大于该事务的插入事务编号,并且行的范围在这里无关紧要。
我能够为 Apache Derby 数据库重现隔离级别 REPEATABLE READ 的 PHANTOM READS ,因为它不使用多版本并发控制(在撰写本答案时版本为 10.8.2.2)。
要重现,请设置适当的事务级别(在 ij - Derby 的 SQL 客户端中):
T1:
T2:
T1 再次:
现在 T1 应该看到多一行;
Possibility to reproduce phantom reads for InnoDB engine for isolation level REPEATABLE READ is questionable, because InnoDB uses Multiversion concurrency control - for every row MVCC engine knows transaction numbers when row was inserted and deleted and can reproduce history of row updates.
So, all consequent SELECT statements will show state of table in the beginning of transaction, except for rows that were inserted, deleted or updated by same this transaction. No new rows committed by other transactions will appear, because they will have insertion transaction numbers greater that of this transaction, and range of rows has no matter here.
I was able to reproduce PHANTOM READS for isolation level REPEATABLE READ for Apache Derby database, because it does not use multiversion concurrency control (version 10.8.2.2 in the moment of writing of this answer).
To reproduce, set proper transaction level (in ij - Derby's SQL client):
T1:
T2:
T1 again:
Now T1 should see one more row;
您可以在MySQL中的
REPEATABLE READ
中产生幻读。首先,设置
REPEATABLE READ
:然后,使用
id
person
表> 和
名称
如下所示。person
表:然后,取 以下步骤使用 MySQL 查询。 *我使用了2个命令提示符:
BEGIN;
BEGIN;
SELECT * FROM person;
约翰一书
大卫二书
INSERT INTO person VALUES (3, 'Tom');
3
和Tom
到person
表。COMMIT;
SELECT * FROM person;
约翰一书
大卫二书
*暂时不会发生幻读!!
更新人员集 name = 'Lisa',其中 id = 3;
Tom
更新为Lisa
。从人中选择*;
1 约翰
2 大卫
3丽莎
*发生幻像读取!!
提交;
此外,我在 Postgresql 中的
REPEATABLE READ
中执行了上述步骤,但幻读 没有发生。You can produce phantom read in
REPEATABLE READ
in MySQL.First, set
REPEATABLE READ
:Then, create
person
table withid
andname
as shown below.person
table:Then, take these steps below with MySQL queries. *I used 2 command prompts:
BEGIN;
BEGIN;
SELECT * FROM person;
1 John
2 David
INSERT INTO person VALUES (3, 'Tom');
3
andTom
toperson
table.COMMIT;
SELECT * FROM person;
1 John
2 David
*Phantom read doesn't occur for now!!
UPDATE person set name = 'Lisa' where id = 3;
Tom
toLisa
.SELECT * FROM person;
1 John
2 David
3 Lisa
*Phantom read occurs!!
COMMIT;
In addition, I did these steps above in
REPEATABLE READ
in Postgresql but phantom read didn't occur.由于不存在范围锁,可能会发生幻读,示例如下(伪代码):
Thread1
thread2
在维基百科中,还有另一个幻读示例: Phantom Reads|wikipedia
这里重要的是事务同步,您可以使用同步点。
编辑使用 mysql sleep 函数的示例(未测试):
Phantom reads can occur because not range-locks exist, then an example is (pseudocode):
Thread1
thread2
In wikipedia there are another example of phantom reads: Phantom Reads|wikipedia
The important thing here is the transactions syncronization, you can use sync points.
EDIT Example using mysql sleep function(not tested):
为了补充 Dani 的良好答案,您可以使用 Microsoft Sql Server 向学生展示该行为。
Sql Server 在可重复读隔离级别中显示幻读,如文档此处。
Postgres 遵循与 InnoDb 相同的概念,如此处所述。对于 Postgres 来说,可重复读取中也不会发生幻读,因此也不适合您的教学目的。
Sql Server 提供了另一种隔离级别——快照,它的作用类似于 MySql InnoDb 和 Postgres 在可重复读方面的作用(这是一种无锁、基于版本的可重复读实现,没有幻读,但不可序列化)。
尽管您确实需要 Windows 计算机,但 Sql Server Express 是免费的。您还可以为自己获取一个 Windows Azure 帐户,并通过 Sql Azure 在线展示该行为。
To complement Dani's good answer, you could use Microsoft Sql Server to show that behavior to your students.
Sql Server shows phantom reads in the repeatable read isolation level as claimed by the documentation here.
Postgres subscribes to the same notion as InnoDb as explained here. With Postgres too, no phantom reads happen in repeatable read and is thus also unsuited for your didactic purpose.
Sql Server offers another isolation level, snapshot, that does what MySql InnoDb and Postgres does in repeatable read (which is a lock-free, version-based implementation of repeatable read without phantom reads, but is not serializable).
Sql Server Express is free although you do need a Windows machine. You could also get yourself a Windows Azure account and show that behavior with Sql Azure online.
mysql使用一致性非锁定读取来避免幻读,这意味着如果您运行查询,mysql将使用快照来呈现您的查询。基于撤消日志。
但有一个例外,如果您在此事务中更新一行,您将看到该行的最新版本,因此您可以使用@ColinBinWang提供的方法产生幻读。
如果您想要更详细的解释,请参阅 https:// /dev.mysql.com/doc/refman/8.0/en/innodb-concient-read.html
mysql use Consistent Nonlocking Read to avoid phantom read,it mean that if you run a query,mysql will use a snapshot to present your query. base on undo log.
but there is an exception,if you update a row in this transactions,you will see the latest version of this row,so you can produce a phantom read use the method @ColinBinWang provide.
if you want a more detailed explanation, see https://dev.mysql.com/doc/refman/8.0/en/innodb-consistent-read.html
尝试了解最高评分答案的根本原因。
为了防止幻读,InnoDB中的RR级别实际上是通过MVCC代表undo日志来实现的,该机制也称为一致性读或快照读。
撤消日志的工作方式就像一条链,用于更新其事务 ID。因此,当另一个事务成功插入记录 A(已提交)时,然后可以执行对 A(在其他事务中)的更新,因为撤消日志现在可以找到该条目。此外,由于记录 A 上没有获取锁,因此更新不会被阻止。总的来说,这在 RR 隔离语义中是完全可以接受的。
似乎只有通过锁定读取来防止光子读取才是安全的,例如使用涉及下一个键锁定的“SELECT ... FOR UPDATE”。
Trying to understand the essential reason for the top rating answer.
To prevent phantom read, RR level in InnoDB is actually implemented through MVCC on behalf of the undo logs, and the mechenism is also known as Consistent Read or Snapshot Read.
The undo log works like a chain for updates with their transaction ids. So when another transaction successfully insert a record A (committed) and then the update upon A (in other transaction) could be performed afterwards since the undo log could find the entry now. Besides, the updates won't be blocked since there is no lock accquired on record A. Overall, it's totally acceptable in RR isolation semantic.
Seems it's only safe to prevent photom read through Lock Read, e.g. using "SELECT ... FOR UPDATE" which involves next-key lock.