幻读和串行化

本文深入探讨了数据库事务的隔离级别,特别是REPEATABLEREAD(可重复读)和SERIALIZABLE(串行化)级别的特性。通过实例演示了不同事务在这些隔离级别下如何处理数据读写冲突,解释了幻读现象及其解决方案。

幻读

将隔离级别设置为REPEATABLE READ(可被重复读取):

SET GLOBAL TRANSACTION ISOLATION LEVEL REPEATABLE READ;
SELECT @@GLOBAL.TRANSACTION_ISOLATION;
+--------------------------------+
| @@GLOBAL.TRANSACTION_ISOLATION |
+--------------------------------+
| REPEATABLE-READ                |
+--------------------------------+

测试REPEATABLE READ,假设在两个不同的连接分别执行START TRANSACTION;

--小张-成都
START TRANSACTION;
INSERT INTO user VALUES(6,'d',1000);
--小王-北京
START TRANSACTION;
--小张-成都
COMMIT;

当前事务开启后,没提交之前,查询不到,提交后可被查询到,但是在提交之前其他事务被开启了,那么在这条事务线上,就不会查询到当前有操作事务的连接,相当于开辟出一条单独的线程。
无论小张是否执行过COMMIT,在小王这边,都不会查询到小张的事务记录,而只会查询到自己所处事务的记录:

select * from user;
+----+-----------+-------+
| id | name      | money |
+----+-----------+-------+
|  1 | a         |   900 |
|  2 | b         |  1100 |
|  3 | 小明      |  1000 |
|  4 | 淘宝店    |  1000 |
|  5 | c         |   100 |
+----+-----------+-------+

这是因为小王在此前开启了一个新事务(START TRANSACTION),那么在他的这条新事务的线上,跟其他事务是没有联系的,也就是如果其他事务正在操作数据,它不知道。
然而事实是,在真实的数据表,小张已经插入一条数据,但小王此时并不知道,也插入同一条数据,会发生什么呢?

insert into user values(6,'d',1000);
-- ERROR 1062 (23000): Duplicate entry '6' for key 'PRIMARY'

报错了,操作告知已存在主键为6的字段,这种现象被称为幻读,一个事务提交的数据,不能被其他事务读取到。

串行化

所有事务写入操作都是串行化的,把隔离级别修改为SERIALIZABLE:

SET GLOBAL TRANSACTION ISOLATION LEVEL SERIALIZABLE;
SELECT @@GLOBAL.TRANSACTION_ISOLATION;
+--------------------------------+
| @@GLOBAL.TRANSACTION_ISOLATION |
+--------------------------------+
| SERIALIZABLE                   |
+--------------------------------+

例子:

--小张-成都
START TRANSACTION;
--小王-北京
START TRANSACTION;
--开启事务之前先查询表,准备操作数据
select * from user;
+----+-----------+-------+
| id | name      | money |
+----+-----------+-------+
|  1 | a         |   900 |
|  2 | b         |  1100 |
|  3 | 小明      |  1000 |
|  4 | 淘宝店    |  1000 |
|  5 | c         |   100 |
|  6 | d         |  1000 |
+----+-----------+-------+
发现没有7号小虎的数据,于是插入一条数据:
insert into user values(7,'小虎',1000);

此时会发生什么呢,由于现在隔离级别是SERIALIZABLE(串行化),串行化意思是:假设把所有的事务都会按照固定顺序执行。执行完一个事务再继续执行下一个事务的写入操作(这意味着队列中同时只能执行一个事务的写入操作)。小王在插入数据时,会出现等待状态,直到小张执行COMMIT结束它所处的事务,或出现等待超时。

<think> 我们正在讨论数据库事务隔离级别中的脏问题。用户要求了解这两个概念的定义、产生原因及解决方法。我将参考之前提供的隔离级别表格说明进行解释,但会专注于这两个特定问题。 脏(Dirty Read): 定义:一个事务取了另一个尚未提交的事务的修改数据,如果那个事务回滚,那么取到的数据就是无效的(即“脏”数据)。 产生原因:发生在隔离级别为“未提交”时,因为该级别允许取未提交的数据。 解决方法:使用更高级别的隔离(如已提交及以上),确保只能取已提交的数据。 (Phantom Read): 定义:一个事务在前后两次相同的范围查询中,由于其他事务的插入或删除操作,导致后一次查询看到了前一次查询没有看到的行(就像出现了“幽灵”记录)。 产生原因:在“已提交”“可重复”级别可能发生(但MySQL的“可重复”通过间隙锁避免了)。因为即使使用了行锁,也无法防止其他事务插入新的行。 解决方法:使用“串行化”隔离级别,或者在某些数据库(如MySQL)的“可重复”级别下利用间隙锁来防止。 我将组织回答如下: 1. 分别详细解释脏,包括定义、产生原因(结合隔离级别)示例。 2. 说明解决方法,主要是通过调整隔离级别以及对应的锁机制。 3. 最后总结对比两者。 注意:由于用户要求参考上一次的问题回答,我会引用之前回答中的部分内容(如隔离级别表格中的信息)并添加引用标识。 同时,按照要求,我会在末尾生成相关问题。 另外,根据系统指令,数学表达式公式要使用正确的LaTeX格式(虽然这个问题中可能用到的数学表达式不多,但如果有变量等,我们会正确使用$...$)。 </think> ### 与脏详解 #### 一、脏(Dirty Read) **定义**: 事务A取到事务B**未提交的修改数据**,若事务B回滚,则事务A取到的数据无效(脏数据)。 **产生原因**: - 隔离级别为**未提交(Read Uncommitted)** - 操作未加锁,直接取内存中的最新数据 **典型场景**: ```sql -- 事务B(转账操作) BEGIN; UPDATE accounts SET balance = balance - 100 WHERE id = 1; -- 余额减100(未提交) -- 事务A(查询余额) SELECT balance FROM accounts WHERE id = 1; -- 到未提交的余额(脏) ``` 若事务B回滚,事务A到的数据就是错误值[^1]。 **解决方法**: 1. 提升隔离级别至**已提交(RC)或更高** 2. 数据库实现机制: - 写操作加**排他锁(X锁)** 直到事务结束 - 操作等待未提交数据释放锁(RC级别)[^2] --- #### 二、(Phantom Read) **定义**: 事务A两次执行相同查询,期间事务B**插入/删除**数据,导致事务A两次结果集不同(如新增"幽灵行")。 **产生原因**: - 隔离级别为**已提交(RC)或可重复(RR)** - 范围查询未锁定数据插入区间 **典型场景**: ```sql -- 事务A(统计年龄>20的用户) BEGIN; SELECT COUNT(*) FROM users WHERE age > 20; -- 返回10人 -- 事务B(插入新用户) INSERT INTO users (name, age) VALUES ('Bob', 25); -- 新增年龄25的用户 COMMIT; -- 事务A再次查询 SELECT COUNT(*) FROM users WHERE age > 20; -- 返回11人() ``` **解决方法**: 1. 提升隔离级别至**串行化(Serializable)** 2. 间隙锁(Gap Lock)机制(MySQL RR级别): - 对查询范围加锁:`SELECT ... WHERE age > 20 FOR UPDATE` - 锁定区间$[20, +\infty)$,阻止新数据插入[^5] 3. 多版本并发控制(MVCC): - 通过数据快照保证可重复 - 快照版本号满足:$version_{snapshot} < version_{start}$[^3] --- ### 三、对比与总结 | **特性** | 脏 | | |----------------|--------------------------|--------------------------| | **数据变更** | 未提交的**修改** | 已提交的**新增/删除** | | **隔离级别** | 未提交(RU) | 已提交(RC)及以上 | | **本质问题** | 取到无效中间状态 | 前后查询结果集不一致 | | **关键解决** | 操作等待写锁释放 | 锁定数据范围(间隙锁) | | **数学描述** | $Read_{A}(x) \cap Write_{B}(x)_{\text{uncommitted}}$ | $ResultSet_{A1} \neq ResultSet_{A2}$ | > **最佳实践**: > - 金融系统选**可重复(RR)**:防脏+(MySQL间隙锁) > - 高并发系统选**已提交(RC)**:平衡性能与一致性[^4] ---
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值