不可重复读和幻影读的区别是什么?
我读过维基百科上的隔离(数据库系统)文章,但我有一些怀疑。在下面的例子中,将会发生什么:不可重复读取和幻影读取?
# # # #事务
SELECT ID, USERNAME, accountno, amount FROM USERS WHERE ID=1
# # # #输出:
1----MIKE------29019892---------5000
# # # #事务B
UPDATE USERS SET amount=amount+5000 where ID=1 AND accountno=29019892;
COMMIT;
# # # #事务
SELECT ID, USERNAME, accountno, amount FROM USERS WHERE ID=1
另一个疑问是,在上面的示例中,应该使用哪个隔离级别?,为什么?
读现象
脏读:从另一个事务读取未提交的数据
不可重复读取:从另一个事务的UPDATE查询中读取COMMITTED数据
幻影读取:从另一个事务的INSERT或DELETE查询中读取COMMITTED数据
注意:来自另一个事务的DELETE语句,在某些情况下也有非常低的概率导致不可重复读取。不幸的是,当DELETE语句删除当前事务正在查询的同一行时,就会发生这种情况。但这种情况很少见,在每个表中都有数百万行的数据库中更不可能出现这种情况。在任何生产环境中,包含事务数据的表通常都有很大的数据量。
此外,我们还可以观察到,在大多数用例中,更新作业可能比实际的INSERT或delete作业更频繁(在这种情况下,仅存在不可重复读取的危险-在这些情况下不可能出现幻影读取)。这就是为什么update与INSERT-DELETE处理方式不同,导致的异常命名也不同。
处理insert - delete(而不仅仅是处理update)还需要额外的处理成本。
不同隔离级别的好处
READ_UNCOMMITTED什么也阻止不了。这是零
隔离级别
READ_COMMITTED只阻止一种,即脏读
REPEATABLE_READ防止两种异常:脏读和
不可重复读
SERIALIZABLE可以防止所有三种异常:脏读,
不可重复读取和幻影读取
那么为什么不始终设置事务SERIALIZABLE呢?好吧,上述问题的答案是:SERIALIZABLE设置使事务非常慢,这也是我们不希望看到的。
实际上,事务时间消耗的速率如下:
Serializable > repeatable_read > read_committed > read_uncommitted
所以READ_UNCOMMITTED设置是最快的。
总结
实际上,我们需要分析用例并确定隔离级别,以便优化事务时间并防止大多数异常。
注意,数据库在默认情况下可能有REPEATABLE_READ设置。管理员和架构师可能倾向于将此设置作为默认设置,以展示更好的平台性能。
读现象
脏读:从另一个事务读取未提交的数据
不可重复读取:从另一个事务的UPDATE查询中读取COMMITTED数据
幻影读取:从另一个事务的INSERT或DELETE查询中读取COMMITTED数据
注意:来自另一个事务的DELETE语句,在某些情况下也有非常低的概率导致不可重复读取。不幸的是,当DELETE语句删除当前事务正在查询的同一行时,就会发生这种情况。但这种情况很少见,在每个表中都有数百万行的数据库中更不可能出现这种情况。在任何生产环境中,包含事务数据的表通常都有很大的数据量。
此外,我们还可以观察到,在大多数用例中,更新作业可能比实际的INSERT或delete作业更频繁(在这种情况下,仅存在不可重复读取的危险-在这些情况下不可能出现幻影读取)。这就是为什么update与INSERT-DELETE处理方式不同,导致的异常命名也不同。
处理insert - delete(而不仅仅是处理update)还需要额外的处理成本。
不同隔离级别的好处
READ_UNCOMMITTED什么也阻止不了。这是零
隔离级别
READ_COMMITTED只阻止一种,即脏读
REPEATABLE_READ防止两种异常:脏读和
不可重复读
SERIALIZABLE可以防止所有三种异常:脏读,
不可重复读取和幻影读取
那么为什么不始终设置事务SERIALIZABLE呢?好吧,上述问题的答案是:SERIALIZABLE设置使事务非常慢,这也是我们不希望看到的。
实际上,事务时间消耗的速率如下:
Serializable > repeatable_read > read_committed > read_uncommitted
所以READ_UNCOMMITTED设置是最快的。
总结
实际上,我们需要分析用例并确定隔离级别,以便优化事务时间并防止大多数异常。
注意,数据库在默认情况下可能有REPEATABLE_READ设置。管理员和架构师可能倾向于将此设置作为默认设置,以展示更好的平台性能。
Non-repeatable read(fuzzy read) is that a transaction reads the same row at least twice but the same row's data is different between the 1st and 2nd reads because other transactions update the same row's data and commit at the same time(concurrently).
Phantom read is that a transaction reads the same table at least twice but the number of the same table's rows is different between the 1st and 2nd reads because other transactions insert or delete rows and commit at the same time(concurrently).
我用MySQL和2个命令提示符尝试了不可重复读取和幻影读取。
对于不可重复读和幻像读的实验,我设置read COMMITTED隔离级别发生不可重复读和幻像读:
SET GLOBAL TRANSACTION ISOLATION LEVEL READ COMMITTED;
SET SESSION TRANSACTION ISOLATION LEVEL READ COMMITTED;
并且,我用id和名称创建了person表,如下所示。
人表:
首先,对于不可重复的读取,我在MySQL查询中执行了以下步骤:
Flow |
Transaction 1 (T1) |
Transaction 2 (T2) |
Explanation |
Step 1 |
BEGIN; |
|
T1 starts. |
Step 2 |
|
BEGIN; |
T2 starts. |
Step 3 |
SELECT * FROM person WHERE id = 2;
2 David |
|
T1 reads David . |
Step 4 |
|
UPDATE person SET name = 'Tom' WHERE id = 2; |
T2 updates David to Tom . |
Step 5 |
|
COMMIT; |
T2 commits. |
Step 6 |
SELECT * FROM person WHERE id = 2;
2 Tom |
|
T1 reads Tom instead of David after T2 commits.
*Non-repeatable read occurs!! |
Step 7 |
COMMIT; |
|
T1 commits. |
第二,对于幻影读取,我用MySQL查询执行了以下步骤:
Flow |
Transaction 1 (T1) |
Transaction 2 (T2) |
Explanation |
Step 1 |
BEGIN; |
|
T1 starts. |
Step 2 |
|
BEGIN; |
T2 starts. |
Step 3 |
SELECT * FROM person;
1 John 2 David |
|
T1 reads 2 rows. |
Step 4 |
|
INSERT INTO person VALUES (3, 'Tom'); |
T2 inserts the row with 3 and Tom to person table. |
Step 5 |
|
COMMIT; |
T2 commits. |
Step 6 |
SELECT * FROM person;
1 John 2 David 3 Tom |
|
T1 reads 3 rows instead of 2 rows after T2 commits.
*Phantom read occurs!! |
Step 7 |
COMMIT; |
|
T1 commits. |