1.2.3、从“本事务读”和“阻塞别的事务”角度看 Mysql 的事务和锁
文章目录
- 开篇:事务隔离级别不但影响读也会影响写
- Mysql 默认事务为:可重复读
- 一、核心概念:可重复读 & 幻读
- 二、数据库标准 vs MySQL 实际
- 三、锁类型与关系
- 四、读写加锁与阻塞
- 五、MVCC 如何实现可重复读 & 幻读
- 六、串行化的读写:几乎锁表
- 七、加锁模式:用户 vs 引擎,行 vs 区间
- 八、事务中的 JOIN(表级?)
- 九、可重复读下:单条、范围、不存在的 id
- 十、混合事务隔离级别实战
- 十一、总结表格(速查)
- 十二、最佳实践(实际开发必看)
- 十三、MVCC 和 锁的触发
- 十四、MySQL 的 事务隔离级别(Transaction Isolation Level)
- 事务隔离级别(Isolation Level)是什么?
- MySQL 支持的 4 种标准事务隔离级别
- 这些隔离级别主要解决什么并发问题?
- MySQL 的默认隔离级别:REPEATABLE READ
- 如何查看和设置 MySQL 的事务隔离级别?
- 各隔离级别对比总结
- 实际开发中怎么选?
- 十五、"本事务读"和"阻塞谁"
- 事务隔离级别(完整版)——既要考虑“读到什么”,也要考虑“阻塞谁”
- 从【阻塞其他事务】的角度深入解析
- 写操作(如 UPDATE / DELETE)
- 范围查询(如 WHERE age > 20)
- 读操作(SELECT)
- 阻塞的本质:锁机制(简要回顾)
- 十六、一句话总结(读 + 阻塞双视角)
- 核心结论
- 开发中你应该如何选择与思考?
- 十七、MyISAM 的事务和锁机制(对比 InnoDB)
- MyISAM 不支持事务
- MyISAM 的锁机制
- MyISAM 的锁类型
- MyISAM 的自动加锁规则
- MyISAM 的适用场景
- 如何查看和切换存储引擎?
- 总结
开篇:事务隔离级别不但影响读也会影响写
本文基于 Mysql InnoDB 存储引擎
MySQL 的事务隔离级别不仅决定了你在一个事务中能读到哪些数据(读的视角),更重要的是,它通过锁机制(如记录锁、间隙锁、临键锁)控制你的写操作是否会阻塞其他事务,从读与写两个维度共同保障事务的一致性与隔离性。
- 事务的 四个特性(ACID):原子性(atomicity,或称不可分割性)、一致性(consistency)、隔离性(isolation,又称独立性)、持久性(durability)
Mysql 默认事务为:可重复读
简单说就是,本未提交事务内,单条数据多次查询,存在性不变。
普通的 select 范围查询因为不会锁数据或区间,有幻读风险。
修改数据会有阻塞其它事务的可能,查询使用 for update 模式,导致锁,可能会影响其它事务的提交。
一、核心概念:可重复读 & 幻读
🔍 实际开发注意:
可重复读 ≠ 幻读安全,除非你用加锁查询(如 FOR UPDATE),否则普通 SELECT 仍可能幻读!
二、数据库标准 vs MySQL 实际
MySQL 的可重复读通过 MVCC + 临键锁(加锁时) 实现比标准更强的隔离性,但 普通 SELECT 仍可能幻读。
三、锁类型与关系
关系:临键锁 = 记录锁 + 间隙锁,是避免幻读的核心
锁类型 | 锁对象 | 作用 | 是否阻塞其他事务 | 触发方式 |
---|---|---|---|---|
记录锁(Record Lock) | 单条索引记录(如主键 id=100) | 锁住具体某行 | ✅ 阻塞其他事务对该行写 | 自动(写操作)或手动(FOR UPDATE) |
间隙锁(Gap Lock) | 索引之间的“间隙”(如 id 在 (10,20)) | 防止插入新数据 | ✅ 阻塞其他事务插入该间隙 | 自动(可重复读下范围查询/写) |
临键锁(Next-Key Lock) = 记录锁 + 间隙锁 | 某索引值 + 它之前的间隙(如 id=15 + (10,15)) | 防止幻读,锁住范围 | ✅ 阻塞插入与修改 | 自动(范围查询加锁时) |
四、读写加锁与阻塞
操作类型 | 是否加锁 | 是否阻塞其他事务 | 由谁触发 | 说明 |
---|---|---|---|---|
普通 SELECT | ❌ 不加锁 | ❌ 不阻塞 | 自动(MVCC) | 快照读,不保证范围安全 |
SELECT … FOR UPDATE | ✅ 加排他锁 | ✅ 阻塞其他读写 | 手动 | 显式加锁,控制并发 |
SELECT … LOCK IN SHARE MODE | ✅ 加共享锁 | ✅ 阻塞其他写 | 手动 | 共享读锁 |
INSERT / UPDATE / DELETE | ✅ 自动加锁 | ✅ 阻塞其他事务 | 自动 | 基于索引与隔离级别 |
范围查询(如 WHERE age > 20) | ❌ 普通查询不加锁 ✅ 加锁查询加临键锁 | ✅ 加锁则阻塞插入 | 自动/手动 | 普通查询可能幻读 |
✅ 总结:
读不一定会加锁(快照读),但写和加锁查询一定会加锁,阻塞其他事务,保证一致性。
五、MVCC 如何实现可重复读 & 幻读
核心:
MVCC 用于实现快照读,让事务内读取一致的数据版本,但范围查询仍可能不安全,除非加锁。
隔离级别 | 是否用 MVCC | 能否看到其他事务提交? | 幻读风险 | 说明 |
---|---|---|---|---|
读未提交 | ❌ 不使用 | ✅ 能看到未提交的修改(脏读) | 高 | 无隔离 |
读已提交 | ✅ 使用 | ✅ 只能看到已提交的最新数据 | 中(不可重复读 + 幻读) | 每次读最新 |
可重复读 | ✅ 使用 | ❌ 事务内多次读一致(防不可重复读) ✅ 但范围查询可能幻读(除非加锁) | 低(有条件) | MySQL 通过临键锁避免幻读(加锁时) |
串行化 | ❌ 基本不用 | ❌ 完全串行,等价锁表 | 无 | 最高隔离,性能差 |
六、串行化的读写:几乎锁表
实际开发:
一般只用在需要强一致且并发极低的场景,比如财务对账,绝大多数业务应避免。
隔离级别 | 读写行为 | 锁行为 | 性能 | 说明 |
---|---|---|---|---|
串行化(Serializable) | 读写都可能加锁 | 几乎等价于锁表或锁范围 | ❌ 极差 | 所有操作串行执行,避免并发问题 |
七、加锁模式:用户 vs 引擎,行 vs 区间
总结:
用户一般只手动加 FOR UPDATE,其他大部分锁是 InnoDB 自动加的,基于你的 SQL 和事务设置。
加锁类型 | 锁粒度 | 由谁触发 | 说明 |
---|---|---|---|
用户显式加锁 | 行/表 | 用户(如 FOR UPDATE) | 手动控制并发 |
数据库隐式加锁 | 行/间隙/范围 | InnoDB 引擎(自动) | 基于 SQL 类型、索引、隔离级别 |
行锁 | 单条记录(如主键) | 自动/手动 | 精准,性能好 |
间隙锁 | 索引间隙 | 自动(可重复读) | 防止插入 |
临键锁 | 行 + 间隙 | 自动(防幻读) | 最常用 |
表锁 | 整表 | 自动(无索引/串行化) | 性能最差 |
八、事务中的 JOIN(表级?)
实际:
事务是一组 SQL 操作的整体,JOIN 只是其中一条语句,同样受事务隔离级别控制。
问题 | 说明 |
---|---|
事务是表级别的? | ❌ 不是,事务是 连接级别 / 语句级别 的,不是针对某张表 |
LEFT JOIN 会怎样? | 事务隔离级别作用于整个事务,包括 JOIN,遵循当前事务的 MVCC 或加锁规则 |
九、可重复读下:单条、范围、不存在的 id
查询类型 | 其他事务插入新数据后,本事务是否能查到? | 说明 |
---|---|---|
单条记录(如 id=100),一开始不存在 | ❌ 看不到(MVCC 保护) | 你的事务有快照,其他事务插入的,你默认看不到 |
范围查询(如 WHERE age > 20),普通 SELECT | ✅ 可能看到(幻读) | 其他事务插入符合范围的数据,你可能查到 |
范围查询 + 加锁(如 FOR UPDATE) | ❌ 看不到 | 临键锁阻止其他事务插入,你查不到新数据 |
十、混合事务隔离级别实战
问题:
如果两个事务,一个配置为 可重复读(REPEATABLE READ),一个为 读未提交(READ COMMITTED),执行“存在则更新,否则插入”逻辑,会怎样?
答案:
问题点 | 说明 |
---|---|
隔离级别不同 | 两个事务看到的数据版本可能不同,一个读快照,一个读最新 |
存在则插入(UPSERT) | 若先查询(SELECT)再插入(INSERT)不是原子操作,可能引发:主键冲突 / 幻读 / 重复插入 |
推荐方案 | ✅ 使用 INSERT … ON DUPLICATE KEY UPDATE ✅ 或 SELECT … FOR UPDATE + 事务包裹,保证原子性 |
实际开发:
不要依赖“先查后写”的逻辑,一定要加锁或使用数据库原子操作,否则并发必出问题!
十一、总结表格(速查)
问题 | 核心结论(精简) |
---|---|
1. 可重复读 & 幻读 | 可重复读保证单条数据一致,但范围查询可能幻读(除非加锁) |
2. 标准 vs MySQL | 标准允许幻读,MySQL 通过临键锁避免(加锁时) |
3. 锁类型 | 记录锁、间隙锁、临键锁,临键锁防幻读 |
4. 加锁与阻塞 | 读一般不加锁(快照),写/加锁查询会加锁,阻塞其他事务 |
5. MVCC 作用 | 可重复读/读已提交用 MVCC 快照读,串行化/读未提交一般不用 |
6. 串行化 | 几乎锁表,读写都串行,性能差 |
7. 加锁模式 | 用户显式(FOR UPDATE)、引擎隐式(自动加行/间隙/临键锁) |
8. JOIN 事务 | 事务隔离作用于整个连接,包括 JOIN |
9. 特殊读写 | 普通 SELECT 范围查询可能幻读,加锁则不会 |
10. 混合事务 | 不同隔离级别并发操作可能出问题,务必用原子 SQL 或加锁 |
十二、最佳实践(实际开发必看)
场景 | 建议 |
---|---|
范围查询且要保证数据不变化(如统计、列表) | ✅ 用 SELECT … FOR UPDATE 或确保业务能接受新数据 |
先查后写(如存在则更新,否则插入) | ❌ 不要直接 SELECT + INSERT,用 ON DUPLICATE KEY UPDATE 或加锁 |
普通单条查询 | ✅ 普通 SELECT 即可,一般无并发问题 |
高并发写入 | ✅ 合理设计索引,避免全表锁 / 大范围锁 |
事务配置 | ✅ 明确事务边界,统一团队隔离级别(推荐可重复读) |
十三、MVCC 和 锁的触发
• 普通 SELECT(不加锁) → 依靠 MVCC,不加锁,不阻塞别人,但可能读到幻影行。
• 加锁查询(如 FOR UPDATE) → 自动加 临键锁 / 记录锁,阻塞其他事务的写或插入。
• 写操作(INSERT/UPDATE/DELETE) → 自动加锁,基于索引和隔离级别,防止并发冲突。
概念 | 图示要点 | 说明 |
---|---|---|
锁类型 | 记录锁(行)、间隙锁(间隙)、临键锁(行+间隙) | 用于控制并发写与范围安全,防止脏写、幻读 |
锁触发 | 普通 SELECT(不加锁)→ 无锁 加锁查询(FOR UPDATE)→ 临键锁 写操作 → 自动加锁 | 加锁行为决定事务间是否会阻塞 |
MVCC | ReadView + 版本链(DB_ROLL_PTR) | 让事务读取“某一时刻的快照”,实现读一致性 |
MVCC 目的 | 避免读写互相阻塞,事务内多次读一致 | 不解决幻读(除非加锁或使用临键锁) |
十四、MySQL 的 事务隔离级别(Transaction Isolation Level)
MySQL 的 事务隔离级别 是用来 控制多个并发事务之间互相影响的程度 的机制,它定义了一个事务在操作数据时,能看到其他事务的哪些修改,以及如何防止并发带来的问题(如脏读、不可重复读、幻读等)。
事务隔离级别(Isolation Level)是什么?
事务隔离级别,就是数据库为了保证事务的隔离性(Isolation),控制事务之间相互干扰的规则级别。
它解决的问题包括:
• 一个事务能不能看到另一个事务未提交的修改?(脏读-提交又回滚)
• 一个事务能不能看到另一个事务已提交的修改?(不可重复读、幻读)
• 多个事务并发修改同一份数据时,如何保证数据一致性?
MySQL 支持的 4 种标准事务隔离级别
隔离级别名称 | 关键词 | 说明 |
---|---|---|
READ UNCOMMITTED(读未提交) | 最低隔离级别 | 一个事务可以读取到其他事务 未提交 的修改(会出现脏读、不可重复读、幻读) |
READ COMMITTED(读已提交) | 常见隔离级别 | 一个事务只能读取到其他事务 已提交 的修改(避免脏读,但仍有不可重复读、幻读) |
REPEATABLE READ(可重复读) | MySQL 默认级别 | 一个事务内多次读取同一数据,结果一致(避免脏读、不可重复读,MySQL 还通过机制避免幻读) |
SERIALIZABLE(串行化) | 最高隔离级别 | 所有事务串行执行,完全避免并发问题(最安全,但性能最差,相当于锁表) |
这些隔离级别主要解决什么并发问题?
问题 | 说明 | 哪些隔离级别可以避免 |
---|---|---|
脏读(Dirty Read) | 一个事务读到了另一个事务 未提交 的数据,如果那个事务回滚了,读到的就是脏数据 | ✅ READ COMMITTED 及以上都可避免 |
不可重复读(Non-Repeatable Read) | 一个事务内,多次读取同一行数据,结果不同,因为其他事务提交了更新 | ✅ REPEATABLE READ 及以上可避免 |
幻读(Phantom Read) | 一个事务内,两次执行同样的范围查询,返回的行数不同,因为其他事务插入了符合该范围的新数据 | ✅ SERIALIZABLE 可完全避免;MySQL 的 REPEATABLE READ 通过临键锁也能避免(特殊) |
MySQL 的默认隔离级别:REPEATABLE READ
MySQL(InnoDB 存储引擎)的默认事务隔离级别是:REPEATABLE READ(可重复读)
它通过以下机制保证事务安全:
• MVCC(多版本并发控制):让你在事务中多次读取同一行时,看到的都是事务开始时的版本(避免不可重复读);
• 临键锁(Next-Key Lock):在范围查询(需 for update)和写操作时自动加锁,避免其他事务插入“幻影行”(从而避免幻读)(这是 MySQL 与标准 SQL 的重要区别!)
如何查看和设置 MySQL 的事务隔离级别?
- 查看当前会话的隔离级别
SELECT @@transaction_isolation;
– 或者
SELECT @@session.transaction_isolation;
-
查看全局隔离级别
SELECT @@global.transaction_isolation; -
设置当前会话的隔离级别(比如设为 READ COMMITTED)
SET SESSION TRANSACTION ISOLATION LEVEL READ COMMITTED; -
设置全局隔离级别(需权限,谨慎操作)
SET GLOBAL TRANSACTION ISOLATION LEVEL READ COMMITTED;
⚠️ 修改全局隔离级别会影响之后新建的会话,默认是 REPEATABLE READ。
各隔离级别对比总结
隔离级别 | 脏读 | 不可重复读 | 幻读 | 说明 |
---|---|---|---|---|
READ UNCOMMITTED | ✅ 可能发生 | ✅ 可能发生 | ✅ 可能发生 | 最低隔离,性能高但风险极高,能看到未提交数据(脏读) |
READ COMMITTED | ❌ 不会发生 | ✅ 可能发生 | ✅ 可能发生 | 每次读最新已提交数据,但事务内多次读可能不一样 |
REPEATABLE READ(MySQL 默认) | ❌ 不会发生 | ❌ 不会发生 | ✅ 一般不会(MySQL 通过临键锁避免) | 事务内多次读一致,最常用,兼顾安全性与性能 |
SERIALIZABLE | ❌ 不会发生 | ❌ 不会发生 | ❌ 不会发生 | 最高隔离,完全串行执行,性能最差,几乎等价于锁表 |
实际开发中怎么选?
场景 | 推荐隔离级别 | 原因 |
---|---|---|
大多数业务系统(如电商、用户管理等) | REPEATABLE READ(默认) | 平衡安全性与性能,避免脏读和不可重复读,MySQL(select+for udate 或者非查询操作) 还避免了幻读 |
需要实时看到最新数据的场景(如监控、报表) | READ COMMITTED | 每次读最新已提交数据,但要注意不可重复读 |
对一致性要求极高的场景(如金融、支付对账) | SERIALIZABLE | 完全避免并发问题,但性能差,需谨慎使用 |
极少使用 | READ UNCOMMITTED | 除非你明确接受脏读,一般不推荐 |
十五、“本事务读"和"阻塞谁”
事务隔离级别(完整版)——既要考虑“读到什么”,也要考虑“阻塞谁”
隔离级别 | 读行为(能看到哪些数据) | 写/更新行为(会阻塞谁,如何保护数据) | 是否防止脏读 | 是否防止不可重复读 | 是否防止幻读 | 说明 |
---|---|---|---|---|---|---|
READ UNCOMMITTED(读未提交) | ✅ 能读到其他事务未提交的数据(脏读) | ❌ 几乎不加锁,读写一般不互相阻塞 | ❌ 不防止 | ❌ 不防止 | ❌ 不防止 | 最低隔离,性能最高,但数据可能脏,一般不使用 |
READ COMMITTED(读已提交) | ✅ 只能读到其他事务已提交的数据(每次读最新) | ✅ 写操作(如 UPDATE/DELETE)会加锁,阻塞其他事务写同一行;读不加锁(快照读少) | ✅ 防止 | ❌ 不防止 | ❌ 不防止 | 每次读最新已提交数据,但事务内多次读可能不一样 |
REPEATABLE READ(可重复读,MySQL 默认) | ✅ 事务内多次读同一数据,结果一致(读旧快照) | ✅ 写/更新操作会加锁(记录锁等); 范围查询/写操作可能加临键锁/间隙锁,阻塞其他事务插入 | ✅ 防止 | ✅ 防止 | ✅ MySQL 通过临键锁避免(加锁时) | 最常用,事务内数据一致,且通过锁机制避免并发问题 |
SERIALIZABLE(串行化) | ✅ 只能读到已提交数据,且读操作可能被升级为加锁读(等价于 SELECT … FOR SHARE) | ✅ 几乎所有读写都会加锁(或串行执行),完全避免并发冲突 | ✅ 防止 | ✅ 防止 | ✅ 防止 | 最高隔离,最安全,但性能最差,接近锁表 |
从【阻塞其他事务】的角度深入解析
事务隔离级别不仅决定你能看到什么数据,更核心的是:
它决定了你的操作(读/写)是否会阻塞其他事务的读/写,以及如何通过锁机制来保证数据一致性。
我们从 写操作、范围查询、读操作 三个常见场景来看不同隔离级别下 是否会阻塞其他事务。
写操作(如 UPDATE / DELETE)
总结:
从 写操作阻塞其他事务 的角度看,隔离级别越高,锁的范围可能越大,阻塞越强,数据一致性越强。
隔离级别 | 是否自动加锁 | 阻塞谁 | 说明 |
---|---|---|---|
READ UNCOMMITTED | ❌ 一般不加锁(直接读写最新数据) | ❌ 基本不阻塞 | 几乎无保护,可能读写脏数据 |
READ COMMITTED | ✅ 加锁(记录锁,只锁要修改的行) | ✅ 阻塞其他事务对同一行的写/更新 | 每次写操作会锁定目标行,防止并发修改冲突 |
REPEATABLE READ | ✅ 加锁(记录锁等,可能加间隙锁) | ✅ 阻塞其他事务对同一行或间隙的写/插入 | 写操作会保护数据一致性,范围写可能加间隙锁避免幻读 |
SERIALIZABLE | ✅ 几乎所有写都会加锁(或等价串行) | ✅ 阻塞所有可能冲突的读写 | 完全串行化,最安全但性能最差 |
范围查询(如 WHERE age > 20)
总结:
MySQL 的可重复读通过加锁查询(临键锁)避免幻读,但普通 SELECT 不会阻塞其他事务插入。
范围查询是否阻塞其他事务插入,取决于你是否用了加锁查询(如 FOR UPDATE)或隔离级别是否足够高(如 SERIALIZABLE)。
隔离级别 | 是否自动加锁 | 是否阻塞其他事务插入符合范围的数据 | 说明 |
---|---|---|---|
READ UNCOMMITTED | ❌ 不加锁 | ❌ 不阻塞 | 读未提交,无保护 |
READ COMMITTED | ❌ 普通查询不加锁 (除非显式 FOR UPDATE) | ❌ 不阻塞 | 范围查询不加锁,其他事务可插入并提交 |
REPEATABLE READ | ✅ 如果是普通查询,一般不加锁 ✅ 如果是加锁查询(如 FOR UPDATE),则加临键锁/间隙锁 | ✅ 加锁查询会阻塞其他事务插入该范围的数据 (通过临键锁保护) | 普通 SELECT 不会阻塞,但加锁查询(如事务内范围查询 + 写) |
SERIALIZABLE | ✅ 范围查询可能被升级为加锁读(如 FOR SHARE) | ✅ 阻塞插入 | 完全串行化,读操作也可能阻塞其他事务 |
读操作(SELECT)
总结:
普通 SELECT 在大多数隔离级别下是不加锁的(快照读),不会阻塞其他事务。
但如果你用了 SELECT … FOR UPDATE,就会加锁,阻塞其他事务的写操作。
隔离级别 | 是否加锁 | 是否阻塞其他事务 | 说明 |
---|---|---|---|
READ UNCOMMITTED | ❌ 不加锁 | ❌ 不阻塞 | 直接读最新数据,哪怕未提交 |
READ COMMITTED | ❌ 普通 SELECT 不加锁(快照读较少) | ❌ 不阻塞 | 每次读最新已提交数据 |
REPEATABLE READ | ❌ 普通 SELECT 不加锁(使用 MVCC 快照读) ✅ 加锁查询(如 FOR UPDATE)会加记录锁 | ❌ 普通读不阻塞 ✅ 加锁查询会阻塞其他事务写/更新 | 事务内多次读一致,普通读不阻塞别人,但也不保证看到最新 |
SERIALIZABLE | ✅ 普通 SELECT 可能被升级为加锁读(如 FOR SHARE) | ✅ 阻塞其他事务写 | 完全串行化,读也可能加锁 |
阻塞的本质:锁机制(简要回顾)
锁类型 | 作用对象 | 是否阻塞其他事务 | 常见触发场景 |
---|---|---|---|
记录锁(Record Lock) | 某一行(如主键 id=100) | ✅ 阻塞其他事务对该行的写 | UPDATE / DELETE / SELECT … FOR UPDATE |
间隙锁(Gap Lock) | 索引之间的间隙(如 id 在 (10,20)) | ✅ 阻塞其他事务插入该间隙 | 可重复读下的范围查询 / 写操作 |
临键锁(Next-Key Lock) = 记录锁 + 间隙锁 | 某索引值 + 它之前的间隙(如 id=15 + (10,15)) | ✅ 阻塞插入与修改 | 可重复读下范围查询 + 写操作,防止幻读 |
表锁 | 整张表 | ✅ 阻塞所有事务读写该表 | 无索引、串行化、大事务等极端情况 |
十六、一句话总结(读 + 阻塞双视角)
核心结论
问题 | 核心结论 |
---|---|
读操作能看到哪些数据? | 取决于隔离级别:读未提交(脏读)、读已提交(最新)、可重复读(事务开始时版本)、串行化(完全串行) |
写操作会阻塞谁? | 写操作通常会加锁,阻塞其他事务对同一行/范围的写或插入,隔离级别越高,阻塞范围越大 |
范围查询会阻塞插入吗? | 普通 SELECT 不会,但加锁查询(如 FOR UPDATE)或高隔离级别(如 SERIALIZABLE)会阻塞 |
如何保证事务安全? | 通过 锁(记录/间隙/临键锁) 和 MVCC(多版本快照) 两种机制共同作用,隔离级别越高,保护越强,但并发性能可能下降 |
开发中你应该如何选择与思考?
场景 | 你应该关心什么 | 推荐隔离级别 | 原因 |
---|---|---|---|
普通业务(如用户管理、订单) | 事务内数据一致,避免脏读/重复读 | REPEATABLE READ(默认) | 平衡安全性与性能,MySQL 自动避免很多问题 |
高并发实时统计 | 要求读最新数据,允许少量不一致 | READ COMMITTED | 每次读最新,但事务内可能读到不同结果 |
金融 / 支付 / 强一致性场景 | 绝对安全,宁可慢也不能错 | SERIALIZABLE | 完全避免并发问题,但性能差 |
你不确定,希望默认安全 | 不做配置,用 MySQL 默认 | REPEATABLE READ | 最常用,也是最平衡的选择 |
十七、MyISAM 的事务和锁机制(对比 InnoDB)
MyISAM 已逐渐被淘汰,现代 MySQL 默认使用 InnoDB。
如果业务需要事务、高并发、数据安全,一定要用 InnoDB。
MyISAM 仅适用于只读或低并发写入的特殊场景。
MyISAM 不支持事务
• InnoDB:支持完整的 ACID 事务(原子性、一致性、隔离性、持久性)。
• MyISAM:不支持事务,所有操作都是立即生效,无法回滚(ROLLBACK 无效)。
MyISAM 的锁机制
特性 | MyISAM | InnoDB |
---|---|---|
锁粒度 | 表级锁(读锁、写锁) | 行级锁(记录锁、间隙锁、临键锁) |
并发性能 | 低(写会阻塞所有读和写) | 高(行锁允许并发读写) |
死锁 | 不会死锁(只有表锁) | 可能死锁(行锁竞争) |
锁升级 | 无 | 行锁可能升级为表锁(如无索引或大事务) |
MyISAM 的锁类型
• 共享锁(读锁):
• 多个会话可以同时获取读锁,互不阻塞。
• 语法:LOCK TABLE table_name READ;
• 示例:
LOCK TABLE users READ; – 加读锁
SELECT * FROM users; – 允许其他会话也加读锁
UNLOCK TABLES; – 释放锁
• 排他锁(写锁):
• 一个会话获取写锁后,其他会话不能加读锁或写锁,所有操作被阻塞。
• 语法:LOCK TABLE table_name WRITE;
• 示例:
LOCK TABLE users WRITE; – 加写锁
UPDATE users SET name = ‘Alice’ WHERE id = 1; – 其他会话的读写都会被阻塞
UNLOCK TABLES; – 释放锁
MyISAM 的自动加锁规则
• SELECT:默认不加锁(除非显式 LOCK TABLE … READ)。
• INSERT / UPDATE / DELETE:自动加表级写锁,阻塞其他所有操作。
• 并发影响:
• 一个会话在写时,其他会话的读和写都会被阻塞,直到写完成。
• 因此,MyISAM 不适合高并发写入场景(如电商订单、支付系统)。
MyISAM 的适用场景
由于 MyISAM 不支持事务 且 只有表级锁,它适用于:
- 读多写少的场景(如日志表、静态数据查询)。
- 不需要事务支持(如临时计算表)。
- 全文索引(FULLTEXT)(在 MySQL 5.6 之前,MyISAM 的全文索引比 InnoDB 更高效)。
但现代 MySQL(5.5+)默认使用 InnoDB,因为:
• InnoDB 支持 事务、行锁、MVCC、外键,更适合高并发 OLTP(在线事务处理)。
• MyISAM 在崩溃恢复、并发写入、数据安全方面较弱。
如何查看和切换存储引擎?
查看表的存储引擎
SHOW TABLE STATUS LIKE ‘users’; – 查看 users 表的引擎
修改表的存储引擎
ALTER TABLE users ENGINE = InnoDB; – 从 MyISAM 切换到 InnoDB
总结
对比项 | MyISAM | InnoDB |
---|---|---|
事务支持 | ❌ 不支持 | ✅ 支持(ACID) |
锁机制 | 表级锁(读锁、写锁) | 行级锁(记录锁、间隙锁、临键锁) |
并发性能 | 低(写阻塞所有操作) | 高(行锁支持并发) |
适用场景 | 读多写少、不需要事务 | 高并发 OLTP(如订单、支付) |