目录
一、开启事务
二、sql解析、查询计划生成
三、查询要修改的数据
1、读buffer pool的过程
buffer pool的结构组成
1)Free List (空闲链表)
2)LRU List (最近最少使用链表)
3)Flush List (刷新链表)
三条链表之间的关系
2、怎么样在buffer pool中找到我们想要的页?
3、解析页的过程
以数据页为例,页的结构
一、开启事务
默认情况下autocommit=1,如果我们没有显式开启事务,而是直接执行update,那么MySQL就会隐式地为我们开启一个事务,并在这条update执行结束后自动提交。
如果set autocommit=0,或者先执行了begin;/start transaction;显式开启事务,那么update执行完成后不会自动提交,需要我们手动发起commit;或者rollback;。
不管用哪种方式开启事务后,MySQL都会在事务内第一次执行增删改操作时,给事务分配一个事务号。假定我们这条update所在的事务事务号为T1024。
二、sql解析、查询计划生成
在Mysql8.0之前,有查询缓存模块。
MySQL需要监测查询缓存涉及的每张表,只要这张表发生了任何变化,都要使其缓存失效。
查询缓存生效的条件是查询语句完全一致,如果新的语句多了一个空格,或者大小写发生变化,查询缓存都不能生效。
查询缓存在MySQL 5.7版本已经默认禁用了,在MySQL8.0版本更是直接被删除。
三、查询要修改的数据
在执行UPDATE
之前,InnoDB需要先定位需要修改的记录。这通常涉及到对索引的查找。
对磁盘和bufferpool的读取都是以页为单位:表空间号+页号。
传入页号这个参数到buffer pool,然后buffer pool判断这个页是否在buffer pool中。如果该记录的页已经在buffer pool
中,那么InnoDB直接从buffer pool
中读取,直接返回我们想要的页。如果不在,InnoDB会从磁盘上加载相应的页到buffer pool
中,再返回。
1、读buffer pool的过程
我们还不清楚buffer pool内部是怎么获取、保存、管理页的。
buffer pool的结构组成
在 MySQL InnoDB 存储引擎中,Buffer Pool
是管理数据库页(page)的核心组件,负责在内存中缓存数据页以加速数据库操作。为了高效地管理和调度这些页,InnoDB 使用了三条主要的链表:Free List
、LRU List
和 Flush List
。这些链表各自负责不同的管理任务,并相互协作来确保内存使用的高效性和数据的一致性。
1)Free List (空闲链表)
描述:空闲链,主要负责管理未被使用的缓冲池空间。
Free List
维护的是已经从Buffer Pool
中释放出来的、当前不再被使用的内存页。它充当了内存池的角色,当新的页需要加载到Buffer Pool
中时,如果没有空闲空间,InnoDB 会从Free List
中获取空闲的页来加载新数据。作用:为即将载入
Buffer Pool
的新页提供空闲空间。与 LRU List 的关系:当页被淘汰出
LRU List
后,如果不再需要使用,该页会被加入到Free List
中。2)LRU List (最近最少使用链表)
描述:最近最少使用链,主要负责在缓冲池满时淘汰缓冲页。
LRU List
(Least Recently Used) 是Buffer Pool
的核心链表,负责管理已经载入内存但尚未被淘汰的页。页的访问顺序决定了它们在LRU List
中的位置。最近被访问的页会移动到LRU List
的热端(热链表),而较少访问的页会逐渐滑向冷端(冷链表)。作用:用于决定哪些页可以保留在
Buffer Pool
中,以提高缓存的命中率。最近访问频繁的页保留在热端,而不常访问的页可能会被淘汰,腾出空间给新页。
buffer pool
采用LRU(Least Recently Used)算法来管理页。如果buffer pool
已满,则会根据LRU策略淘汰最久未使用的页,以腾出空间来加载新的页。与 Free List 的关系:当页在
LRU List
中到达冷端并且不再使用时,可能会被从LRU List
中移除并放入Free List
。3)Flush List (刷新链表)
描述:脏链,主要负责管理要被刷新到磁盘的页。
Flush List
维护了所有脏页的列表,这些页已经在内存中被修改,但还没有将修改后的数据写回磁盘。当需要释放Buffer Pool
的内存或在适当的时间点时,脏页会从Flush List
中被取出,并被刷写到磁盘以保证数据的一致性。作用:负责将脏页刷新到磁盘,以确保事务提交时的数据一致性和持久性。
与 LRU List 的关系:当一个页在
Buffer Pool
中被修改后,它会同时存在于LRU List
和Flush List
中。脏页仍然会按照 LRU 规则进行管理,但它们同时在Flush List
中等待被刷新到磁盘。
三条链表之间的关系
1)加载新页:当从磁盘加载一个新页到 Buffer Pool
时,InnoDB 会优先查看 Free List
中是否有可用的空闲页。如果有,则从 Free List
中分配;如果没有,则从 LRU List
的冷端淘汰较少使用的页,并将其放入 Free List
。
2)访问页:当某个页在 Buffer Pool
中被访问时,该页会被移动到 LRU List
的热端,以防止它被淘汰。
3)页的修改和刷新:
- 当页被修改时,它会被标记为脏页并加入到
Flush List
中。此时它仍然存在于LRU List
中,按照访问频率进行排序。 - 后台线程会周期性地扫描
Flush List
,将脏页刷写回磁盘,以减少Buffer Pool
中的脏页数量。 -
淘汰页:当
LRU List
中的页达到冷端并且未被访问时,它可能会被淘汰出Buffer Pool
。如果它是脏页,则需要先将其刷新到磁盘,然后再移出LRU List
并加入Free List
。
这三条链表通过协同工作,高效地管理 Buffer Pool
中的页,使得 MySQL 能够在高并发环境下保持性能和数据的一致性。
2、怎么样在buffer pool中找到我们想要的页?
(1)通过 B+ 树索引定位页
InnoDB 使用 B+ 树索引结构来存储数据表中的数据,这种索引结构使得查找某一特定页的过程高效且快速。定位页的过程如下:
- 索引查找:如果一个 SQL 语句通过索引来访问数据(如通过
WHERE
子句指定的条件),则 InnoDB 会先查找索引 B+ 树中的叶节点,以定位数据页或记录所在的具体位置。 - 叶节点和页映射:B+ 树的叶节点包含了索引列的值和指向包含实际数据行的页的指针。通过这些指针,InnoDB 可以直接定位到存储目标数据的页。
(2) 检查页是否在 Buffer Pool
中
一旦 InnoDB 知道了需要访问的页的地址(page_id
),它会在 buffer pool
中查找该页是否已经被加载。
buffer pool
内部使用哈希表来加速页的查找。每个数据页在 buffer pool
中都有一个对应的哈希表项。
- 哈希表的键通常是由
page_id
构成,它唯一标识了页在buffer pool
中的位置。 - 哈希表项包含信息:每个哈希表项都包含页的元数据,如页所在的内存地址、页的状态(如脏页、已锁定等)、页的引用计数等。
因此,InnoDB 通过计算哈希值,快速在哈希表中找到对应的页控制块(Page Control Block
)。
(3)页的状态检查
如果页在 buffer pool
中找到了,InnoDB 需要进一步检查页的状态,以确保页可以被当前事务访问:
- 引用计数(
ref_count
):表示页被引用的次数。如果页的引用计数大于零,说明页当前正被其他事务或查询使用。 - 锁状态:InnoDB 需要检查页是否被其他事务锁定,以及锁的类型(如共享锁、排他锁等)。根据当前事务的需要,决定是等待锁释放还是直接访问页。
(4)从磁盘加载页到 Buffer Pool
如果在 buffer pool
中未找到所需的页,或者页的状态不满足当前事务的需要,InnoDB 会从磁盘加载页到 buffer pool
中:
- 从
Free List
获取空闲页:如果buffer pool
中有可用的空闲页,InnoDB 会从Free List
中获取一个空闲页,准备加载新数据。 - 从磁盘读取页:InnoDB 根据
page_id
,从表空间文件中读取页的数据到buffer pool
的空闲页中。 - 更新哈希表:新加载的页会被添加到哈希表中,并且更新其页控制块的信息。
(5)将页插入到 LRU List
中
新加载的页或被访问的页会被插入到 LRU List
的热端,表示这是最近使用的页,应该保留在内存中以加速未来的访问。
如果 buffer pool
已满,LRU List
的冷端的页可能会被淘汰。这些页要么被移入 Free List
,要么被写入磁盘(如果是脏页)。
(6)返回页给事务
一旦页被成功地定位并加载到 buffer pool
中,InnoDB 就可以将页返回给当前事务,供其进行读或写操作。
(7)维护页的一致性
在页被使用过程中,InnoDB 会通过一系列机制来确保数据的一致性和完整性:
- 锁定和并发控制:InnoDB 通过锁机制(如行锁、间隙锁等)来控制并发事务对页的访问,避免数据不一致的情况。
- 脏页管理:修改过的页会被标记为脏页,并加入到
Flush List
中,等待后台线程将其写回磁盘。 - 日志记录:InnoDB 还会将对页的修改记录到
redo log
和undo log
中,以支持崩溃恢复和事务回滚。
3、解析页的过程
怎样从一个16k的数据页中找到我们想要的数据?
以数据页为例,页的结构
实际上每条数据在物理上是乱序的,逻辑上用一个单向链表连接起来。链表以infimum和supremum这两个特殊记录标记首尾。infimum指向第一条记录,最后一条记录指向supremum。
每个数据页都有一个页目录,它包含指向页中若干记录的指针(通常是记录头部)。这些指针指向的记录是页中某些分段的起点。因此,页最后的pagedirectory结构(类似数组)可以帮我们加快对页的搜索。
由于记录是有序存储的,InnoDB 使用二分查找算法快速定位到目标记录在页中的位置。
查找一个数据页时,就会在Page Directory进行二分查找每次查找都要取到槽对应的行进行主键的比较;直到最终找到数据或确认没有对应的数据。
- InnoDB 先从页目录中找到接近目标记录的指针,再从这个指针开始顺序扫描记录,直到找到目标记录。这种方式比全表扫描更高效。
- 在页中,记录通过双向链表链接起来。每条记录有指向前后记录的指针(
next_record
和prev_record
),InnoDB 可以通过这个链表依次遍历页中的记录。 - 一旦定位到目标记录的起始位置,InnoDB 会沿着链表遍历,通过
next_record
,直到找到与查询条件匹配的记录。