MySQL 存储引擎

本文深入探讨了InnoDB存储引擎的数据组织结构,包括表空间、段、区、页的概念,详细解析了B+树索引的工作原理,以及InnoDB如何使用聚簇索引和二级索引组织数据。同时,文章介绍了InnoDB的BufferPool机制,包括其功能、结构、初始化过程、页定位、LRU算法、脏页刷新策略和Checkpoint机制。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

InnoDB 的数据组织结构
    • 在 InnoDB 存储引擎中,所有的数据都被逻辑地存放在表空间中,表空间(tablespace)是存储引擎中最高的存储逻辑单位,在表空间的下面又包括段(segment)、区(extent)、页(page)
    • 同一个数据库实例的所有表空间都有相同的页大小;默认情况下,表空间中的页大小都为 16KB,当然也可以通过改变 innodb_page_size 选项对默认大小进行修改,需要注意的是不同的页大小最终也会导致区大小的不同
    • MySQL 使用 InnoDB 存储表时,会将表的定义数据索引等信息分开存储,其中前者存储在 .frm 文件中,后者存储在 .ibd 文件中
  • 索引
    • InnoDB 存储引擎在绝大多数情况下使用 B+ 树建立索引,这是关系型数据库中查找最为常用和有效的索引,但是 B+ 树索引并不能找到一个给定键对应的具体值,它只能找到数据行对应的页,然后正如上一节所提到的,数据库把整个页读入到内存中,并在内存中查找具体的数据行。
    • B+ 树是平衡树,它查找任意节点所耗费的时间都是完全相同的,比较的次数就是 B+ 树的高度。
    • InnoDB 存储引擎中的表都是使用索引组织的,也就是按照键的顺序存放,聚簇索引就是按照表中主键的顺序构建一颗 B+ 树,并在叶节点中存放表中的行记录数据
    • 正常的表应该有且仅有一个聚集索引(绝大多数情况下都是主键),表中的所有行记录数据都是按照聚集索引的顺序存放的,当我们使用聚集索引对表中的数据进行检索时,可以直接获得聚集索引所对应的整条行记录数据所在的页,不需要进行第二次操作。
    • 所有的非聚簇索引都成为二级索引,二级索引也是通过 B+ 树实现的,但是它的叶节点并不包含行记录的全部数据,仅包含索引中的所有键和一个用于查找对应行记录的主键。
InnoDB 的 BufferPool
  • 功能,存储外存页面在内存中的镜像
    • 只读镜像(非脏页)
    • 更新镜像(脏页)
  • 结构
    • 头结构,Buffer Pool Header,不属于 buffer pool 内存,一个页约 800Bytes
    • 页数据,buffer pool 内存,系统启动时既完全分配,但是不写数据
    • 每一个内存buffer,在指定buffer header之后,永不更改
    • 指定buffer header,可以定位buffer;指定buffer,可以定位到其对应的buffer header
  • 初始化
    1. 根据innodb_buffer_pool_size与innodb_buffer_pool_instances参数,划分各buffer pool instance大小(需要去除change buffer大小)
    2. 根据buffer pool instance大小,计算额外需要分配的buffer headers空间
    3. 初始化各buffer pool instance,前部分集中分配buffer headers,后部分为free buffers;并为每个free buffer指定一个buffer header
    4. 初始化buffer headers,包括创建Latch,Mutex;初始化Pin (0)等
    5. 创建Buffer Pool的Page Hash 哈希表
    6. 将所有的free buffers链入buffer pool instance的free list链表,等待分配
  • page 定位
    • 给定一个外存页面,如何快速判断此页面是否在Buffer Pool中
    • 均使用Hash表进行快速查找
  • LRU
    • Buffer Pool 已满时,读取外存新页面,必须选择一个内存 Buffer 进行替换
    • 最近最少使用(Least Recent Used),最经典的替换策略
    • LRU为双向链表,并且分为冷热端两部分
      在这里插入图片描述
    • LRU_young ,针对频繁访问的 buffers
    • LRU_old ,针对不经常访问的 buffers
    • LRU_old 所占比例由参数:innodb_old_blocks_pct 控制,默认占 3/8
    • LRU 相关参数: access_time(block第一次被真正访问的时间),freed_page_clock(此 block 上一次移动到 LRU_young 时,当前Buffer Pool Instance一共替换出去的 blocks数量的快照)
    • 如何判断一个LRU链表中的Block是否够Young
      在这里插入图片描述
    • 算法解析:若从page上一次移动到buf pool的LRU_young以来,buf pool在此期间替换出去的page数量,不足LRU_young list 长度的1/4,那么说明本page足够年轻,无需移动;否则,本次访问需要移动page到LRU_young
    • 一个大的全表扫描,可能替换LRU链表中的所有Page,导致LRU失效(优化参数 innodb_old_blocks_time)
    • innodb_old_blocks_time 参数作用:第一次访问page,不提升至LRU_young,只设置access_time;第二次访问page,当前时间与access_time差值大于此参数,则提升,否则不提升
  • Dirty Page Flush
    • 除了 LRU List Flush 外,刷脏页还存在 Flush List Flush 机制
    • InnoDB维护一个Flush List链表,保存所有的Dirty Pages,Dirty Pages 在链表中按照其第一次修改的时间排序
    • Flush List Flush 可以推进 Checkpoint,减少 Crash 后 redo 重做的日志量
    • 从Flush List Head开始,flush部分dirty pages到磁盘
    • 写回磁盘的dirty page,必须保证日志已经 flush 到newest_modification_lsn
    • 写回磁盘的dirty page,被修改为clean page,从Flush List移除
    • 并不从内存LRU List摘除,因此不会释放 buffer pool 内存空间
  • CheckPoint
    • InnoDB Checkpoint,是一个LSN (Log Sequence Number),指向日志(redo)文件的特定位置
    • 标识小于此Checkpoint LSN的脏页,已经写出磁盘,无需Redo
    • 是系统crash recovery的起点
    • Checkpoint LSN,保存在每个日志组日志文件第一页之中
  幻想到一种异常:一个事务修改数据线记录 redo(操作A),后写 buffer pool 中的页(操作B),
如果这两个操作相隔时间非常长,也就是一直没有产生脏页,可能导致 CheckPoint 一直无法推进。
但是写 buffer pool 中的页是内存操作,这种可能不可能发生,除非内部出现类似死锁的情况。`
其他

InnoDB 的事务
InnoDB 的锁

参考文档

http://hedengcheng.com/?p=360
https://draveness.me/mysql-innodb

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值