Mysql笔记:事务隔离级别理解

本文深入解析了数据库事务的四种隔离级别:未提交读、提交读、可重复读和串行化,通过具体示例阐述了脏读、不可重复读和幻读等问题的产生原因及MySQL的解决策略。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

这个问题其实有很多人都已经教科书式的总结了很多遍,如:

隔离级别中文描述此级别问题(面试官喜欢用这个)READ UNCOMMITED未提交读脏读READ COMMITED提交读不可重复读REPEATABLE READ可重复读幻读SERIALIZABLE串行化锁

但是在这个表格中最后一列的问题因何产生,很多人会不明白其中的缘由。我先说下我的理解,然后再来一点点解释:

事务隔离的四个级别可以先用“事务是否可并发”来划分成两个对立面来理解:

  1. 事务不可并发在 Mysql 中只有 SERIALIZABLE 这一级别满足;其它的当然是事务可并发了;
  2. 事务不可并发,Mysql 选择了串行化这一实现方式,因此引入了锁,也带来了性能问题;
  3. 事务可并发,因此在多个并发的事务期间,我们并不知道哪个事务的哪段逻辑(begin/rollback/commit)会在下一个时间片内被执行;

并发事务带来的问题

在上面的描述中,2、3是对1的一个扩展,2不难理解,但是 3 可能有些生硬,我们可以简单的换种理解方式,

  1. 假设同一时间有两个事务: A & B ,并且事务 A 执行 update,事务 B 执行 select。
  2. 假设事务的开启、提交、回滚及事务中执行的 Action 都能在一个 cpu 时间片内完成,那么可把 A&B 的事务拆成如下逻辑调用段:

#事务A事务B1beginbegin2updateselect3commitcommit4rollbackrollback

基于上面的假设,我们再来理解事务并发情况下各种问题的产生:

脏读

  1. A begin => update 后让 cpu
  2. 同时B begin => select,但是事务 B 很心大,并没有去验证 A 的有效性,读到了 A update 后的数据;
  3. A 在下一个 cpu 时间又得到了调度,A 发现自己刚才的操作无效了,A rollback 得到了执行,但是它无法告知 B 了,所以 B 读到的数据是无效的;
  4. 不可重复读
  5. 知道了脏读的原因后,为了解决这个问题,Mysql 规定 B 读的数据只能读取已经 commit 状态的数据:
  6. A begin => update 后让 cpu
  7. 同时 B begin => select,这次 B 很小心地验证 A 的数据是否 commit 了,B 这次读到了 A begin 以前的数据;
  8. 事务 A 在下一个 cpu 时间又得到了调度,A commit 了;
  9. B 再次 select,但是已经 select 到了 A commit 后的数据了,B 在 A commit 前后读到了两次不一样的数据,即不可重复读了;
  10. 幻读
  11. 知道了不可重复读的原因后,Mysql 又规定,既然 B 第一次读到的是 A commit 前的数据,那么在事务 B 中后面无论多少次 select 都只能读到 A commit 之前的数据。但是问题又来了:
  12. 这次 A 不是 update 了,而是 insert,B select 也不是单条了,而是 select range;
  13. B 在 A commit 前后两次 select range 会发现结果的数量不一至;这就是幻读;
  14. InnoDB 针对幻读也做了处理:MVCC,在每一行后都有隐藏的两列版本号来实现;大致与处理不可重复读相同;

转载于:https://my.oschina.net/u/3967312/blog/3000943

### MySQL 事务隔离级别解释 #### 1. Read Uncommitted (读未提交) 这是最低的隔离级别,在此级别下,一个事务可以读取另一个尚未提交的事务中的更改。这可能导致脏读现象的发生,即读取到其他事务中未提交的数据。 ```sql SET SESSION TRANSACTION ISOLATION LEVEL READ UNCOMMITTED; START TRANSACTION; SELECT * FROM account; -- 可能会读取到其他事务未提交的数据 COMMIT; ``` 这种情况下,如果两个事务几乎同时修改同一记录,则第一个查询该记录的事务可能会看到不一致的状态[^1]。 #### 2. Read Committed (读已提交) 在此隔离级别上,一个事务仅能在另一事务已经提交之后才能看见其做出的变化。这意味着不会发生脏读,但是仍然可能出现不可重复读的情况——当同一个事务内多次读取同一条数据时,由于其间有其它事务对该条目进行了更新并提交,所以每次获取的结果可能不同。 ```sql SET SESSION TRANSACTION ISOLATION LEVEL READ COMMITTED; START TRANSACTION; SELECT * FROM account WHERE id = 1; -- 第一次读取 -- 假设此时有一个外部事务改变了这条记录并提交 SELECT * FROM account WHERE id = 1; -- 第二次读取得到不同的结果 COMMIT; ``` #### 3. Repeatable Read (可重复读) InnoDB 默认采用的就是这个隔离等级。它确保在一个事务期间对于相同的行进行任何数量次 SELECT 查询都将返回相同的结果集,即使在这之间存在其他的提交操作也不会影响当前事务所见的内容。不过需要注意的是,默认情况下并不能防止幻象读问题(Phantom Reads),即新插入满足条件的新行使得再次检索时出现了之前不存在的数据行。 ```sql SET SESSION TRANSACTION ISOLATION LEVEL REPEATABLE READ; START TRANSACTION; SELECT * FROM account WHERE id = 1; -- 多次执行都获得同样的结果 -- 即使有其他事务插入新的符合条件的记录 SELECT * FROM account WHERE id = 1; -- 结果依旧不变 COMMIT; ``` #### 4. Serializable (串行化) 最高级别的隔离度,强制所有事务依次顺序执行而不是并发运行。通过这种方式完全消除了任何形式上的并发冲突,包括但不限于脏读、不可重复读以及幻影读等问题。然而这样做也意味着性能开销极大增加,因为所有的写入请求都需要排队等待前一个完成才行。 ```sql SET SESSION TRANSACTION ISOLATION LEVEL SERIALIZABLE; START TRANSACTION; SELECT * FROM account FOR UPDATE; -- 锁定表直到事务结束 INSERT INTO account VALUES (...); UPDATE account SET balance = ... ; DELETE FROM account WHERE ... COMMIT; ``` 上述SQL语句展示了如何设置不同的隔离级别,并给出了简单的例子说明每种情况下的行为差异。实际应用中应根据具体需求权衡选择合适的隔离级别以平衡安全性和效率的要求[^2]。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值