事务隔离级别
事务隔离级别定义并发事务之间的可见性和冲突处理强度,是理解脏读、不可重复读、幻读与并发代价的核心入口。
[!info] related notes
事务隔离级别
一句话定义
事务隔离级别定义了并发事务之间能看到多少彼此的中间结果,以及数据库愿意为此付出多少锁、版本管理和等待成本。
为什么事务还需要“隔离级别”
事务并不天然意味着“并发下一定安全”。
单个事务内部可以是原子的,但多个事务并发运行时仍然会出现:
- 读到别人还没提交的数据
- 同一事务里前后两次读取结果不一致
- 条件查询前后结果集变化
- 两个事务基于旧值同时做决定,最终互相覆盖
隔离级别就是数据库给出的并发语义选择:
- 保证更强,通常意味着更多等待、锁冲突或重试成本
- 保证更弱,通常意味着更高吞吐,但应用要接受更多并发异常风险
核心机制 / 工作原理
1. 隔离级别本质上在定义“谁能看见什么”
一个事务在执行过程中,最关键的问题包括:
- 它能不能看到别的事务未提交的修改
- 它在同一次事务里第二次读取时,是否还能看到和第一次一样的数据
- 它按某个条件查询出的结果集,之后会不会凭空多出或少掉一些行
隔离级别就是对这些可见性问题做不同强度的承诺。
2. 常见的四级隔离
常见隔离级别从低到高通常是:
- 读未提交
READ UNCOMMITTED - 读已提交
READ COMMITTED - 可重复读
REPEATABLE READ - 串行化
SERIALIZABLE
它们常围绕三类经典异常来讲:
- 脏读:读到了别的事务还没提交的数据
- 不可重复读:同一事务里两次读同一行,结果不一致
- 幻读:同一事务里两次按同条件查询,结果集行数变了
但真实系统里,光背这三类还不够,因为很多业务 bug 其实更接近“丢失更新”或“写偏斜”。
3. 不同隔离级别,数据库通常会动用不同工具
数据库并不是单靠一个配置开关就实现隔离,而是会组合:
所以隔离级别真正决定的是:
- 当前事务看到的数据快照有多稳定
- 并发事务之间允许多大程度的交错
- 冲突发生时数据库更倾向于“阻塞”还是“回滚其中一方”
四个级别可以怎样理解
1. 读未提交
这是最弱的隔离。
它允许事务读到别的事务尚未提交的数据,所以会有明显的脏读风险。工程上很少作为默认业务隔离使用,因为它让“看到的值到底算不算数”本身都变得不可靠。
2. 读已提交
它至少保证:
- 你不会读到别人尚未提交的数据
但它不保证:
- 同一事务里两次读取同一行还能看到同样结果
- 条件查询前后结果集不变
很多数据库把它作为默认隔离,因为它在正确性和吞吐之间比较均衡。
3. 可重复读
它想解决的是:
- 同一事务中反复读同一批数据时,希望看到一个更稳定的视图
这通常意味着:
- 事务会基于某个一致性快照工作
- 对同一行的重复读取更容易保持一致
但不同数据库对“可重复读”这个名字背后的实现并不完全相同,所以不能只背标准名字,不看产品细节。
4. 串行化
这是最强的隔离目标。
它希望并发事务的最终效果等价于“一个一个顺序执行”。这能减少大量并发异常,但代价通常是:
- 更多锁冲突或序列化失败
- 更多等待
- 更高重试成本
所以它通常不是默认开到最强,而是只在某些强一致业务边界里使用。
最小例子 / 最小场景
假设事务 A 查询“余额大于 1000 的账户数”,事务 B 在中间插入一条符合条件的新账户:
- 隔离较弱时,事务 A 第二次再查,结果集可能多一行
- 隔离更强时,事务 A 可能继续看到第一次查询时的同一个一致性视图
- 再更强时,事务 B 甚至可能被阻塞,或者其中一方需要回滚重试
这个例子说明,隔离级别关注的不是某一条 SQL 的语法,而是并发执行时系统如何安排可见性和冲突。
从理解顺序上,最该先抓住什么
1. 隔离级别不是“越高越好”
更高隔离通常意味着:
- 更长等待时间
- 更多锁
- 更高资源消耗
- 更高事务失败和重试概率
所以选择隔离级别,本质上是在“正确性风险”和“并发吞吐”之间做工程权衡。
2. 名字相同,不代表实现相同
同样叫 REPEATABLE READ,MySQL 和 PostgreSQL 在:
- 版本可见性实现
- 是否使用 gap lock 一类额外锁策略
- 幻读和范围更新的处理方式
上都可能不同。
3. 应用真正关心的是“哪些异常我不能接受”
例如:
- 转账、扣库存、抢名额,往往比普通列表查询更怕并发写冲突
- 报表或后台浏览场景,通常可以接受更弱隔离换更高吞吐
因此先识别业务不变量,再倒推隔离级别,通常比反过来更合理。