Skip to content

Commit

Permalink
update +_posts/Tech/Mysql/2021-01-06-mysql原理3-事务.md
Browse files Browse the repository at this point in the history
  • Loading branch information
mafulong committed Jan 13, 2025
1 parent e5ae9cd commit bb86382
Showing 1 changed file with 100 additions and 66 deletions.
166 changes: 100 additions & 66 deletions _posts/Tech/Mysql/2021-01-06-mysql原理3-事务.md
Original file line number Diff line number Diff line change
Expand Up @@ -18,66 +18,13 @@ log buffer -> page cache -> 磁盘。





## 总结

事务提交流程:

- 写undo日志到log buffer
- 准备阶段,sql成功执行,生成xid、redolog、undolog,调用prepare,将事务标记为trx_started,**将redo log写盘**,这里由innodb_flush_log_at_trx_commited参数控制。
- 提交阶段,引擎执行prepare成功后,开始写binlog到file cache
- 调用fsync,将binlog从file cache写到磁盘,这是由**sync_binlog**参数控制的
- 通知引擎commit,引擎提交后会清除undolog,并再次将redo log写盘,标记事务为trx_not_started,由后台线程持久化

**注意undo log不需要2pc。**



redo log和binlog两者是2PC控制的,先redo log prepare,再更新binlog,然后redo log commit并进行持久化。



总结一下ACID特性及其实现原理:

- 原子性:语句要么全执行,要么全不执行,是事务最核心的特性,事务本身就是以原子性来定义的;实现主要基于undo log
- 持久性:保证事务提交后不会因为宕机等原因导致数据丢失;实现主要基于redo log
- 隔离性:保证事务执行尽可能不受其他事务影响;InnoDB默认的隔离级别是RR,RR的实现主要基于锁机制(包含next-key lock)、MVCC(包括数据的隐藏列、基于undo log的版本链、ReadView)
- 一致性:事务追求的最终目标,一致性的实现既需要数据库层面的保障,也需要应用层面的保障



必背术语:

- 加锁读也叫当前读
- 非加锁读也叫快照读



补充:

- 关于mysql RR到底可不可以解决幻读?

> [参考](https://www.zhihu.com/question/372905832)
实际上,在RR下,select不会有phantom,读的是快照;可对于select for update(locking read)这种语句,如果前一次是select读快照(non-locking read),在RR下是可能会有幻像的,这因为select for update被认为是写。



RR级别下怎么解决幻读这个问题也得拆成快照读之间,当前读之间,快照读和当前读混合三部分。RR没有解决快照读/当前读混合下的幻读。

1. RR隔离级别,如果事务中都是快照读,或者全都是当前读,都不会产生幻读。只有当前读和快照读混用,才会产生幻读。
2. MVCC保证快照读不会幻读
3. next-key lock保证当前读不会产生幻读









## 并发问题

> 1. **脏读** :脏读就是指当一个事务正在访问数据,并且对数据进行了修改,而这种修改还没有提交到数据库中,这时,另外一个事务也访问这个数据,然后使用了这个数据。
Expand Down Expand Up @@ -230,33 +177,85 @@ Buffer Pool的使用大大提高了读写数据的效率,但是也带了新的



## 事务提交
## 事务执行过程

在数据库的世界里,数据从来都不重要,日志才是最重要的,有了日志就有了一切。

### 事务提交流程
### 事务执行流程

- 写undo日志到log buffer
- 准备阶段,sql成功执行,生成xid、redolog、undolog,调用prepare,将事务标记为trx_started,**将redo log写盘**,这里由innodb_flush_log_at_trx_commited参数控制。
- 提交阶段,引擎执行prepare成功后,开始写binlog到file cache
- 调用fsync,将binlog从file cache写到磁盘,这是由**sync_binlog**参数控制的
- 通知引擎commit,引擎提交后会清除undolog,并再次将redo log写盘,标记事务为trx_not_started,由后台线程持久化

**注意undo log不需要2pc。**

1. 开始事务。
1. 当用户执行 `BEGIN``START TRANSACTION` 命令后,事务启动。
2. InnoDB 会分配一个唯一的事务 ID (`transaction ID`),用于标识当前事务。
2. 准备阶段。执行 SQL,修改数据并生成 redo log 和 undo log。用户执行一系列 `INSERT``UPDATE``DELETE``SELECT` 操作。这些操作会被记录到 **redo log (重做日志)****undo log (回滚日志)** 中,以支持崩溃恢复和事务回滚。事务的修改内容存储在 **buffer pool (内存中的数据页)** 中,尚未写入磁盘。
- 先记录 undo log 到log buffer。 记录逻辑日志,用于支持回滚操作,确保事务的原子性.
- **修改 Buffer Pool:**
- 数据修改会先应用到内存中的数据页(Buffer Pool)。是个缓存。
- 被修改的数据页会被标记为 "脏页"(Dirty Page),等待后续异步刷盘操作。
- **生成redo log:**
- **redo log**:记录物理日志,用于崩溃恢复,确保即使系统宕机也可以通过重做操作恢复数据一致性。
- **事务未提交:**
- SQL 执行过程中,事务仍处于活跃状态,未进行提交操作。
- 数据的修改仅在事务内部可见,其他事务无法看到。
3. 事务提交
1. Prepare 阶段(两阶段提交的第一阶段):写入事务的修改记录和 prepare 标记。事务在提交时,先进入 Prepare 阶段,确保所有的日志记录都写入了持久存储。
- **写入 redo log:**
- 将当前事务的所有 redo log 记录从 **redo log buffer** 刷入磁盘(或根据参数配置写入)。
- 写入日志时,标记事务为“准备提交”状态(Prepare 状态)。
- 更新 binlog。
- 调用fsync,将binlog从file cache写到磁盘,这是由**sync_binlog**参数控制的
2. Commit 阶段:写入事务的 commit 标记,完成提交。、
- 在 redo log 中写入commit记录。引擎提交后会清除undolog,并再次将redo log刷入磁盘(或根据参数配置写入)。
- 此时,该状态并不需要持久化到磁盘,只需要 `write()` 到操作系统的 Page Cache 中即可。因为,只要 binlog 写磁盘成功,就算 redo log 的状态还是 prepare 也没有关系,一样会被认为事务已经执行成功。在 MySQL 重启后,会按顺序扫描 redo log 文件,找到**处于 prepare 状态的 redo log 写入事务**,就使用 redo log 中的 XID 去 binlog 查看是否存在此 XID,**binlog没有就回滚,有就提交。**



<img src="https://cdn.jsdelivr.net/gh/mafulong/mdPic@vv8/v8/202501132356473.png" alt="img" style="zoom: 67%;" />



undo log -> buffer pool 修改 -> redo log 刷盘 prepare -> binlog -> redo log 更新为提交。

**注意undo log不需要2pc。**

### 2PC

redo log和binlog两者是2PC控制的,先redo log prepare,再更新binlog,然后redo log commit并进行持久化。

- 因为 **redo log 影响主库的数据****binlog 影响从库的数据**,所以, **redo log 和 binlog 必须保持一致才能保证主从数据一致**
- 事务的提交过程有两个阶段,就是将 **redo log 的写入**拆成了两个步骤:**prepare****commit**,中间再穿插写入 binlog 的步骤。
- **事务没提交的时候,redo log 也是可能被持久化到磁盘的**。redo log有参数可以每秒异步刷盘。


### redolog的写入机制

- 为了控制 redo log 的写入策略,InnoDB 提供了 innodb_flush_log_at_trx_commit 参数,它有三种可能取值
### redo log的写入机制

- 为了控制 redo log 的写入策略,InnoDB 提供了 innodb_flush_log_at_trx_commit 参数,它有三种可能取值,默认为1。
- 设置为 0 的时候,表示每次事务提交时都只是把 redo log 留在 redo log buffer 中 ;
- 设置为 1 的时候,表示每次事务提交时都将 redo log 直接持久化到磁盘;
- 默认的情况. 设置为 1 的时候,表示每次事务提交时都将 redo log 直接持久化到磁盘;
- 设置为 2 的时候,表示每次事务提交时都只是把 redo log 写到 page cache
- InnoDB 有一个后台线程,每隔 1 秒,就会把 redo log buffer 中的日志,调用 write 写到文件系统的 page cache,然后调用 fsync 持久化到磁盘
- 如果是0或者2,则InnoDB 有一个后台线程,每隔 1 秒,就会把 redo log buffer 中的日志,调用 write 写到文件系统的 page cache,然后调用 fsync 持久化到磁盘



### 崩溃恢复机制

如果系统崩溃或掉电,可能出现以下情况:

- Buffer Pool 中的数据页被修改,但还没来得及把 redo log 刷到磁盘。
- 这种情况是崩溃恢复需要处理的问题。

**崩溃恢复机制**

- 当数据库重启时,InnoDB 会通过 **redo log****undo log** 恢复一致性。

- 两种可能性

- redo log 缺失:因为 redo log 未持久化,则对应事务被视为未提交,数据不会被刷入磁盘。
- redo log 已持久化:即使 Buffer Pool 数据丢失,InnoDB 也能通过 redo log 重做操作,恢复数据。



### 事务由存储引擎实现

Expand Down Expand Up @@ -432,7 +431,13 @@ RC每次执行select前都会重新建立一个新的ReadView,因此如果事
另一种是加锁读,查询语句有所不同,如下所示:

```
#共享锁读取``select``...lock ``in` `share mode``#排它锁读取``select``...``for` `update
#共享锁读取
``select``...lock ``in` `share mode``
#排它锁读取
``select``...``for` `update
```

加锁读在查询时会对查询的数据加锁(共享锁或排它锁)。由于锁的特性,当某事务对数据进行加锁读后,其他事务无法对数据进行写操作,因此可以避免脏读和不可重复读。而避免幻读,则需要通过next-key lock。**next-key lock**是行锁的一种,实现相当于**record lock(**记录锁**) + gap lock(**间隙锁**)**;其特点是不仅会锁住记录本身**(record lock**的功能**)**,还会锁定一个范围**(gap lock**的功能**)**。因此,加锁读同样可以避免脏读、不可重复读和幻读,保证隔离性。
Expand Down Expand Up @@ -467,3 +472,32 @@ RC每次执行select前都会重新建立一个新的ReadView,因此如果事
- 数据库本身提供保障,例如不允许向整形列插入字符串值、字符串长度不能超过列的限制等
- 应用层面进行保障,例如如果转账操作只扣除转账者的余额,而没有增加接收者的余额,无论数据库实现的多么完美,也无法保证状态的一致

## QA



总结一下ACID特性及其实现原理:

- 原子性:语句要么全执行,要么全不执行,是事务最核心的特性,事务本身就是以原子性来定义的;实现主要基于undo log
- 持久性:保证事务提交后不会因为宕机等原因导致数据丢失;实现主要基于redo log
- 隔离性:保证事务执行尽可能不受其他事务影响;InnoDB默认的隔离级别是RR,RR的实现主要基于锁机制(包含next-key lock)、MVCC(包括数据的隐藏列、基于undo log的版本链、ReadView)
- 一致性:事务追求的最终目标,不同事务访问结果一样。一致性的实现既需要数据库层面的保障,也需要应用层面的保障



补充:

- 关于mysql RR到底可不可以解决幻读?

> [参考](https://www.zhihu.com/question/372905832)
实际上,在RR下,select不会有phantom,读的是快照;可对于select for update(locking read)这种语句,如果前一次是select读快照(non-locking read),在RR下是可能会有幻像的,这因为select for update被认为是写。



RR级别下怎么解决幻读这个问题也得拆成快照读之间,当前读之间,快照读和当前读混合三部分。RR没有解决快照读/当前读混合下的幻读。

1. RR隔离级别,如果事务中都是快照读,或者全都是当前读,都不会产生幻读。只有当前读和快照读混用,才会产生幻读。
2. MVCC保证快照读不会幻读
3. next-key lock保证当前读不会产生幻读

0 comments on commit bb86382

Please sign in to comment.