mysql 默认的事务隔离级别是 可重复读(repeatable read)。这是 mysql innodb 存储引擎的默认设置,也是 mysql 与其他数据库(如 oracle、postgresql 默认使用 read committed)的重要区别。
一、为什么选择 repeatable read 作为默认级别?
1. 保证数据一致性的最佳平衡
-- 在 repeatable read 级别下: -- 事务 a start transaction; select * from accounts where user_id = 1; -- 第一次查询 -- 事务 b(同时执行) start transaction; update accounts set balance = balance + 100 where user_id = 1; commit; -- 事务 a 再次查询 select * from accounts where user_id = 1; -- 结果与第一次相同(不可重复读被解决) commit;
优势:
- 防止不可重复读:同一事务内多次读取同一数据,结果一致
- 防止脏读:不会读取未提交的数据
- 一定程度防止幻读:通过 mvcc + next-key locking 机制
2. 适应 mysql 主从复制架构
mysql 的复制机制(特别是基于语句的复制)要求:
- binlog 格式:默认使用基于语句的复制(statement-based replication)
- 可重复读确保一致性:在 repeatable read 下,事务内的 select 看到的是同一快照,使得 binlog 中的语句在从库重放时结果一致
-- 主库执行(基于语句的复制) start transaction; insert into orders values (1, 'pending'); -- 此时其他事务修改了数据 update orders set status = 'shipped' where id = 1; commit; -- 从库重放 binlog 时,需要保证 update 语句看到的数据与主库一致 -- repeatable read 的快照机制保证了这一点
3. innodb 的 mvcc 实现优势
innodb 通过 多版本并发控制(mvcc) 实现 repeatable read:
-- mvcc 工作原理简示
-- 数据行结构
| id | name | balance | trx_id | roll_pointer |
|----|------|---------|--------|--------------|
| 1 | alice| 1000 | 100 | 指向旧版本 |
-- 事务 101 读取时:
-- 1. 创建 read view(包含活跃事务列表)
-- 2. 只读取 trx_id < 101 且已提交的数据
-- 3. 通过 undo log 构造历史版本
mvcc 在 repeatable read 下的特点:
- 事务开始时创建 read view,整个事务期间都使用这个快照
- 读操作不需要加锁,提高并发性能
- 写操作使用 next-key locking 防止幻读
4. 历史兼容性和应用习惯
- 历史原因:早期 mysql 的 myisam 不支持事务,innodb 引入后选择了与标准 sql 一致的默认级别
- 应用习惯:许多 php、java 应用基于 mysql 开发,默认级别已被广泛接受
二、不同隔离级别的对比
| 隔离级别 | 脏读 | 不可重复读 | 幻读 | innodb 实现机制 |
|---|---|---|---|---|
| read uncommitted | ❌ 可能 | ❌ 可能 | ❌ 可能 | 无锁读取最新数据 |
| read committed | ✅ 防止 | ❌ 可能 | ❌ 可能 | 每次 select 创建新 read view |
| repeatable read (默认) | ✅ 防止 | ✅ 防止 | ⚠️ 部分防止 | 事务开始创建 read view + next-key lock |
| serializable | ✅ 防止 | ✅ 防止 | ✅ 防止 | 所有 select 自动加锁 |
三、repeatable read 的实际效果
1. 快照读(一致性非锁定读)
-- 事务 a start transaction; -- 创建 read view -- 此时数据:id=1, balance=1000 select balance from accounts where id = 1; -- 返回 1000 -- 事务 b 修改数据并提交 start transaction; update accounts set balance = 2000 where id = 1; commit; -- 事务 a 再次读取(不可重复读被防止) select balance from accounts where id = 1; -- 仍然返回 1000 commit; -- 事务提交后读取最新数据 select balance from accounts where id = 1; -- 返回 2000
2. 当前读(锁定读)
-- 使用 for update 或 lock in share mode 进行当前读 start transaction; select * from accounts where id = 1 for update; -- 获取最新数据并加锁 -- 其他事务的 update 会被阻塞
3. 幻读的防止
-- 事务 a
start transaction;
select * from users where age > 20; -- 返回 5 条记录
-- 事务 b 插入新记录
start transaction;
insert into users (name, age) values ('bob', 25); -- 会被阻塞(next-key lock)
commit; -- 等待事务 a 释放锁
-- 事务 a 再次查询
select * from users where age > 20; -- 仍然返回 5 条记录
commit; -- 释放锁,事务 b 的插入才能执行
四、如何查看和修改隔离级别
查看当前隔离级别
-- 查看全局隔离级别 select @@global.transaction_isolation; -- 查看会话隔离级别 select @@session.transaction_isolation; -- 查看当前连接的隔离级别 select @@transaction_isolation; -- 输出示例:repeatable-read
修改隔离级别
-- 修改当前会话的隔离级别 set session transaction isolation level read committed; -- 修改全局隔离级别(重启后生效) set global transaction isolation level read committed; -- 或在 my.cnf 中配置 [mysqld] transaction-isolation = read-committed
五、什么时候应该调整隔离级别?
推荐使用 read committed 的场景
-- 1. 高并发写入场景 set session transaction isolation level read committed; start transaction; -- 较少的锁冲突,更高的并发度 -- 2. 使用基于行的复制(row-based replication) -- 需要配合修改配置 set global binlog_format = 'row'; -- 3. oracle 迁移到 mysql 的应用 -- 保持与 oracle 默认行为一致
坚持使用 repeatable read 的场景
-- 1. 金融交易系统(需要高度一致性) start transaction; -- 多次读取余额必须一致 -- 2. 报表系统(需要一致的数据视图) start transaction; -- 生成报表期间数据不应变化 -- 3. 使用基于语句的复制(默认) -- 保证主从数据一致性
六、注意事项和最佳实践
1. 长事务问题
-- 长事务在 repeatable read 下会导致问题 start transaction; select * from large_table; -- 创建快照 -- 长时间不提交... -- 结果:undo 日志堆积,影响性能 -- 建议:设置事务超时 set session max_execution_time = 5000; -- 5秒超时
2. 监控和调优
-- 监控长事务
select
trx_id,
trx_started,
timediff(now(), trx_started) as duration,
trx_state
from information_schema.innodb_trx
order by trx_started;
-- 查看锁等待
select * from information_schema.innodb_lock_waits;
3. 应用程序适配
// spring boot 中配置隔离级别
@configuration
public class datasourceconfig {
@bean
public platformtransactionmanager transactionmanager(datasource datasource) {
datasourcetransactionmanager tm = new datasourcetransactionmanager(datasource);
tm.setdefaulttimeout(30); // 设置超时
tm.setdefaultisolationlevel(transactiondefinition.isolation_repeatable_read);
return tm;
}
}
// 或在具体方法上指定
@transactional(isolation = isolation.read_committed, timeout = 10)
public void updateaccount(account account) {
// ...
}
七、与其他数据库的对比
| 数据库 | 默认隔离级别 | 备注 |
|---|---|---|
| mysql (innodb) | repeatable read | mvcc + next-key lock 实现 |
| oracle | read committed | 多版本读一致性实现不同 |
| postgresql | read committed | 使用 mvcc,但默认级别不同 |
| sql server | read committed | 可通过快照隔离提升 |
总结
mysql 选择 repeatable read 作为默认隔离级别,主要基于:
- 数据一致性:提供了脏读和不可重复读的防止,适应大多数应用场景
- 复制兼容:配合基于语句的复制,保证主从数据一致性
- 性能平衡:mvcc 实现读不加锁,写操作通过 next-key locking 防止幻读
- 历史兼容:长期默认设置,生态系统已适应
最佳实践建议:
- 大多数情况:使用默认的 repeatable read
- 高并发写入:考虑切换到 read committed + row 格式 binlog
- 金融系统:保持 repeatable read,注意长事务控制
- 迁移应用:根据源数据库特性调整隔离级别
理解隔离级别的选择对于设计高性能、高可用的 mysql 应用至关重要。
以上就是深入解析mysql中默认的事务隔离级别与选择原因的详细内容,更多关于mysql事务隔离级别的资料请关注代码网其它相关文章!
发表评论