Files
rikako-note/mysql/mysql文档/mysql_mvcc_impl.md
2025-08-14 19:49:26 +08:00

151 lines
12 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

- [mysql mvcc](#mysql-mvcc)
- [Internal Impl](#internal-impl)
- [undo log](#undo-log)
- [insert undo log](#insert-undo-log)
- [update undo log](#update-undo-log)
- [commit regularly](#commit-regularly)
- [purge](#purge)
- [insert and delete rows in smallish batches](#insert-and-delete-rows-in-smallish-batches)
- [purge lag](#purge-lag)
- [innodb\_purge\_threads](#innodb_purge_threads)
- [innodb\_max\_purge\_lag](#innodb_max_purge_lag)
- [Multi-Versioning and Secondary Indexes](#multi-versioning-and-secondary-indexes)
- [mvcc mechanism](#mvcc-mechanism)
- [read view](#read-view)
- [read view for read committed isolation level](#read-view-for-read-committed-isolation-level)
- [read view for repeatable read isolation level](#read-view-for-repeatable-read-isolation-level)
- [read view impl](#read-view-impl)
- [innodb clustered index hidden columns](#innodb-clustered-index-hidden-columns)
- [implementation principle](#implementation-principle)
# mysql mvcc
innodb是一个`多版本`的存储引擎对于被修改的records其保存了records旧版本的信息从而支持事务的`并发``回滚`等特性。
innodb使用了rollback segment中存储的信息来进行`undo操作`,从而`支持事务在需要时进行回滚`;`并且支持通过undo信息构建record的早期版本从而实现一致性读`
## Internal Impl
在innodb的内部实现中其为数据库中存储的每行数据都额外添加了3个字段
- `DB_TRX_ID`长度为6字节用于标识`最后对改行数据进行insert/update操作的事务`
- 其中delete操作也被看做是`update`因为innodb在对数据执行删除操作时并不会立马对数据进行物理删除而是会先标记该行数据的delete mark数据实际被物理删除发生在purge阶段
- `DB_ROLL_PTR`长度为7字节被称为`roll pointer`。该指针指向undo record通过undo record中的内容可以构建该行数据被更新前的版本
- `DB_ROW_ID`长度为6字节该字段包含一个row id该row id在数据被插入时会单调的增长。
- 如果表使用的是innodb自动生成的聚簇索引未显式指定主键那么自动生成的聚簇索引将会包含该row id的值
- 如果表显式指定了聚簇索引,那么`DB_ROW_ID`并不会出现在任何索引中
## undo log
在rollback segment中undo log可以分为两种类型
- insert undo log
- update undo log
### insert undo log
insert undo log只会在事务回滚时被用到在事务提交之后insert undo log的内容可以被立马删除。
### update undo log
update undo log除了用于事务回滚外还用于一致性读MVCC。对于update undo log其只在满足如下条件时可以被删除
-`需要通过该update undo log来构建数据先前版本`的事务都不存在时update undo log才可以被删除
> innodb会为每个事务分配一个快照事务在进行一致性读时实际读取的是快照中的旧版本数据。
>
> 快照需要通过update undo log来还原行数据的旧版本。如果没有事务再需要通过该update undo log来还原旧数据代表该update undo log可以被删除。
### commit regularly
在编写和数据库交互的代码时,推荐周期性的提交事务,`包括只会进行一致性读的读事务`。如果事务A的运行时间较长且一直未提交那么在事务A运行时其他事务对数据库的更新操作其生成的update undo log在事务A运行期间都无法被丢弃因为有可能A会读取被修改数据之前的版本此时需要通过undo update log来进行旧版本数据的还原
故而如果存在一直不提交的事务那么可能会造成update undo log无法丢弃那么rollback segment占用的空间大小会不断增加填满其所位于的undo tablespace。
### purge
在innodb的mvcc方案中在通过delete sql删除行数据后行数据并不会立马就从该数据库中被移除不会立马被物理删除
innodb直到`删除update undo log record时``删除undo log record关联的行数据/index records`。该删除操作被称为`purge`
purge的操作很快并且purge的顺序通常和执行delete sql的时间顺序相同。
### insert and delete rows in smallish batches
如果针对表同时执行insert和delete的小批量操作且insert和delete的速率相同那么purge现成的回收速率可能会小于数据的插入速率。这样会导致`dead rows`标记为逻辑删除但未实际purge的数据行堆积表占用空间越来越大并造成disk相关操作变慢。
在上述场景下应当限制新操作并且通过调整innodb_max_purge_lag向purge thread分配更多资源。
#### purge lag
##### innodb_purge_threads
purge操作在后台由一个或多个purge threads执行。purge threads的数量由`innodb_purge_threads`来进行控制,默认值的取值逻辑如下:
- 如果可获取的逻辑核数小于等于16则默认值为1
- 如果logic processors大于16那么默认值为4
当dml操作集中在一张表上那么该表的purge操作由一个现成来执行这样可能会造成purge操作变慢增加purge lag。
##### innodb_max_purge_lag
如果purge lag超过`innodb_max_purge_lag`purge工作会自动在多个purge threads之间进行重新分配。
当purge threads设置过大时可能会造成与user threads的争用故而相当适当的管理purge threads大小。
`innodb_max_purge_lag`的默认值为0代表默认不存在max purge lag。
## Multi-Versioning and Secondary Indexes
在mvcc中对待聚簇索引和辅助索引的方式不同。在聚簇索引中record中的列是`update in-place`并且其hidden system columns指向undo log records通过undo log records可以还原数据的先前版本。
但是辅助索引不包含hidden system columns并且对于辅助索引的更新也不是update in-place。
当辅助索引的column被更新时旧的辅助索引记录将会被标记为delete marked并且插入新的辅助索引记录。`delete marked index records`最终会被purge。
当辅助索引中的index record被`delete marked``辅助索引页被newer transaction(更新的事务)更新时innodb将会在聚簇索引中查询记录`。在聚簇索引中该record的DB_TRX_ID将会被检查查看并且会通过undo log构建出当前线程可见的数据版本。
> 行数据的版本记录存储在聚簇索引中。如果innodb在查找记录索引时发现该辅助index record被delete marked此时并不确定delete marked的操作是否对查询事务可见需要在聚簇索引中查找记录并构建历史版本。
>
> 当辅助索引页被newer transaction更新时也无法确定该辅助索引页中的内容是否对当前事务可见同样需要查询聚簇索引来构建先前的历史版本。
> 如果当前事务初始化后record才被其他事务修改那么根据事务的一致性读原则record的修改不应对当前事务可见需要通过record对应的undo log内容还原到数据的旧版本。
> 如果`当前辅助索引中的数据被delete marked`或`当前辅助索引页被newer transaction所更新`,那么`covering index`技术将不会被使用。故而并不会直接从辅助索引中返回值innodb而是会再次从clustered index中查询记录。
## mvcc mechanism
mvcc机制主要用于处理多事务之间对数据的并发访问并且通过`版本快照`来实现事务之间的数据隔离。
事务A可见的数据版本为`事务A开启时的数据版本`即使在事务A执行的过程中事务B对数据进行了修改事务A后续读取时仍然读取的是修改之前的版本。
综上所属根据mvcc机制可以实现可重复读的隔离级别。
### read view
在mvcc中存在`read view`这一概念,其原理类似于如下描述:
- 在事务开启时,为所有数据创建一个快照,后续该事务在执行过程中都会从快照中读取数据,从而可以消除其他事务修改数据所造成的影响
#### read view for read committed isolation level
对于`read committed`隔离级别的事务,其等价于`generate a read view before each select statement is executed`
#### read view for repeatable read isolation level
对于`repeatable read`隔离级别的事务,其等价于`generate a read view before transaction executes the first select statement`并且在后续整个事务的执行过程中都使用该read view。
#### read view impl
read view实现并非是简单的为所有数据创建一个备份其原理如下。
read view在被创建时会记录如下信息
- `m_ids`当read view被创建时read view会记录当前数据库中所有活跃事务id的集合
- `min_trx_id` 该field代表当read view被创建时数据库中所有活跃事务id中的最小值相当于`m_ids`集合中的最小值
- `max_trx_id` 该值代表当`read view被创建时``数据库将要授予下一个新创建事务的事务id`,即`当前全局最大的事务id + 1`
- `creator_trx_id`代表创建该read view的事务id
#### innodb clustered index hidden columns
除了read view外innodb mvcc中还包含另外的部分`hidden_columns`
innodb的clustered index中会包含如下hidden columns
- `trx_id` 代表最后对该clustered index record进行修改的事务id
- `roll_pointer` 每当clustered index record被修改时指向`记录数据旧版本undo log`的指针会被写入到该column中。故而`roll_pointer`包含了一个单向链表,`包含clustered index record所有的旧版本`
#### implementation principle
通过上述记录的read view信息和clustered index hidden columns信息可以决定数据的历史版本对事务是否可见`每个历史版本中都包含trx_id信息用于代表该版本的最后修改事务id`
- `trx_id = creator_trx_id`: 如果`数据最后被修改的事务id``创建read view的事务id`相同,那么该行数据版本对当前事务可见
- `trx_id < min_trx_id`:如果`行数据的最后被修改事务id`小于`read view中最小的活跃事务id`代表read view创建时trx_id对应的事务已经被提交此时该行数据版本对当前事务可见
- `trx_id >= max_trx_id`:如果`行数据最后被修改的事务id`大于或等于`read view被创建时的全局最大事务id + 1`read view创建时`该数据版本对应的修改事务id还不存在`即read view创建时该修改还未发生故而该行数据版本对当前事务不可见
- `min_trx_id <= trx_id < max_trx_id`:
- `trx_id位于m_ids中`如果trx位于m_ids中代表read view在创建时该数据版本对应的事务还未提交即该数据版本对事务不可见
- `trx_id未位于m_ids中`: 代表在read view创建时该数据版本对应的事务已经提交故而该行数据版本可见
> read view通过记录创建时的`活跃事务id列表`和`全局最大事务id + 1`,可以明确的区分`数据版本的最后修改事务id造成的修改`是否对readview可见其判断逻辑如下
> - 对于id大于等于`全局最大事务id + 1`的事务在read view创建时都没有被创建故而未来事务造成的修改对read view不可见
> - 对于id小于`最小活跃事务id`的事务代表在read view创建时已提交read view创建之前提交的事务对read view是可见的
> - 对于id位于`[min_trx_id, max_trx_id)`之间的事务,其是否可见取决于其是否位于`活跃事务id列表`中来进行判断
> - 如果位于`活跃事务id列表`中代表read view创建时该历史版本对应修改还未提交故而对read view不可见
> - 如果不位于`活跃事务id列表`中代表read view创建时该历史版本对应修改已经提交已提交事务的修改对read view可见