事务隔离级别
查询与设置
-- 查询(MySQL 8.0 用 transaction_isolation 代替 tx_isolation)
SELECT @@GLOBAL.transaction_isolation, @@transaction_isolation;
-- 设置会话级
SET SESSION transaction ISOLATION LEVEL READ UNCOMMITTED;
四种隔离级别对比
| 隔离级别 | 脏读 | 不可重复读 | 幻读 | 加锁情况 | 适用 |
|---|---|---|---|---|---|
| READ UNCOMMITTED(读未提交) | ✓ | ✓ | ✓ | 几乎不加锁 | 几乎不用 |
| READ COMMITTED(读已提交) | ✗ | ✓ | ✓ | 写加 X 锁;读用 MVCC 快照读 | Oracle 默认 |
| REPEATABLE READ(可重复读) | ✗ | ✗ | ✓ | RR 下用 Next-Key Lock 解决幻读 | MySQL 默认 |
| SERIALIZABLE(串行化) | ✗ | ✗ | ✗ | 读加 S 锁,写加 X 锁 | 强一致性场景 |
💡 关键点:
- MySQL 的 RR 比 SQL 标准的 RR 更强——标准 RR 只解决”不可重复读”,幻读未解决;MySQL InnoDB 的 RR 用 Next-Key Lock 解决了幻读
- Oracle、PostgreSQL 默认 RC,不解决幻读
- “不可重复读”侧重单条记录值被修改;“幻读”侧重记录数量变化(被 INSERT/DELETE)
Spring 事务传播机制
Spring 的 Propagation 枚举定义了 7 种事务传播行为,核心关注”内外方法都有事务时怎么处理”。
| 传播行为 | 含义 |
|---|---|
REQUIRED(默认) | 外层有事务 → 加入外层;外层没事务 → 开启新事务。一起提交一起回滚 |
REQUIRES_NEW | 总是开启新事务,挂起外层事务;新事务执行完恢复外层 |
SUPPORTS | 外层有事务 → 加入;外层没事务 → 非事务方式执行 |
NOT_SUPPORTED | 不支持事务;外层有事务则挂起,内层执行完恢复 |
NESTED | 外层有事务 → 嵌套子事务(Savepoint);外层没事务 → 同 REQUIRED |
MANDATORY | 必须在已有事务中执行,否则抛异常 |
NEVER | 必须在无事务中执行,否则抛异常 |
实战选型
REQUIRED:99% 场景,默认就是这个REQUIRES_NEW:典型场景”记录操作日志”——主业务失败回滚时,操作日志仍然要保留,所以日志写入必须用独立事务NESTED:内层可以独立回滚(通过 Savepoint),外层事务不受影响SUPPORTS/NOT_SUPPORTED:基本不会主动用MANDATORY/NEVER:测试场景,强制约束调用栈
多数据源事务管理
痛点

默认 @Transactional 只能管单数据源事务。多个数据源时:
- 事务 A(数据源 1)开启 → 事务 B(数据源 2)执行 → 数据源 2 抛异常
- 数据源 2 因为没在事务中执行,结果不会回滚
- 数据源 1 捕获异常后回滚 → 两个数据源数据不一致
解决方案:ChainedTransactionManager
Spring 提供的链式事务管理器:
@Bean
public PlatformTransactionManager transactionManager() {
return new ChainedTransactionManager(
txManagerDataSource1, // 按顺序开启
txManagerDataSource2
);
}
原理:按设置顺序依次开启事务,倒序提交。任何一步失败则整体回滚。
A 开启 → B 开启 → A 执行 → B 执行 → B 提交 → A 提交
⚠️ ChainedTransactionManager 已废弃(Spring 5.x),原因:它本质是两阶段提交(2PC)的非标准实现,无法保证严格一致性。生产推荐:使用 Seata / Atomikos 等真正支持 XA 事务的分布式事务方案。
查询请求处理流程
完整流程
1. 客户端发 SQL 到服务器
2. 服务器检查查询缓存是否命中(MySQL 8.0 已移除此步)
3. 分析器解析 SQL → 优化器生成执行计划
4. 根据执行计划,调用存储引擎 API 查数据
5. 将结果返回客户端
查询缓存细节(仅 MySQL 5.7 及之前)
SHOW VARIABLES LIKE 'query_cache_type'; -- ON / OFF / DEMAND
SHOW VARIABLES LIKE 'query_cache_size'; -- 缓存内存大小
SHOW VARIABLES LIKE 'query_cache_limit'; -- 单条结果最大缓存值
特性:
- 通过大小写敏感的 Hash 查找实现,必须 SQL 完全一致才命中
- 表被修改时,相关查询缓存全部清空
- 8.0 彻底移除——命中率低 + 维护成本高
- 加锁检查缓存可能反而降低查询性能
DEMAND 模式下手动控制:
SELECT SQL_CACHE * FROM user WHERE id = 1; -- 走缓存
SELECT SQL_NO_CACHE * FROM user WHERE id = 1; -- 不走缓存
实战建议
- 优先用默认 RR:MySQL 的 RR 解决了幻读,比标准 RR 更强
- 慎用 SERIALIZABLE:并发性能差,除非对一致性要求极强
@Transactional加在哪个方法上:默认只对 public 方法 + 外部调用生效,自调用(this.xxx)不触发代理- 多数据源优先选 Seata 分布式事务,不要用 ChainedTransactionManager
- 慢查询日志一定要开:
long_query_time=0.1(100ms)
下一篇会讲”实战调优”:SQL 注入防护(PreparedStatement)、MySQL 8.0 新特性、版本选择与升级方法论。