4.2.3 MYSQL事务原理分析
文章目录
- 4.2.3 MYSQL事务原理分析
- 1. 事务
- 1. 前提:并发连接访问
- 2. 事务定义
- 3. 事务语句:
- 4. acid特性
- 5. undolog-回滚日志
- 6. redolog-重做日志
- 2. 隔离级别
- 1. 四种隔离级别
- 2. 并发问题解释
- 3. 隔离级别设置
- 3. MVCC(Multiversion Concurrency Control,多版本并发控制)(重点)
- 1. read view
- 2. 不同隔离级别下MVCC行为
- 3. 快照读 VS 当前读
- 4. redo/undo
- 4. 锁
- 1. 锁的粒度
- 2. 锁类型
- 1. 全局锁
- 2. 表级锁
- 3. 行级锁
- 3. 锁的算法
- 5. 死锁
- 1. 相反加锁顺序死锁
- 2. 锁冲突死锁
- 3. 查看死锁
- 3. 如何避免死锁
4.2.3 MYSQL事务原理分析
1. 事务
1. 前提:并发连接访问
在数据库中,多个连接同时访问(并发),容易引发数据混乱,需要事务来保证操作的完整性和正确性
2. 事务定义
是用户定义一系列操作,这些操作要不然都做,要不然都不做,不可分割,告诉mysql这是不可分割的一系列整体
3. 事务语句:
-- 显示开启事务
START TRANSACTION
-- 提交事务,并使得已对数据库做的所有修改持久化
COMMIT
-- 回滚事务,结束用户的事务,并撤销正在进行的所有未提交的修改
ROLLBACK
-- 创建一个保存点,一个事务可以有多个保存点
SAVEPOINT identifier
-- 删除一个保存点
RELEASE SAVEPOINT identifier
-- 事务回滚到保存点
ROLLBACK TO [SAVEPOINT] identifier
4. acid特性
特性 | 解释 | 备注 |
---|---|---|
A - Atomicity(原子性) | 事务中的操作要么全部成功,要么全部失败 | 通过 undo log 支持回滚 |
C - Consistency(一致性) | 事务执行前后,数据保持一致 | 依靠约束、外键等规则 |
I - Isolation(隔离性) | 多个事务之间互不干扰 | MVCC支持非锁定一致性读 |
D - Durability(持久性) | 提交后的数据永久保存,即使系统宕机 | 依靠 redo log |
5. undolog-回滚日志
- 用于回滚数据
- 当事务执行过程中出现错误或被用户取消,undo log 记录修改前的旧值,可以通过它恢复原状
- 存在于 InnoDB 引擎中
6. redolog-重做日志
- 用于恢复数据
- 事务提交前,会先把修改写入 redo log,防止崩溃丢失
- 提交时再持久化到磁盘数据页,确保事务的持久性(D)
- InnoDB 特有,保证即使宕机也能恢复到提交状态
2. 隔离级别
- 并发访问时,如果没有控制好,事务之间互相干扰,可能会产生各种脏读、不可重复读、幻读等问题。
- 隔离级别就是用来平衡:并发性能 vs. 数据正确性(隔离得越严格,性能越低,但数据越安全)
1. 四种隔离级别
隔离级别 | 描述 | 可能出现的问题 |
---|---|---|
READ UNCOMMITTED(读未提交) | 可以读到别的事务未提交的数据 | 脏读、不可重复读、幻读 |
READ COMMITTED(读已提交) | 只能读到别的事务已提交的数据 | 不可重复读、幻读 |
REPEATABLE READ(可重复读)(MySQL默认) | 同一事务中,多次读取同一数据结果一致 | 幻读(MySQL用Gap锁避免了) |
SERIALIZABLE(可串行化) | 强制事务串行执行,完全隔离 | 无任何并发问题,但性能最低 |
2. 并发问题解释
- 脏读:读到了未提交事务修改的数据。
- 不可重复读:同一事务中两次读到的数据不一样(因为别的事务提交了修改)。
- 幻读:同一事务中,查询时发现新增了数据行
3. 隔离级别设置
-- 查看当前隔离级别
SELECT @@tx_isolation;--临时修改当前会话隔离级别(只影响当前连接)
SET SESSION TRANSACTION ISOLATION LEVEL READ COMMITTED;--修改全局隔离级别(需要重启或新连接生效)
SET GLOBAL TRANSACTION ISOLATION LEVEL REPEATABLE READ;
3. MVCC(Multiversion Concurrency Control,多版本并发控制)(重点)
-
提高并发性能,保证读一致性(不加锁也能读到合理数据)。用来实现一致性的非锁定读;非锁定读是指不需要等待访问的行上X锁的释放
-
原理:对每条记录保存多个版本(通过undo log),不同事务读取时根据自己的Read View判断可见性
1. read view
项目 | 作用 |
---|---|
m_ids | 当前活跃的事务ID集合(还未提交的事务) |
min_trx_id | 当前活跃事务中最小的事务ID |
max_trx_id | 下一个即将分配的事务ID |
creat_trx_id | 每一行数据的创建事务ID(trx_id字段) |
- trx_id < min_trx_id ;说明该记录在创建 read_view 之前已经提交,所以对当前事务可见;
- trx_id >= max_trx_id ;说明该记录是在创建 read_view 之后启动事务生成的,所以对当前事务
不可见; - min_trx_id <= trx_id < max_trx_id ;此时需要判断是否在 m_ids 列表中;
- 在列表中;生成该版本记录的事务仍处于活跃状态,该版本记录对当前事务不可见;
- 不在列表中;生成该版本记录的事务已经提交,该版本记录对当前事务可见;
2. 不同隔离级别下MVCC行为
在 read committed 和 read repeatable 隔离级别下,MVCC 采用 read view 来实现的,它们的区别在于创建 read view 时机不同
隔离级别 | MVCC处理 | 说明 |
---|---|---|
READ COMMITTED(读已提交) | 每次查询都生成新的 Read View | 读到别人刚提交的数据(变化的快照) |
REPEATABLE READ(可重复读) | 整个事务期间共用同一个 Read View | 保证事务内读到的数据不变 |
3. 快照读 VS 当前读
类型 | 说明 | 举例 |
---|---|---|
快照读(Snapshot Read) | 非锁定读,走MVCC快照 | SELECT * FROM table WHERE id = 1; |
当前读(Current Read) | 加锁读,拿最新版本 | SELECT * FROM table WHERE id = 1 FOR UPDATE; |
4. redo/undo
日志 | 作用 | 何时使用 |
---|---|---|
Redo Log | 用来恢复已提交事务的修改(崩溃恢复) | crash后根据redo log恢复 |
Undo Log | 保存数据的旧版本,供回滚或MVCC快照读使用 | 回滚或快照读时用 |
undo log 会随着事务提交后被清理掉(并不是永久保存)
4. 锁
在 MySQL 中,锁是并发控制的核心机制,主要包括不同粒度的锁(行、页、表)和不同类型的锁(共享锁、排他锁、意向锁等)。
1. 锁的粒度
MySQL 中事务使用的是粒度锁(Granular Locking),主要体现在以下三个层次:
- 表级锁(Table-Level Lock):作用于整张表,开销小,加锁快,但并发性低。
- 页级锁(Page-Level Lock):作用于数据页(主要用于 MyISAM 的索引缓存,InnoDB 不使用)。
3。 行级锁(Row-Level Lock):作用于某一行记录,开销大,加锁慢,但并发性好,InnoDB 支持。
2. 锁类型
1. 全局锁
- 全局只读锁:使用 FLUSH TABLES WITH READ LOCK,可用于做一致性备份,防止其他事务对数据进行修改。
- 全局写锁:使用 LOCK TABLES,可用于做表结构变更。
注意:
- 全局锁会锁定整个数据库,其他事务无法进行读写操作。
2. 表级锁
- 意向共享锁(IS Lock):事务打算对表中某些记录加共享锁
- 意向排他锁(IX Lock):事务打算对表中某些记录加排他锁
意向锁不会阻塞其他意向锁,但会与表级的共享或排他锁发生冲突
3. 行级锁
- 共享锁(S Lock):又称读锁,允许其他事务也获得该行的共享锁(读读并发)
- 排他锁(X Lock):又称写锁,阻止其他事务获得该行的任何锁(独占写)
InnoDB 使用MVCC + 行锁结合实现高并发控制
3. 锁的算法
InnoDB 使用多种行锁算法来保证事务隔离性
- 记录锁(Record Lock)
定义:锁定索引上的某一行记录。
作用:防止其他事务对该记录进行修改或删除。
示例:
SELECT * FROM user WHERE id = 5 FOR UPDATE;
若 id 有索引,这条语句会对 id=5 的那一条记录加记录锁
- 间隙锁(Gap Lock)
定义:锁定某一范围之间的间隙,不锁定已有记录。
作用:防止其他事务在该范围内插入新记录,避免“幻读”。
特点:仅适用于 范围查询,例如 <, >, BETWEEN。
示例:
SELECT * FROM user WHERE id < 5 FOR UPDATE;
会锁住 (5, ∞) 范围的间隙,阻止插入 id > 5 的新记录。
- 临键锁(Next-Key Lock)
定义:记录锁 + 前一个间隙锁的组合。
作用:锁定某一条记录及其前面的间隙。
目的:在可重复读(RR)隔离级别下防止幻读。
示例:
SELECT * FROM user WHERE id BETWEEN 5 AND 10 FOR UPDATE;
会加多个临键锁,如:
(4, 5],锁住 id=5
(5, 6],锁住 id=6
…
(9, 10],锁住 id=10
5. 死锁
死锁是指两个或多个事务在执行过程中因争夺资源而互相等待,导致程序无法继续执行下去的现象。在 InnoDB 中,死锁一旦被检测到,会自动回滚其中一个事务来解除死锁
1. 相反加锁顺序死锁
两个事务以不同顺序获取相同资源,互相等待对方释放。
示例:
-- 事务A
LOCK row 1;
LOCK row 2;-- 事务B
LOCK row 2;
LOCK row 1; -- 等待A释放,造成死锁
2. 锁冲突死锁
一个事务未能及时释放锁,另一个事务等待同一资源,导致互相阻塞。常见于高并发写操作 + 高隔离级别(如可重复读)
解决建议:
- 降低隔离级别至 Read Committed,避免不必要的间隙锁。
- 减小锁粒度,避免大范围更新或范围查询
3. 查看死锁
SHOW ENGINE INNODB STATUS; -- 查看死锁信息SELECT * FROM information_schema.INNODB_LOCKS;-- 查看当前锁信息
SELECT * FROM information_schema.INNODB_LOCK_WAITS;
3. 如何避免死锁
方法 | 说明 |
---|---|
统一加锁顺序 | 所有事务访问资源时使用一致的顺序加锁,避免环形等待 |
控制事务粒度与范围 | 缩小每个事务锁定的数据范围和执行时间 |
减少范围查询 | 减少 BETWEEN / < / > 查询,避免间隙锁干扰 |
合理设置索引 | 避免全表扫描导致不必要的行锁或表锁 |
设置死锁重试机制(代码层) | 捕获错误码 1213 ,程序自动重试一次事务操作 |
适当降低隔离级别 | 如使用 Read Committed 替代 Repeatable Read |