MySQL事务隔离级别详解

发布于:2025-04-17 ⋅ 阅读:(28) ⋅ 点赞:(0)

MySQL事务隔离级别详解

1. 基本概念

1.1 什么是事务隔离级别?

事务隔离级别是数据库管理系统为了保证数据一致性,在多个事务并发访问时提供的不同级别的保护机制。

1.2 事务并发问题

  1. 脏读(Dirty Read)

    • 一个事务读取到另一个事务未提交的数据
    • 如果那个事务回滚,读到的数据就是无效的
  2. 不可重复读(Non-repeatable Read)

    • 同一事务中,多次读取同一数据得到不同结果
    • 因为其他事务修改并提交了这个数据
  3. 幻读(Phantom Read)

    • 同一事务中,多次查询某个范围的记录,数量不一致
    • 因为其他事务插入或删除了符合这个范围的记录

2. 四种隔离级别

2.1 读未提交(READ UNCOMMITTED)

-- 设置隔离级别
SET SESSION TRANSACTION ISOLATION LEVEL READ UNCOMMITTED;

-- 示例:脏读
-- 事务1
START TRANSACTION;
UPDATE account SET balance = balance - 100 WHERE id = 1;
-- 此时事务2可以读取到余额减少100的结果
ROLLBACK;  -- 如果回滚,事务2读取到的数据就是脏数据

-- 事务2
SELECT balance FROM account WHERE id = 1;  -- 读取到未提交的数据

特点:

  • 最低的隔离级别
  • 允许读取未提交的数据
  • 可能出现脏读、不可重复读和幻读
  • 性能最好,但几乎不用

2.2 读已提交(READ COMMITTED)

-- 设置隔离级别
SET SESSION TRANSACTION ISOLATION LEVEL READ COMMITTED;

-- 示例:不可重复读
-- 事务1
START TRANSACTION;
SELECT balance FROM account WHERE id = 1;  -- 第一次读取:1000
-- 事务2执行更新并提交
SELECT balance FROM account WHERE id = 1;  -- 第二次读取:900
COMMIT;

特点:

  • Oracle默认的隔离级别
  • 只能读取已经提交的数据
  • 避免了脏读
  • 可能出现不可重复读和幻读

2.3 可重复读(REPEATABLE READ)

-- 设置隔离级别
SET SESSION TRANSACTION ISOLATION LEVEL REPEATABLE READ;

-- 示例:避免不可重复读
-- 事务1
START TRANSACTION;
SELECT balance FROM account WHERE id = 1;  -- 第一次读取:1000
-- 事务2执行更新并提交
SELECT balance FROM account WHERE id = 1;  -- 第二次读取:仍然是1000
COMMIT;

-- 示例:可能的幻读
-- 事务1
START TRANSACTION;
SELECT COUNT(*) FROM account WHERE balance > 1000;  -- 返回10条
-- 事务2插入一条balance=1500的记录并提交
INSERT INTO account VALUES (1500);  -- 这条记录可能会出现在后续的查询中
COMMIT;

特点:

  • MySQL的默认隔离级别
  • 同一事务中多次读取结果一致
  • 避免了脏读和不可重复读
  • 使用MVCC(多版本并发控制)实现
  • 在某些场景下可能出现幻读

2.4 串行化(SERIALIZABLE)

-- 设置隔离级别
SET SESSION TRANSACTION ISOLATION LEVEL SERIALIZABLE;

-- 示例:完全串行化执行
-- 事务1
START TRANSACTION;
SELECT * FROM account WHERE balance > 1000;
-- 事务2必须等待事务1完成才能执行
COMMIT;

特点:

  • 最高的隔离级别
  • 完全串行化执行,避免所有并发问题
  • 性能最差,很少使用

3. 实现原理

3.1 MVCC(多版本并发控制)

-- MVCC相关的隐藏字段
-- DB_TRX_ID:最后一次修改该记录的事务ID
-- DB_ROLL_PTR:回滚指针,指向上一个版本
-- DB_ROW_ID:隐藏主键

3.2 锁机制

-- 共享锁(S锁)
SELECT * FROM account WHERE id = 1 LOCK IN SHARE MODE;

-- 排他锁(X锁)
SELECT * FROM account WHERE id = 1 FOR UPDATE;

4. 最佳实践

4.1 选择合适的隔离级别

  • 一般情况下使用默认的REPEATABLE READ
  • 对一致性要求特别高的场景可以使用SERIALIZABLE
  • 需要权衡性能和一致性

4.2 注意事项

-- 查看当前隔离级别
SELECT @@transaction_isolation;

-- 设置隔离级别
SET SESSION TRANSACTION ISOLATION LEVEL REPEATABLE READ;

-- 开启事务
START TRANSACTION;

-- 提交或回滚
COMMIT;
ROLLBACK;

4.3 常见问题解决

  1. 性能问题:

    • 避免长事务
    • 合理设置隔离级别
    • 适当使用锁机制
  2. 并发问题:

    • 使用乐观锁或悲观锁
    • 合理设计业务逻辑
    • 正确处理死锁

什么是幻读?
幻读是指在同一事务中,多次查询某个范围的记录,数量不一致。因为其他事务插入或删除了符合这个范围的记录。大白话就是同一事务内执行两次相同的查询,结果不一致。

当前读场景解决?
场景1:select xx for update 其他事务不能插入数据
此时无论怎么查询都不会多出数据,因为其他事务根本没法插入了
场景2:
事务1普通select查询whereid=3
事务2插入insertid=3然后提交
事务1普通select查询whereid=3会查询不到数据
事务1如果直接updatexx whereid=3此时数据就会被更新到,再次查
为什么会被更新,因为当前读语法必须保证读到最新已经被提交的数
询就会查询出数据,产生幻读现象
那一旦更新到数据了,此时这条数据的undolog版本链最新的事务id就
是当前的事务id了
所以一旦事务1再次进行普通select查询,那这条数据就会被查询出出来
对于事务1来说就是两次查询结果不一样发生幻读
场景3:
T1时刻:事务1先执行快照读语句 select * from test where id>100;得到3条记录
T2时刻:事务2插入一个id=200的记录并提交
T3时刻:事务1再执行快照读语句 select * from test where id>100;得到4条记录

要避免这类特殊场景下发生幻读的现象的话,就是尽量在开启事之后
,马上执行select…for update这类当前读的语句,因为它会对记
录加next-keylock,从而避免其他事务插入一条新记录
其实场景2和场景3本质上都是先快照读后来发生了当前读针对这种
情况mysql确实没办法,
换个角度去思考的话这种问题一定是问题嘛?其实也不一定看你如何
定义,像大厂高并发业务场景的数据库隔离级别都会调整后成rc的,大家
都认为幻读不一定是问题,需要根据业务场景自行处理

序列化 最慢的所有事务串行执行,没什么好说的


网站公告

今日签到

点亮在社区的每一天
去签到