近期企业内部分享了这篇架构,粘出来分享给大家.
参考资料:https://dev.mysql.com/doc/ref...
InnoDB存储引擎是平衡了高可靠和高性能的存储引擎,它也是Mysql8.0中默认的存储引擎.
从架构图中可见,InnoDB引擎可划分为两大部分:内存结构和硬盘结构.
内存部分(Memory)又包含Buffer pool, change buffer, Log Buffer 等,通过操作系统缓存与硬盘结构进行交互.
硬盘部分包含若干个表空间,如系统表空间,单文件表空间,一般表空间,临时表空间,undo表空间和redo log.
1.Buffer Pool(缓冲池)
缓冲池是主内存的一部分,它用于在我们查询访问任何表和索引数据时进行缓存.据官方文档介绍的统计,高达80%的查询来会由缓存池命中.
在介绍缓存池的官方文档中,我们可以见到一个分明另外的lru算法.
首先,缓冲池可以理解为一个塞满page的链表,注意,其中单位是页(page, innodb引擎的存储单位自上到下为表空间 table spaces,片段 segemant, 分区 extend, 页 page, 行row,列 column),也就是说,缓存是以页为单位的,io操作将硬盘中的数据读入缓冲的单位也是页.
其次,缓冲队列其实有两个sublist,最新的5/8为new sublist,较老的3/8为old sublist.new sublist 的tail与old sublist 的head相邻,这个相邻点被官方文档称之为midpoint(中点).较新访问的数据放置于new sublist,反之放置于old sublist.
第三,当我们从硬盘中读取新的页并入池时,它是放置于中点的.当然,这个"读"包含我们用户的初始化操作(user-initiated, 包含sql查询),也包含引擎为优化而做的预读操作(read-ahead).
第四,有关移动操作,对于old sublist的页成员进行读取操作时,分两种情况,若为用户初始化操作造成,则会立即将其移动到new list头,若为预读操作造成,则可能不会移动.
第五,每个页的位置相当于一个虚拟的"年龄",每当buffer pool有一个页被读取,都会造成其他页的年龄加1(实际效果即向后移位).同时,old sublist的成员也会因一些页的初次读入缓存而老化,通过这两个机制的共同作用,new sublist的元素不停地移入old sublist,old sublist中的数据可能会慢慢过期或得到新生.
2.change buffer(变更缓冲)
辅助索引和聚簇索引有什么区别?除了耳熟能详的前者需要二次io以外,变更缓冲也是两者在查询上的一个不同点.
change buffer的作用点在于,对于一些数据的二级索引进行更新操作(insert update delete都在此更新范围),而相应的辅助索引页还并未加载进缓冲池时,将相应的对辅助索引的更新缓存到change buffer,直到当后续对相应的页进行了读操作时,将这个结果合并进buffer pool.
这样实现的理由也很简单,我们知道辅助索引(二级索引)不像聚簇索引一样唯一,它的插入操作相对为随机写.对二级索引的删除和更新操作也可能会影响到在索引树中根本不相邻的数据页,因此将它暂时缓冲起来,在后续使用到变更索引的读操作发生时统一合并入buffer pool,再由buffer pool返回读取的结果,可以减少大量的随机写操作.
同时,当mysql进行周期净化(purge)操作时,或进行慢关闭时,可以将这些change buffer批量地写入到硬盘,后者会写入到系统表空间的change buffer,从而减少了多个单次io的开销.
特殊情况,当二级索引包含降序索引列,或主键包含降序索引列时,change buffer将不可用.change buffer的批量落盘操作会造成mysql瞬间的io开销增大.
3.adaptive hash index (自适应哈希索引)
自适应哈希索引作用于缓冲池中的数据,当有效地平衡了工作负载和缓冲池内存时,我们使用自适应哈希索引可以令mysql在不损坏任何事务性和可靠性的前提下逼近内存数据库的性能.
它是自适应的(意味着可以由innodb自行观测查询频率,并估算优化的结果和构建哈希索引的开销比),它的构建是使用索引key的左前缀实现的,主要针对频繁访问的数据.
有两种特殊地场景,高并发地访问自适应哈希索引(如并发地进行join)可能会是一个重要的竞态来源,当我们大量使用通配符%或like操作时,自适应哈希索引将毫无益处,此时可以通过重启服务并加上--skip-innodb-adaptive-hash-index关闭之.
4 log buffer
log buffer是一块内存区域,用于保存将要刷盘的日志操作,它有两个落盘的机制,一是周期性地落盘,二是事务提交后落盘,因此当我们使用大事务时,适当调大它的值,可以减少无谓的io.
5.system tablespace (系统表空间)
系统表空间是在硬盘中存放double write buffer和change buffer的表空间,同时,若一个表创建于系统表空间(而不是单文件表空间或一般表空间)时,它可存放这些表的表数据和索引数据.
6.File-Per-Table Tablespaces (单文件表空间)
据官方文档介绍,单文件表空间相对于传统的所有表数据存放于系统表空间的优势在于更加灵活,它使得每个表可以有自己的表空间文件.
7.General Tablespace (一般表空间)
一般表空间是共享的,由create tablespace语法创建的表空间.它与系统表空间类似,存放可供多表使用的共享数据.它具备先天的使用优势,如在完整生命周期内将表空间的数据加载至内存,则可减少类似单文件表空间的频繁io开销.
8.undo tablespaces
undo log包含于undo段,undo段包含于回滚段,而undo表空间就是存放了可用于回滚事务的undo log.
9.temporary tablespaces(临时表空间)
临时表空间包含两个级别:会话级别的临时表空间和全局的临时表空间.
会话级临时表空间每次使用时会从一个临时表空间池中取出并和session进行attach,默认情况下,server启动时会创建一个拥有10个临时表空间的池供使用,当会话释放时,它持有的临时表空间会被清空并还池.
会话级临时表空间默认大小为5个page大小,并且以.ibt结尾.
全局级临时表空间用于存放对于用户创建的临时表的变更回滚段.在server关闭或放弃了全局级临时表空间的初始化操作时销毁,在server每次启动时重建.
10.double write buffer(双写缓冲)
双写缓冲存放于系统表空间,是innodb从内存模块的buffer pool取出的页刷新到磁盘的目的地,这也说明对于innodb的写操作其实是二次写,但这并不影响宏观的性能,反而增大了吞吐量.double write buffer中已写入的数据将在后续刷入它真正的目的地,若在此过程中出现宕机等事故,mysql server将可以在double write buffer中找到一个准确的copy.
数据页刷入double write buffer的过程使用了操作系统的fsync()函数,将较大的数据量统一处理,顺序写入.这一特性可以手动关闭,或者在支持原子写的Fusion-io设备上运行时,将自动禁用double write buffer.
11 redo log(重放日志)
重放日志也是一个用于恢复数据的存放于硬盘的数据结构,但它作用于那些未完成的事务.在一次crash保存的未完成的对某些数据文件的更改可以在后续服务启动后的初始化过程中基于redo log自动重放.
重放日志在物理上体现为两个文件:ib_logfile0,ib_logfile1,mysql中对这两个文件的使用是"环形方式",这意味着可以交替重用.redo log中的日志标识是一个按LSN(日志顺序号)永远递增的数.
为了节省性能,重放日志也支持按组提交落盘(批量).日志本身支持压缩.
12 undo log(回滚日志)
undo log是单次读写事务的回滚日志,它包含了对于一个事务来说必要的针对一个聚簇索引记录的最近修改的回滚信息,如果有一个事务在一致读场景下需要读到原版本数据,它可以从它事务的undo log中找到相应的数据.
mysql innodb引擎的隔离级别实现与undo log有重大关联,当隔离级别为读已提交时,事务对数据的读取永远是取最新提交,并不需要undo log,但当级别为可重复读时,每次读取需要利用undo log进行.
- Multi-Versioning(多版本并发控制,一般简称mvcc)
innodb是一个支持多版本并发的存储引擎,前面已经提过,innodb利用表空间的回滚段(包含了undo log)来保存了为支持事务的并发和回滚而必需的,被修改数据行的老版本信息,它使用该信息进行回滚以及一致性读等操作.
关于mvcc的内部实现,innodb为每一行数据多维护了隐式的三个field,分别是6字节长的DB_TRX_ID,7字节长的DB_ROLL_PTR,6字节的DB_ROW_ID.
DB_TRX_ID:它表示最近对一个行的数据的插入或更新的事务id(删除在此理解为一种更新操作,只是行中有一个位表示标记为删除).
DB_ROLL_PTR:即滚动指针,它指向undo段的一个undo log.当发生该行的更新时,undo log中包含了重建历史数据的全部信息.
DB_ROW_ID:行id,它是一个在新行插入时单调递增的值,当innodb自动生成聚簇索引时,索引将包含行id,否则行id不出现在任何索引中.
存放于undo段的undo log区分为insert和update两种(后者包含delete),前者可以在事务提交之后直接舍弃,后者因一致性读的原因(这些读需要访问历史版本的数据),只能在所有快照均没有事务在使用时才能舍弃.
因此,需要及时地提交事务,尤其那些包含一致读的事务,否则undo log会越来越大,最终充满表空间.
涉及mvcc,被删除的数据行不会立即物理删除,直到有关的undo log被删除,这一操作称为purge,并且它会按照实际删除的顺序进行.
对于mvcc,innodb在聚簇索引和二级索引实现上稍有不同,聚簇索引能覆盖的记录会就地更新,而指向早期版本记录的undo log指向的一些隐藏信息条目可以进行重建,但对于二级索引,它并不包含隐藏系统列,也不进行原地更新.
当二级索引被更新时,老的二级索引记录会被标记删除,新的二级索引会插入,标记删除的老二级索引记录会最终被清理,如果已经开启了一个针对它的读事务,且发生了写操作,则从undo log中查找相应的版本记录.
当二级索引被删除时,覆盖索引将不可用,即不从索引树中查找,而是从聚簇索引记录中查找.
当使用索引条件下推优化时,且只有部分where查询条件可使用到索引,mysql server仍旧会将全条件下推到存储引擎,而存储引擎会即使相应的索引取值,如果没有取到值,则省去了后续的使用聚簇索引再次查找的过程.如果有记录(甚至可能是被标记删除但能匹配的记录,还未被purge,可能是因为mvcc),则无可避免后续的,还需要进行一次的利用聚簇索引查找的过程.
**粗体** _斜体_ [链接](http://example.com) `代码` - 列表 > 引用
。你还可以使用@
来通知其他用户。