MySQL 事务隔离级别
MySQL 事务隔离级别
事务隔离是数据库的重要概念,搞懂4种级别很有必要。
1. 事务的ACID
- 原子性(Atomicity): 要么全成功,要么全失败
- 一致性(Consistency): 事务前后数据一致
- 隔离性(Isolation): 多个事务互不影响
- 持久性(Durability): 提交后永久保存
2. 4种隔离级别
1)读未提交(Read Uncommitted)
- 能读到未提交的数据
- 有脏读、不可重复读、幻读问题
- 性能最好
2)读提交(Read Committed)
- 只能读到已提交的数据
- 解决脏读
- 有不可重复读、幻读问题
- Oracle默认级别
3)可重复读(Repeatable Read)
- 同一个事务里读多次结果一样
- 解决脏读、不可重复读
- 有幻读问题
- MySQL默认级别
4)串行化(Serializable)
- 事务排队执行
- 解决所有问题
- 性能最差
3. 三种问题
脏读:
- 读到另一个事务未提交的数据
- 对方回滚了,你读的就是脏数据
不可重复读:
- 同一个事务里,两次读同一行结果不一样
- 中间被其他事务修改了
幻读:
- 同一个事务里,两次查询行数不一样
- 中间被其他事务插入了
4. 查看和设置
-- 查看当前隔离级别
SELECT @@transaction_isolation;
-- 设置隔离级别
SET TRANSACTION ISOLATION LEVEL READ COMMITTED;
5. 实际使用
- 一般用默认的可重复读就够了
- 对一致性要求高的用串行化
- 不要随便改隔离级别
总结
MySQL事务隔离级别要点:
- 读未提交:性能最好,问题最多
- 读提交:解决脏读
- 可重复读:MySQL默认,解决不可重复读
- 串行化:最安全,性能最差
- 理解脏读、不可重复读、幻读
做数据库开发,事务隔离一定要懂。
MySQL 事务隔离级别
MySQL 事务隔离级别
事务是数据库的重要特性,隔离级别决定了事务之间互相影响的程度。这篇讲清楚四个隔离级别。
1. 事务的 ACID 特性
- 原子性(Atomicity): 事务要么全部成功,要么全部失败
- 一致性(Consistency): 事务前后数据库状态一致
- 隔离性(Isolation): 多个事务之间互不干扰
- 持久性(Durability): 事务提交后永久保存
2. 三个并发问题
脏读: 一个事务读到另一个事务还没提交的数据
不可重复读: 同一个事务内,两次读同一行数据结果不一样(被别的事务修改了)
幻读: 同一个事务内,两次查询返回的行数不一样(被别的事务插入/删除了)
3. 四个隔离级别
1)读未提交(Read Uncommitted)
- 最低级别,能读到未提交的数据
- 会出现脏读、不可重复读、幻读
2)读提交(Read Committed)
- 只能读到已提交的数据
- 解决脏读,但还有不可重复读、幻读
- Oracle 默认级别
3)可重复读(Repeatable Read)
- 同一个事务内多次读同一行结果一致
- 解决脏读、不可重复读,但还有幻读
- MySQL InnoDB 默认级别
4)串行化(Serializable)
- 最高级别,事务串行执行
- 解决所有问题,但性能最差
4. 查看和设置
-- 查看当前隔离级别
SELECT @@transaction_isolation;
-- 设置隔离级别(会话级别)
SET SESSION transaction_isolation = 'READ-COMMITTED';
-- 设置全局隔离级别
SET GLOBAL transaction_isolation = 'READ-COMMITTED';
5. InnoDB 的优化
MySQL InnoDB 在可重复读级别下,通过 MVCC(多版本并发控制)和间隙锁,也解决了幻读问题,比标准 SQL 定义的更强。
6. 实际开发怎么选
- 大多数场景用默认的可重复读就够了
- 对一致性要求极高的场景(金融),用串行化
- 报表分析等读多写少的场景,可以调低到读提交提高性能
总结
| 隔离级别 | 脏读 | 不可重复读 | 幻读 | 性能 |
|---|---|---|---|---|
| 读未提交 | 有 | 有 | 有 | 最好 |
| 读提交 | 无 | 有 | 有 | 好 |
| 可重复读 | 无 | 无 | 无(InnoDB) | 一般 |
| 串行化 | 无 | 无 | 无 | 最差 |
MySQL 默认可重复读,日常开发不用改,理解原理就行。
MySQL事务隔离级别详解,读提交可重复读串行化
前言
做后端开发的同学肯定都听说过事务隔离级别,但是很多同学搞不清楚读未提交、读提交、可重复读、串行化这四个级别到底有什么区别,也不知道自己项目应该用哪个级别。
很多面试的时候也经常被问到:MySQL的默认隔离级别是什么?能解决什么问题?很多同学答不上来。
这篇文章就把MySQL的事务隔离级别从头到尾讲清楚,看完之后你就彻底搞懂了。
一、先复习一下事务的ACID特性
讲隔离级别之前,先复习一下事务的四个特性,也就是ACID:
- 原子性(Atomicity):事务里的操作要么全部成功,要么全部失败回滚
- 一致性(Consistency):事务执行前后,数据库的完整性约束没有被破坏
- 隔离性(Isolation):多个事务之间互相隔离,不能互相干扰
- 持久性(Durability):事务提交之后,修改是永久的
我们今天讲的隔离级别,就是跟第三个特性"隔离性"有关的。隔离性的程度不同,就有了不同的隔离级别。
二、并发事务会带来什么问题
为什么需要隔离级别?因为多个事务同时操作数据库的时候,会出现各种问题。
常见的并发问题有三个:
1. 脏读
脏读就是:一个事务读到了另一个事务还没提交的数据。
比如:
- 事务A把某条数据从1改成了2,但是还没提交
- 事务B读了这条数据,读到的是2
- 然后事务A回滚了,数据又变回了1
- 事务B读的那个2就是脏数据,这就是脏读
2. 不可重复读
不可重复读就是:一个事务里,两次读同一条数据,结果不一样。
比如:
- 事务A第一次读某条数据,值是1
- 事务B把这条数据改成了2,并且提交了
- 事务A第二次再读这条数据,值变成了2
- 同一个事务里,两次读同一条数据结果不一样,这就是不可重复读
3. 幻读
幻读就是:一个事务里,两次查询的结果集不一样。
比如:
- 事务A查询id > 5的记录,查到了5、6两条
- 事务B插入了一条id=7的记录,并且提交了
- 事务A再查一次,发现多了一条id=7的
- 这就是幻读
注意:不可重复读和幻读的区别:
- 不可重复读:同一条数据,两次读结果不一样(修改导致的)
- 幻读:查询的结果集变多或者变少了(插入/删除导致的)
三、四个事务隔离级别
为了解决上面说的这三个问题,SQL标准定义了四个隔离级别,级别越高,越能解决问题,但是并发性能越差。
1. 读未提交(Read Uncommitted)
最低的隔离级别。一个事务可以读到另一个事务还没提交的数据。
能解决什么问题: 啥也解决不了
会出现什么问题: 脏读、不可重复读、幻读都可能出现
这个级别基本上没人用,所以就不多说了。
2. 读提交(Read Committed,RC)
一个事务只能读到另一个事务已经提交的数据。
能解决什么问题: 解决了脏读
还会出现什么问题: 不可重复读、幻读
Oracle默认就是这个隔离级别。
3. 可重复读(Repeatable Read,RR)
一个事务里,多次读同一条数据,结果是一样的。
能解决什么问题: 解决了脏读、不可重复读
还会出现什么问题: 幻读
注意:MySQL的InnoDB引擎在RR级别下,通过间隙锁解决了幻读问题,所以MySQL的RR级别实际上可以完全解决幻读。
MySQL默认就是这个隔离级别。
4. 串行化(Serializable)
最高的隔离级别。所有事务一个一个排队执行,完全串行。
能解决什么问题: 脏读、不可重复读、幻读全都解决了
缺点: 性能太差了,并发度最低,基本上没人用
四、四个隔离级别对比表
给大家整理了一个表格,一目了然:
| 隔离级别 | 脏读 | 不可重复读 | 幻读 | 性能 |
|---|---|---|---|---|
| 读未提交 | ❌ 可能出现 | ❌ 可能出现 | ❌ 可能出现 | 最好 |
| 读提交(RC) | ✅ 解决了 | ❌ 可能出现 | ❌ 可能出现 | 比较好 |
| 可重复读(RR) | ✅ 解决了 | ✅ 解决了 | ✅ 解决了(MySQL InnoDB) | 比较差 |
| 串行化 | ✅ 解决了 | ✅ 解决了 | ✅ 解决了 | 最差 |
五、MySQL默认的隔离级别
很多同学面试的时候被问到"MySQL默认的隔离级别是什么",答不上来。
记住:MySQL InnoDB默认的隔离级别是可重复读(RR)。
注意和Oracle区分一下:Oracle默认的是读提交(RC)。
六、怎么查看当前的隔离级别
登录MySQL之后,执行下面的命令:
-- 查看全局的隔离级别
SELECT @@global.tx_isolation;
-- 查看当前会话的隔离级别
SELECT @@session.tx_isolation;
执行完之后会看到类似这样的结果:
+-----------------+
| @@tx_isolation |
+-----------------+
| REPEATABLE-READ |
+-----------------+
这就说明当前是可重复读级别。
七、怎么修改隔离级别
临时修改(当前会话)
-- 改成读提交
SET SESSION TRANSACTION ISOLATION LEVEL READ COMMITTED;
-- 改成可重复读
SET SESSION TRANSACTION ISOLATION LEVEL REPEATABLE READ;
这种修改只对当前连接有效,断开连接之后就恢复了。
全局临时修改
SET GLOBAL TRANSACTION ISOLATION LEVEL READ COMMITTED;
这种修改对新连接有效,已经存在的连接不受影响,MySQL重启之后就恢复了。
永久修改
要永久修改的话,需要改配置文件。编辑my.cnf,在[mysqld]下面添加:
[mysqld]
transaction-isolation = READ-COMMITTED
修改完之后重启MySQL服务,就永久生效了。
八、实际项目中应该用哪个级别
很多同学问:实际项目中应该用哪个隔离级别?
一般来说:
- 如果对并发性能要求高:用读提交(RC)就够了,很多互联网公司都是用RC
- 如果对数据一致性要求高:用可重复读(RR),MySQL默认就是这个
- 串行化基本上没人用,性能太差了
其实大部分情况下,用MySQL默认的RR就够了,不用特意改。如果并发特别高,可以考虑改成RC,这样间隙锁就没了,并发性能会好很多。
常见坑
坑1:不知道MySQL默认的隔离级别是RR
很多同学以为MySQL默认是读提交,跟Oracle一样,结果面试答错了。记住:MySQL默认是可重复读(RR)。
坑2:把不可重复读和幻读搞混了
很多同学分不清不可重复读和幻读。记住:
- 不可重复读:同一条数据被修改了,两次读结果不一样
- 幻读:结果集的行数变多或者变少了,是插入/删除导致的
坑3:以为RR级别解决不了幻读
很多同学以为RR级别解决不了幻读,其实MySQL的InnoDB引擎在RR级别下,通过间隙锁是可以解决幻读的。这一点跟标准SQL不一样,MySQL做了增强。
坑4:修改了隔离级别但是没生效
很多同学用SET GLOBAL改了隔离级别,然后用当前连接查,发现还是原来的级别。这是因为SET GLOBAL只对新连接有效,当前连接还是原来的。要当前连接生效的话,用SET SESSION。
总结
MySQL事务隔离级别的核心知识点:
- 并发事务的三个问题:脏读、不可重复读、幻读
- 四个隔离级别:读未提交、读提交、可重复读、串行化,级别越高越安全,性能越差
- 脏读:读到了别的事务还没提交的数据
- 不可重复读:同一个事务里,两次读同一条数据结果不一样(修改导致的)
- 幻读:同一个事务里,两次查询的结果集不一样(插入/删除导致的)
- MySQL InnoDB默认是可重复读(RR),并且通过间隙锁解决了幻读
- 查看隔离级别:SELECT @@tx_isolation
- 修改隔离级别:SET SESSION/GLOBAL TRANSACTION ISOLATION LEVEL ...
- 实际项目用RR或者RC就够了,串行化性能太差没人用
事务隔离级别是数据库的基础知识点,不管是面试还是实际开发,都是必须掌握的。
遇到问题加QQ23979811 协助处理
MySQL 事务隔离级别
MySQL 事务隔离级别详解
事务是数据库并发控制的基础,搞懂四个隔离级别,才能解决脏读、幻读问题。
1. 事务 ACID 特性
- 原子性(Atomicity):要么全成功,要么全回滚
- 一致性(Consistency):事务前后数据一致
- 隔离性(Isolation):事务之间互不干扰
- 持久性(Durability):提交后永久保存
2. 四种隔离级别
MySQL 有四种隔离级别,从上到下越来越严格:
读未提交(READ UNCOMMITTED)
- 可以读到别人还没提交的数据
- 会出现脏读
- 几乎没人用
读提交(READ COMMITTED)
- 只能读到别人已经提交的数据
- 解决了脏读,但会出现不可重复读
- Oracle、SQL Server 默认级别
可重复读(REPEATABLE READ)
- 同一个事务里,读多次结果都一样
- 解决了脏读、不可重复读
- MySQL InnoDB 默认级别
- InnoDB 用间隙锁解决了幻读
串行化(SERIALIZABLE)
- 事务一个一个排队执行
- 最安全,但性能最差
- 几乎没人用
3. 三种读问题
脏读:读到了别人还没提交的数据,别人回滚了,你读到的就是脏数据。
不可重复读:同一个事务里,两次读同一行数据,结果不一样(别人中间改了并提交了)。
幻读:同一个事务里,两次查同一个范围,行数不一样(别人中间插了新行)。
4. 查看和设置隔离级别
-- 查看当前隔离级别
SELECT @@global.tx_isolation;
SELECT @@session.tx_isolation;
-- 设置全局隔离级别
SET GLOBAL tx_isolation = 'READ-COMMITTED';
-- 设置当前会话隔离级别
SET SESSION tx_isolation = 'READ-COMMITTED';
5. 实际应用建议
- 默认就好:MySQL 默认的可重复读已经够用
- 高并发场景:可以降到读提交,提升并发性能
- 金融级场景:用串行化,绝对安全
- 大多数业务:默认级别就行,不用折腾
6. 代码示例
// PDO 事务示例
try {
$pdo->beginTransaction();
$pdo->exec("UPDATE account SET money = money - 100 WHERE id = 1");
$pdo->exec("UPDATE account SET money = money + 100 WHERE id = 2");
$pdo->commit();
} catch (Exception $e) {
$pdo->rollBack();
echo "转账失败:" . $e->getMessage();
}
总结
MySQL 事务隔离级别核心:
- 读未提交:脏读
- 读提交:不可重复读
- 可重复读:默认级别,解决幻读
- 串行化:最安全最慢
大多数业务用默认的可重复读就够了。
MySQL 事务与锁:理解隔离级别和死锁的实战指南
前言
事务和锁是 MySQL 最核心也最容易出问题的部分。很多人写了很多年 SQL,却还是搞不清隔离级别和死锁。本文用最直白的方式讲清楚。
一、事务的 ACID
事务就是一组操作,要么全部成功,要么全部失败。四个特性:
- A 原子性:一组操作是一个整体,不能拆分
- C 一致性:事务前后,数据库从一个一致状态到另一个一致状态
- I 隔离性:多个事务之间互不干扰
- D 持久性:事务提交后,数据就永久保存了
二、并发问题
多个事务同时操作数据,会出什么问题?
1. 脏读
事务 A 读到了事务 B 还没提交的数据。
事务 A:修改了余额为 1000,但还没提交
事务 B:读到了余额 1000
事务 A:回滚了,余额变回 500
事务 B:拿着 1000 的错误数据继续操作
2. 不可重复读
事务 A 两次读同一行数据,结果不一样。
事务 A:第一次读余额是 500
事务 B:修改了余额为 1000,提交了
事务 A:第二次读余额是 1000
3. 幻读
事务 A 两次查询,结果集的行数不一样。
事务 A:第一次查询 age > 20 的用户,有 10 条
事务 B:插入了一条 age = 25 的用户,提交了
事务 A:第二次查询 age > 20 的用户,有 11 条
三、四种隔离级别
MySQL 用隔离级别来解决这些并发问题:
| 隔离级别 | 脏读 | 不可重复读 | 幻读 |
|---|---|---|---|
| 读未提交(READ UNCOMMITTED) | 会 | 会 | 会 |
| 读提交(READ COMMITTED) | 不会 | 会 | 会 |
| 可重复读(REPEATABLE READ) | 不会 | 不会 | 会(InnoDB 解决了) |
| 串行化(SERIALIZABLE) | 不会 | 不会 | 不会 |
MySQL 默认是可重复读(REPEATABLE READ),InnoDB 在这个级别下用间隙锁解决了幻读。
查看当前隔离级别
SELECT @@tx_isolation;
修改隔离级别
SET SESSION TRANSACTION ISOLATION LEVEL READ COMMITTED;
四、锁的类型
1. 共享锁(S 锁)
读锁,多个事务可以同时持有。
SELECT ... LOCK IN SHARE MODE;
2. 排他锁(X 锁)
写锁,只有一个事务能持有。
SELECT ... FOR UPDATE;
3. 表锁 vs 行锁
- 表锁:锁住整张表,开销小,但并发低
- 行锁:锁住一行数据,开销大,但并发高
五、死锁
什么是死锁
两个事务互相等待对方释放锁。
事务 A:锁住了 id = 1,等待 id = 2
事务 B:锁住了 id = 2,等待 id = 1
两个事务都卡住了,谁也不让谁。
怎么避免死锁
- 按相同顺序访问表和行:所有事务都按 id 从小到大操作
- 事务尽量短:事务越长,锁持有时间越长
- 降低隔离级别:隔离级别越低,锁越少
- 加索引:没有索引会走表锁,更容易死锁
查看死锁
-- 查看最近一次死锁
SHOW ENGINE INNODB STATUS;
六、实战:转账
-- 开启事务
BEGIN;
-- 扣款(加行锁)
UPDATE account SET balance = balance - 100 WHERE id = 1;
-- 检查余额
SELECT balance FROM account WHERE id = 1;
-- 如果余额不够,回滚
-- ROLLBACK;
-- 加钱
UPDATE account SET balance = balance + 100 WHERE id = 2;
-- 提交
COMMIT;
七、常见坑
- 事务太长:锁持有时间太长,容易死锁
- 没有索引:行锁变表锁,并发暴跌
- 隔离级别太高:用串行化,性能很差
- 忘记提交:事务一直开着,锁一直占着
总结
MySQL 事务与锁的核心思路:
- 事务保证 ACID,一组操作要么全成功要么全失败
- 隔离级别越高越安全,但性能越差
- MySQL 默认可重复读,InnoDB 解决了幻读
- 行锁比表锁并发高,但一定要有索引
- 按相同顺序操作,能避免大部分死锁
搞懂这些,你对 MySQL 的理解就超过 80% 的开发者了。