我在MySQL上用SERIALIZABLE进行了不可重复读取和幻影读取是否发生的实验,但出乎我的意料,SERIALIZABLE确实发生了不可重复读取和幻影读取。
以下是不可重复读和幻影读的定义:
- 不可重复读(模糊读)**是指在事务过程中对提交的更新数据的读取。
- Phantom Read**是指在事务过程中对已提交的插入或删除数据的读取。
对于我的实验,我设置了SERIALIZABLE全局和仅会话,如下所示:
mysql> SELECT @@GLOBAL.transaction_isolation, @@SESSION.transaction_isolation;
+--------------------------------+---------------------------------+
| @@GLOBAL.transaction_isolation | @@SESSION.transaction_isolation |
+--------------------------------+---------------------------------+
| SERIALIZABLE | SERIALIZABLE |
+--------------------------------+---------------------------------+
并且默认开启自动提交,如下图所示:
mysql> SELECT @@autocommit;
+--------------+
| @@autocommit |
+--------------+
| 1 |
+--------------+
其中,InnoDB默认设置如下:
mysql> SELECT TABLE_NAME, ENGINE FROM information_schema.TABLES WHERE TABLE_SCHEMA = 'test';
+------------+--------+
| TABLE_NAME | ENGINE |
+------------+--------+
| person | InnoDB |
+------------+--------+
并且,我使用了**“Person”表和“id”和“name”**,如下所示:
Id|名称
1|John
2|大卫
首先,对于不可重复读取,我使用MySQL查询执行了以下这些步骤。*我使用了MySQL 8.0.30版和2个命令提示符**:
Flow|Transaction 1(T1)|Transaction 2(T2)|说明
-|-|
第一步|BEGIN;
||T1启动。
第二步||BEGIN;
|T2启动。
Step 3|UPDATE person SET name = 'Tom' WHERE id = 2;
||**T1将David更新为Tom
|第四步||SELECT * FROM person WHERE id = 2;
|t2无法读取id为2
的Person表。T2需要等待T1提交。|
第五步|COMMIT;
|正在等待...|T1提交。
Step 6||SELECT * FROM person WHERE id = 2;
2 Tom|现在,T2可以读取PERSON表,其中id为2,T2读取Tom而不是David。#发生不可重复读取!!
Step 7||COMMIT;
|T2提交。
第二,对于Pantom Read,我使用MySQL查询执行了以下步骤。*我使用了MySQL 8.0.30版和2个命令提示符:
Flow|Transaction 1(T1)|Transaction 2(T2)|说明
-|-|
第一步|BEGIN;
||T1启动。
第二步||BEGIN;
|T2启动。
第三步|INSERT INTO person VALUES (3, 'Tom');
||t1将3
和Tom
所在行插入到Person表中。
|第四步||SELECT * FROM person;
|T2无法读取Person表。T2正在等待T1提交。|
第五步|COMMIT;
|正在等待...|T1提交。
Step 6||SELECT * FROM person;
1 John2 David3 Tom|T2现在可以读取Person表,而T2读取的是3行而不是2行。#幻影读取!!
Step 7||COMMIT;
|T2提交。
那么,在MySQL上用SERIALIZABLE阻止不可重复读和幻影读是不可能的吗?
2条答案
按热度按时间j8ag8udp1#
这是InnoDB锁定读取的副作用。锁定读取总是读取最近提交的数据,因此它们的工作方式就像您使用了读提交隔离级别一样。因此,给定事务(例如上面的T2事务)中的数据视图可能会查看事务开始时未提交的新数据。
InnoDB通过隐式地使每个
SELECT
查询都成为锁定读取来实现SERIALIZABLE,就好像您已经使用了FOR SHARE
(在MySQL8.0之前,相同的子句被称为LOCK IN SHARE MODE
)。因此,所有查询都是锁定读的,并且总是以读提交的方式查询。不幸的是,这与文档中关于SERIALIZABLE类似于可重复读取的说法相冲突。它不是,除非您使用自动提交模式,因此
SELECT
是非锁定的,因为已知它处于只读事务中。这是InnoDB中的设计。不,如果不切换到不同的存储引擎或不同品牌的数据库,就无法阻止它。
还有另一种方法可以获得真正的可序列化事务:使用显式
LOCK TABLES
语句实现悲观锁定。这将确保一次只有一个事务可以访问表,但会阻碍并发性。请回复您的评论:
我不知道有哪种存储引擎可用于MySQL,以支持事务并防止幻影读取。当然不是MySQL默认安装中提供的任何存储引擎。
当然,像MyISAM或Memory这样的非事务性存储引擎不存在幻影读取问题,因为在非事务性读取问题中没有未提交的数据。但我不建议使用这些存储引擎,因为它们不支持ACID属性。
Facebook开发了一个名为RocksDB的事务存储引擎,但我找不到有关它处理事务隔离级别的文档。
MySQL附带的其他存储引擎根本不支持事务。
我还使用了另一个特定的RDBMS来防止幻影读取:InterBase,也就是Firebird。但你可能不会喜欢这个解决方案。
在InterBase中,在您的问题中描述的示例中,事务T2将阻塞,等待T1解析。如果T1回滚,则T2可以读取行。T2中的结果与T2开始时的数据状态相同,因为T1回滚了新行。但如果T1提交,则T2无法读取数据,因为这会使其视图不一致。所以T2得到了一个错误。
这太可怕了。T2必须等待T1解决,但最终还是会得到一个错误。这是雪上加霜!
但它确实阻止了幻影读取。
我认为InnoDB之所以是这样实现的,是因为InterBase中使用的替代解决方案不被大多数人喜欢。
2admgd592#
不可以,在MySQL上使用SERIALIZABLE可以同时阻止不可重复读取和幻影读取。此外,MySQL的默认隔离级别可重复读还可以防止不可重复读和幻影读。
下表列出了根据我的实验,MySQL上的每个隔离级别可以预防哪些问题:
隔离级别|脏读|不可重复读|幻读
-|-|
未提交读取|未阻止
已提交读取|已阻止|未阻止|未阻止
可重复读取|已阻止
SERIALIZABLE|已阻止
实际上,您下面对不可重复读取和幻影读取的定义是错误的,您的实验步骤也是错误的:
以下是不可重复读和幻影读的正确定义:
下面是正确步骤的不可重复读取和幻影读取的实验。实际上,在下面的SERIALIZABLE、不可重复读取和幻影读取的实验中,它们不会发生,所以如果你想看到它们的发生,请参见不可重复读取和幻影读取有什么区别?这就是我对读取承诺发生它们的实验的答案。
对于我的实验,我设置了SERIALIZABLE,如下所示:
并且,我使用了**“Person”表和“id”和“name”**,如下所示:
Id|名称
1|John
2|大卫
首先,对于不可重复读,我用MySQL查询执行了**下面这些正确的步骤:
Flow|Transaction 1(T1)|Transaction 2(T2)|说明
-|-|
第一步|
BEGIN;
||T1启动。第二步||
BEGIN;
|T2启动。第三步|
SELECT * FROM person WHERE id = 2;
2 David||t1为DavidStep 4||
UPDATE person SET name = 'Tom' WHERE id = 2;
|#T2需要等待T1提交将David更新为Tom,所以无法进行不可重复读取!!第五步|
COMMIT;
|正在等待...|T1提交。Step 6||
UPDATE person SET name = 'Tom' WHERE id = 2;
|现在T2可以将David更新为Tom了。Step 7||
COMMIT;
**|t2提交。第二,对于Pantom Read,我使用MySQL查询执行了**以下这些正确的步骤:
Flow|Transaction 1(T1)|Transaction 2(T2)|说明
-|-|
第一步|
BEGIN;
||T1启动。第二步||
BEGIN;
|T2启动。第三步|
SELECT * FROM person;
1 John2 David||t2读Person表。第四步||
INSERT INTO person VALUES (3, 'Tom');
|#T2需要等待t1提交,才能将3
和Tom
所在的行插入到Person表中,无法进行虚读!!第五步|
COMMIT;
|正在等待...|T1提交。Step 6||
INSERT INTO person VALUES (3, 'Tom');
|现在,t2可以将3
和Tom
所在的行插入到Person表中。Step 7||
COMMIT;
|T2提交。