事务隔离级别选错了,数据可能被“吞”掉——从脏读到幻读,一次讲透

简介: 事务隔离级别是数据库并发控制的核心机制,但很多开发者和DBA对脏读、不可重复读、幻读的区别一知半解,遇到问题只能“加锁试试”。本文从四个隔离级别出发,用真实SQL案例讲透三种并发问题的本质差异,对比MySQL与PostgreSQL在默认隔离级别上的不同选择,并结合业务场景给出选型建议,帮助读者写出更可靠的事务代码。

大家好,我是小耶,写功课只是为了我踩过的坑,你们别再踩了!

你有没有遇到过这种场景:一个事务里查了两次同样的数据,结果不一样;或者查了两次同样的范围条件,结果集多了一行。很多开发遇到这种情况第一反应是“是不是缓存有问题”,但其实大概率是事务隔离级别没选对。

事务隔离级别决定了多个事务同时运行时,彼此能看到多少对方的数据。选得太低,数据可能被“污染”;选得太高,并发性能会急剧下降。今天我们从四个隔离级别出发,把脏读、不可重复读、幻读这三个概念彻底讲透。

一、先搞懂四个隔离级别

SQL标准定义了四种隔离级别,从低到高依次是:

1. READ UNCOMMITTED(读未提交) ——最低级别。事务可以读取其他事务尚未提交的数据。几乎不提供并发控制,实际生产中极少使用。

2. READ COMMITTED(读已提交,简称RC) ——只能读取其他事务已经提交的数据。避免了脏读,但无法避免不可重复读。

3. REPEATABLE READ(可重复读,简称RR) ——保证同一事务内多次读取同一数据结果一致。MySQL InnoDB的默认隔离级别。

4. SERIALIZABLE(可串行化) ——最高级别,事务完全串行执行,避免所有并发问题,但性能开销最大。

不同隔离级别能解决的问题,可以浓缩成一张表:

隔离级别 脏读 不可重复读 幻读
READ UNCOMMITTED 可能 可能 可能
READ COMMITTED 避免 可能 可能
REPEATABLE READ 避免 避免 InnoDB基本避免
SERIALIZABLE 避免 避免 避免

二、三种并发问题,用真实SQL讲清楚

脏读:读到别人“还没想好”的数据

脏读指的是一个事务读取了另一个事务尚未提交的修改。如果那个事务最终回滚了,读到的就是“脏数据”——从未真正存在过的数据。

用电商库存场景来理解:事务A开始扣减库存,把商品X的库存从100改为99,但还没提交;此时事务B查询库存,读到了99。如果事务A因支付失败回滚了,库存恢复为100,但事务B已经基于99做了后续操作——超卖了。

-- 事务A(未提交)
START TRANSACTION;
UPDATE products SET stock = stock - 1 WHERE id = 1;  -- stock从100变99
-- 此时事务B读取到stock=99(未提交的数据)
-- 事务A回滚
ROLLBACK;  -- stock恢复为100,但事务B已经用了99的数据

解决脏读,至少要用READ COMMITTED级别。

不可重复读:同一事务内,数据“变了”

不可重复读指在同一个事务内,多次读取同一数据时结果不一致。它与脏读的关键区别在于:脏读读的是未提交的数据,不可重复读读的是已提交的数据。

用财务场景理解:财务人员在事务A中查询员工张三的工资,第一次看到5000元;此时HR在事务B中给张三加薪至6000元并提交;事务A再次查询时,工资变成了6000元。同一个事务里两次查询结果不一样——这就是不可重复读。

-- 事务A(隔离级别READ COMMITTED)
START TRANSACTION;
SELECT salary FROM employees WHERE id = 1;  -- 返回5000

-- 事务B(同时执行)
START TRANSACTION;
UPDATE employees SET salary = 6000 WHERE id = 1;
COMMIT;  -- 提交修改

-- 事务A再次查询
SELECT salary FROM employees WHERE id = 1;  -- 返回6000,和第一次不一样
COMMIT;

READ COMMITTED级别无法避免这个问题。需要升级到REPEATABLE READ。

幻读:结果集“凭空多了一行”

幻读指在同一个事务内,多次查询某个范围的数据时,结果集的数量发生了变化——第一次查有5行,第二次查变成了6行。

打个比方:你在图书馆按分类找书,第一次看到书架上有5本书;等你转了一圈回来再数,变成了6本——多出来那本就是你不在的时候别人放上去的。

-- 事务A(隔离级别REPEATABLE READ)
START TRANSACTION;
SELECT * FROM orders WHERE status = 'pending';  -- 返回5行

-- 事务B(同时执行)
START TRANSACTION;
INSERT INTO orders (id, status) VALUES (100, 'pending');
COMMIT;

-- 事务A再次查询
SELECT * FROM orders WHERE status = 'pending';  -- 返回6行,多了一行!
COMMIT;

三、MySQL的RR为什么能“基本避免”幻读?

根据SQL标准,REPEATABLE READ级别应该允许幻读。但MySQL InnoDB通过MVCC(多版本并发控制)+ Gap Lock(间隙锁) 的组合,在大多数场景下实际避免了幻读。

  • MVCC​:事务开始时拍一次快照,后面的普通SELECT都读这个快照,别人提交了也看不见
  • Gap Lock(间隙锁) :锁定索引记录之间的空隙,阻止其他事务在范围内插入新行

但需要注意的是,​DML语句(UPDATE/DELETE)不遵循快照读​,可能看到其他事务刚提交的行。所以在某些边缘场景下,RR级别仍可能出现幻读。

四、MySQL vs PostgreSQL:默认隔离级别为什么不同?

这是一个容易被忽略但很重要的差异。

  • MySQL InnoDB默认使用REPEATABLE READ(RR)
  • PostgreSQL默认使用READ COMMITTED(RC)

为什么不同?因为两者的MVCC实现机制不同:

对比维度 MySQL InnoDB(RR) PostgreSQL(RC)
快照时机 事务开始时拍一次快照 每条SELECT重新生成快照
不可重复读 避免 RC级别会出现
幻读 基本避免(MVCC+间隙锁) RC级别会出现
适用场景 高并发CRUD 复杂查询、数据分析

MySQL选择RR作为默认,是为了在并发性能和数据一致性之间取得平衡,适合大多数互联网业务场景。PostgreSQL选择RC作为默认,则是因为它在RR级别下对幻读的处理更加严格,可能导致更多死锁。

五、业务场景中怎么选隔离级别?

业务场景 推荐级别 理由
金融账务、库存扣减 SERIALIZABLE 或 RR 数据一致性要求极高
电商订单、用户中心 RR(MySQL默认) 平衡性能与一致性
报表查询、数据分析 RC 查询为主,可接受不可重复读
高并发日志写入 RC 或 READ UNCOMMITTED 写入为主,对一致性要求低

选型原则​:不是隔离级别越高越好。隔离级别越高,并发性能越低。应该根据业务对一致性和性能的要求做权衡。

六、实战避坑:两个常见的隔离级别陷阱

陷阱1:在RC级别下做“先查后改”

很多业务逻辑是“先查询状态,再根据状态做更新”。在RC级别下,两次查询之间数据可能被其他事务修改,导致“查的时候是A,改的时候已经变成B了”——数据被“吞”了。解决方案:要么用RR级别,要么在查询时加FOR UPDATE锁住数据。

陷阱2:RR级别下的间隙锁导致死锁

RR级别通过间隙锁避免幻读,但间隙锁也是死锁的常见来源。在RR级别下做范围删除或范围更新,可能锁住大量不存在的“间隙”,多个事务互相等待形成死锁。如果业务场景不需要防止幻读,可以考虑降级到RC级别,牺牲一点一致性换取更高的并发性能。

总结

事务隔离级别是数据库并发控制的核心机制,直接影响数据的正确性和系统的吞吐量。理解脏读、不可重复读、幻读的本质差异,知道MySQL为什么选择RR、PostgreSQL为什么选择RC作为默认,你就能在业务场景中做出更合理的选择,写出更可靠的事务代码。

小耶在手,SQL 不愁

还有什么想了解的,欢迎留言!小耶一定知无不言言无不尽……我们下次见~

相关文章
|
25天前
|
存储 人工智能 关系型数据库
湖库一体:2026年数据库架构的“终极答案”还是新瓶装旧酒?
2026年6月,OceanBase发布湖库一体AI数据库,阿里云PolarDB年初已推出AI数据湖库(Lakebase),Databricks也在6月推出了LTAP架构。“湖库一体”成为2026年数据库圈最热的概念之一。本文从湖库一体的概念定义出发,拆解其技术原理,对比“湖仓一体”与“湖库一体”的差异,分析三大厂商的落地路径,并讨论这一趋势对DBA和架构师的现实意义。
|
2月前
|
人工智能 Cloud Native 关系型数据库
MySQL 8.4 LTS来了!从8.0到8.4,DBA必须知道的5个核心变化
MySQL 8.0社区版将于2026年结束生命周期,8.4 LTS作为首个长期支持版本,提供5年超长支持周期(至2031年)。本文从InnoDB并行查询、Redo Log动态容量、默认认证插件变更、参数默认值调整、云原生适配五个维度,梳理DBA升级前必须掌握的核心变化,并提供升级检查清单。
|
2月前
|
SQL 运维 自然语言处理
国产向量数据库有哪些?两大技术流派深度对比与选型指南
向量数据库是2026年数据库领域增长最快的细分赛道之一。本文从RAG应用和企业知识库的实际需求出发,系统梳理国产向量数据库的两大技术流派——独立向量数据库与融合型向量数据库,深入对比两者的架构差异、适用边界和选型逻辑。
|
2月前
|
SQL 人工智能 自然语言处理
Vibe Coding 是什么?当“感觉编程”遇上数据库
Vibe Coding是2026年编程圈最火的概念之一,指开发者通过自然语言描述“感觉”或“意图”,由AI自动生成代码、调试、优化。本文从Vibe Coding的起源讲起,分析它如何改变数据库开发方式:从手写SQL到自然语言查询、从人工调索引到AI推荐、从经验运维到智能诊断。探讨这项趋势对DBA职业的影响,并给出拥抱变化的实用建议。技术会变,但人的判断力、审美和业务理解才是长期竞争力。
|
13天前
|
SQL 监控 关系型数据库
锁等待比死锁更隐蔽:不报错、不告警、只默默变慢
死锁是最“显眼”的锁问题——它会直接报错,DBA一眼就能看到。但真正让系统“卡住”的,往往是那些不报错、不告警、只默默等待的锁等待问题。一条SQL平时0.1秒,今天突然3秒,执行计划没变、索引没坏、数据量也没暴涨——背后可能是一条长事务在锁着关键行。本文从锁等待的排查方法出发,讲解如何通过系统视图定位锁等待链、如何识别长事务、如何评估锁等待对系统性能的影响,帮助读者在死锁日志之外,建立完整的锁问题排查能力。
|
20天前
|
关系型数据库 MySQL 数据库
字符集没统一,DBA的头发就是这么掉光的
本文讲解数据库字符集(UTF8/GBK/Latin1)和排序规则(collation)的底层原理,分析数据迁移乱码、JOIN因collation不同走不了索引、emoji存储失败等常见问题的根因,给出字符集选型建议和排查方法。
|
21天前
|
SQL 监控 关系型数据库
SQL调优进阶:从“优化一条SQL”到“优化一个系统”的思维升级
很多DBA在SQL调优上已经驾轻就熟——看执行计划、加索引、改写法,单条SQL的优化能力很强。但系统性的性能问题,往往不是“一条SQL慢”导致的。本文从“单条SQL视角”升级到“系统视角”,教读者如何从全局定位性能瓶颈、如何建立系统化的调优策略,让优化从“修修补补”变成“系统重构”。
|
2月前
|
存储 SQL 缓存
InnoDB索引结构深潜:B+Tree与回表机制的底层逻辑
索引是SQL性能优化的核心,但很多人只停留在“建索引就能快”的层面,对索引的底层结构缺乏认知。本文从B+Tree的数据结构出发,深入讲解聚簇索引与二级索引的存储差异、回表机制的工作流程及代价分析、覆盖索引消除回表的原理。
|
27天前
|
SQL JSON 关系型数据库
执行计划中的“隐藏信息”:读懂optimizer trace,看透优化器的每一步决策
小耶分享MySQL优化干货:EXPLAIN只告诉你“选了什么”,而Optimizer Trace才是揭秘优化器“为什么这么选”的关键工具!它像行车记录仪,完整追踪成本估算、索引选择、规则应用全过程。四步启用,精准定位全表扫描等异常根因,助你从执行计划阅读者进阶为优化器理解者。
|
29天前
|
SQL 关系型数据库 MySQL
从“会写”到“会调”:SQL调优进阶的5个思维升级
小耶分享SQL调优五大思维升级:从“靠猜”到“让数据说话”,从单条SQL到系统视角,看全EXPLAIN而非只盯type,用假设驱动替代试错,从救火转向防火式质量管理。重在方法论,不止于技巧。