MySQL中间件(三):mysql事务原理


一、mysql事务原理分析

1. 事务是什么?

技术背景

在数据库中,多个连接并发同时访问,容易引发数据混乱,需要事务来保证操作的完整性和正确性。

事务是指用户定义的一系列操作,这些操作要么都做,要么都不做,是一个不可分割的单位


事务语句

 - 显示开启事务
begin / start transaction 
- 提交事务,并使得已对数据库做的所有修改持久化
commit 
- 回滚事务,回滚到事务刚开始的状态
rollback 

- 创建一个保存点,一个事务可以有多个保存点
savepoint identifier
- 删除一个保存点
release savepoint identifier
- 事务回滚到保存点
ROLLBACK TO [SAVEPOINT] identifier

ACID特性(分析事物)

特性含义实现机制
原子性(Atomicity)事务中所有操作要么全部完成,要么全部不完成,不可分割通过 Undo log 实现回滚能力
一致性(Consistency)事务执行前后,数据库的完整性约束没有被破坏,数据保持一致依赖约束,外键等规则强制实现
隔离性(Isolation)多个事务并发执行时,事物之间不互相影响通过 MVCC 和锁机制实现 (4种隔离级别)
持久性(Durability)一旦事务提交,其对数据库的修改应该永久保存,即使系统崩溃防止异常数据丢失基于 Redo log 实现崩溃恢复

undo log 与 redo log

Undo Log(回滚日志)
作用:用来记录数据修改前的状态,用于事务回滚或 MVCC 的快照读
特点:InnoDB 引擎特有,保证事务原子性
Redo Log(重做日志)
作用:记录事务对数据页的修改,确保事务持久化
特点:顺序写入,高效持久化,崩溃回复时用于重建数据

*undo log 会随着事务提交后被清理掉,并不是永久保存


2. 事务隔离级别

技术背景 (并发异常导致引入事务)

多事务并发执行时,若没有适当的隔离机制,可能会出现以下问题:

  • 脏读: 一个事务读到另一个未提交事务修改的数据

  • 不可重复读:同一事务多次读取同一数据返回不同结果

  • 幻读:同一事务中两次查询同一个范围内记录的结果不一样; (当前读和快照读的不一致)

    eg. 不可重复读
    请添加图片描述
    eg. 幻读
    请添加图片描述


四种隔离级别

隔离级别(并发性向下越低)‘是否可能’ 有脏读不可重复读幻读
read uncommitted 读未提交
read committed 读已提交×
repeatable read 可重复读 [mysql默认]××
serializable 可串行化×××

隔离级别操作命令

1.read uncommitted (读未提交,三种异常都会发生)
2.read committed (读已提交,无脏读,读取最新版本的行数据)
3.repeatable read (可重复读,无脏读、不可重复读,读取事务开始前版本的行数据 
					innodb默认隔离级别,通过 读操作加锁也可解决幻读问题)
4.serializable (可串行化,无异常但并发能力低,通常不采用)
----------------------------------------

- 查看当前会话隔离级别
SELECT @@tx_isolation;
- 设置当前会话隔离级别
SET SESSION TRANSACTION ISOLATION LEVEL READ COMMITTED;
- 设置全局隔离级别(需重启或新连接生效)
SET GLOBAL TRANSACTION ISOLATION LEVEL REPEATABLE READ;

3. *MVCC 多版本并发控制

技术背景(引入mvcc原因)

1.MVCC 是 InnoDB 实现高并发的关键技术,通过保存数据的多个版本,使得读写操作可以并发执行而互不阻塞,保证读一致性(不加锁也能读到合理数据)

2.用来实现一致性的非锁定读;非锁定读是指不需要等待访问的行上X锁的释放

3.原理:通过undo log对每条记录保存多个版本,不同事务读取时根据自己的Read View(事务执行期间的快照)判断可见性。

*Undo Log 会随着事务提交后被清除(并不是永久保存)


Read View

属性作用
m_ids当前活跃的事务ID集合(还未提交的事务)
min_trx_id当前活跃事务中最小的事务ID
max_trx_id下一个即将分配的事务ID(并不一定是最大的事务ID)
creat_trx_id每一行数据的创建事务ID(trx_id字段)

事务可见性判断

事务可以看见事务本身的修改,事物间的修改不可见
请添加图片描述

  1. trx_id < min_trx_id (已提交 可见)
    说明该记录在创建read view 之前已经提交,所以对当前事务可见;
  2. trx_id >= max_trx_id (后启动,不可见)
    说明该记录是在创建read view 之后启动事务生成的,所以对当前事务不可见
  3. min_trx_id <= trx_id < max_trx_id(需要判断是否在m_ids集合)
    a. 在列表中;生成该版本记录的事务仍处于活跃状态,该版本记录对当前事务不可见;
    b. 不在列表中;生成该版本记录的事务已经提交,该版本记录对当前事务可见;

不同隔离级别下MVCC行为

在 read committed 和 read repeatable 隔离级别下,MVCC 采用 read view 来实现的,它们的区别在于创建 read view 时机不同.

  • read committed(读已提交):每次读取数据时生成新的read view,读到别人刚提交的数据(变化的快照)
  • repeatable read(可重复读):启动事务时,生成新的read view,一直使用到事务提交或事务回滚为止,保证事务内读到的数据不变

快照读 vs 当前读的区别?

  • 快照读(Snapshot Read):简单的sql select语句,读取记录的可见版本,非锁定读,走MVCC快照;
    当前读(Current Read):DML操作以及 sql语句加读写锁操作 ,读取当前记录的最新版本,加锁读,拿最新版本;
  • 举例:share mode共享读锁、updata写锁排他锁
    请添加图片描述

4. mysql锁机制

mysql支持多种 锁的粒度

  • 表级锁:对整张表加锁,开销小但并发度低
  • 页级锁:对数据页加锁(主要用于 MyISAM 索引缓存,InnoDB 不使用)
  • 行级锁:对记录加锁,开销大但并发度高(InnoDB 支持)

常见锁类型

全局锁: 会锁定整个数据库,其他事务无法进行读写操作

FLUSH TABLES WITH READ LOCK; - 全局只读锁(用于一致性备份)

LOCK TABLES table_name WRITE; - 全局写锁(用于表结构变更)

表级锁 :MySAM采用
意向共享锁(IS):事务打算对表中某些记录加共享锁
意向排他锁(IX):事务打算对表中某些记录加排他锁

意向锁不会阻塞其他意向锁,但会与表级的共享或排他锁发生冲突

行级锁 :innodb使用MVCC + 行锁结合实现高并发控制
共享锁(S Lock):允许其他事务读取同一行,但阻止写入
排他锁(X Lock):阻止其他事务获取任何类型的锁


锁的算法

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)
    记录锁与间隙锁的组合,锁定记录及其前面的间隙,是InnoDB 默认的行锁算法
    目的:在可重复读(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. 相反加锁顺序:两个事务以不同顺序获取相同资源,互相等待对方释放;通过调整加锁顺序解决

【eg】 事务A 锁定资源X 并请求资源Y,同时事务B 锁定资源Y 并请求资源X
	  
- 事务A
LOCK row 1;
LOCK row 2;

- 事务B
LOCK row 2;
LOCK row 1;  - 等待A释放,造成死锁

2. 锁冲突导致死锁:一个事务未能及时释放锁,另一个事务等待同一资源,导致互相阻塞。常见于高并发写操作 + 高隔离级别

解决建议: 降低隔离级别至Read Committed,避免不必要的间隙锁。
减小锁粒度,避免大范围更新或范围查询

案例理解: mysql连接池的应用场景,只封装写事务


如何避免死锁

方法说明
统一加锁顺序用一致的顺序加锁,避免环形等待
控制事务粒度与范围缩小每个事务锁定的数据范围和执行时间
减少范围查询减少 BRTWEEN/</> 查询,避免间隙锁干扰
合理设置索引避免全表扫描导致不必要的行锁或表锁
设置死锁重试机制捕获错误码 1213,程序自动重试一次事务操作
适当降低隔离级别如使用 Read Committed 替代 Repeatable Read(锁冲突)

优秀笔记:
1. MYSQL事务原理分析
2. MYSQL事务原理分析(三)
参考学习:https://github.com/0voice

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值