MySQLInnoDB引擎现在广为使用,它提供了事务,行锁,日志等一系列特性,本文分析下InnoDB的内部实现机制,MySQL版本为5.7.24,操作系统为Debian9。MySQLInnoDB的实现非常复杂,本文只是总结了一些皮毛,希望以后能够研究的更加深入些。
1InnoDB架构
Innodb架构图
InnoDB的架构分为两块:内存中的结构和磁盘上的结构。InnoDB使用日志先行策略,将数据修改先在内存中完成,并且将事务记录成重做日志(RedoLog),转换为顺序IO高效的提交事务。这里日志先行,说的是日志记录到数据库以后,对应的事务就可以返回给用户,表示事务完成。但是实际上,这个数据可能还只在内存中修改完,并没有刷到磁盘上去。内存是易失的,如果在数据落地前,机器挂了,那么这部分数据就丢失了。
InnoDB通过redo日志来保证数据的一致性。如果保存所有的重做日志,显然可以在系统崩溃时根据日志重建数据。当然记录所有的重做日志不太现实,所以InnoDB引入了检查点机制。即定期检查,保证检查点之前的日志都已经写到磁盘,则下次恢复只需要从检查点开始。
2InnoDB内存中的结构
内存中的结构主要包括BufferPool,ChangeBuffer、AdaptiveHashIndex以及LogBuffer四部分。如果从内存上来看,ChangeBuffer和AdaptiveHashIndex占用的内存都属于BufferPool,LogBuffer占用的内存与BufferPool独立。
BufferPool
缓冲池缓存的数据包括PageCache、ChangeBuffer、DataDictionaryCache等,通常MySQL服务器的80%的物理内存会分配给BufferPool。
基于效率考虑,InnoDB中数据管理的最小单位为页,默认每页大小为16KB,每页包含若干行数据。为了提高缓存管理效率,InnoDB的缓存池通过一个页链表实现,很少访问的页会通过缓存池的LRU算法淘汰出去。InnoDB的缓冲池页链表分为两部分:Newsublist(默认占5/8缓存池)和Oldsublist(默认占3/8缓存池,可以通过innodb_old_blocks_pct修改,默认值为37),其中新读取的页会加入到Oldsublist的头部,而Oldsublist中的页如果被访问,则会移到Newsublist的头部。缓冲池的使用情况可以通过showengineinnodbstatus命令查看。其中一些主要信息如下:
----------------------BUFFERPOOLANDMEMORYTotallargememoryallocated#分配给InnoDB缓存池的内存(字节)Dictionarymemoryallocated#分配给InnoDB数据字典的内存(字节)Bufferpoolsize#缓存池的页数目Freebuffers#缓存池空闲链表的页数目Databasepages#缓存池LRU链表的页数目Modifieddbpages0#修改过的页数目......
ChangeBuffer
通常来说,InnoDB辅助索引不同于聚集索引的顺序插入,如果每次修改二级索引都直接写入磁盘,则会有大量频繁的随机IO。Changebuffer的主要目的是将对非唯一辅助索引页的操作缓存下来,以此减少辅助索引的随机IO,并达到操作合并的效果。它会占用部分BufferPool的内存空间。在MySQL5.5之前ChangeBuffer其实叫InsertBuffer,最初只支持insert操作的缓存,随着支持操作类型的增加,改名为ChangeBuffer。如果辅助索引页已经在缓冲区了,则直接修改即可;如果不在,则先将修改保存到ChangeBuffer。ChangeBuffer的数据在对应辅助索引页读取到缓冲区时合并到真正的辅助索引页中。ChangeBuffer内部实现也是使用的B+树。
可以通过innodb_change_buffering配置是否缓存辅助索引页的修改,默认为all,即缓存insert/delete-mark/purge操作(注:MySQL删除数据通常分为两步,第一步是delete-mark,即只标记,而purge才是真正的删除数据)。
ChangeBuffer
查看ChangeBuffer信息也可以通过命令。更多信息见mysqlserverteam:the-innodb-change-buffer。
-------------------------------------INSERTBUFFERANDADAPTIVEHASHINDEXIbuf:size1,freelistlen0,segsize2,0mergesmergedoperations:insert0,deletemark0,delete0discardedoperations:insert0,deletemark0,delete0Hashtablesize,nodeheaphas0buffer(s)Hashtablesize,nodeheaphas0buffer(s)Hashtablesize,nodeheaphas0buffer(s)Hashtablesize,nodeheaphas0buffer(s)Hashtablesize,nodeheaphas0buffer(s)Hashtablesize,nodeheaphas0buffer(s)Hashtablesize,nodeheaphas0buffer(s)Hashtablesize,nodeheaphas0buffer(s)
AdaptiveHashIndex
自适应哈希索引(AHI)查询非常快,一般时间复杂度为O(1),相比B+树通常要查询3~4次,效率会有很大提升。innodb通过观察索引页上的查询次数,如果发现建立哈希索引可以提升查询效率,则会自动建立哈希索引,称之为自适应哈希索引,不需要人工干预,可以通过innodb_adaptive_hash_index开启,MySQL5.7默认开启。
考虑到不同系统的差异,有些系统开启自适应哈希索引可能会导致性能提升不明显,而且为监控索引页查询次数增加了多余的性能损耗,MySQL5.7更改了AHI实现机制,每个AHI都分配了专门分区,通过innodb_adaptive_hash_index_parts配置分区数目,默认是8个,如前一节命令列出所示。
LogBuffer
LogBuffer是重做日志在内存中的缓冲区,大小由innodb_log_buffer_size定义,默认是16M。一个大的LogBuffer可以让大事务在提交前不必将日志中途刷到磁盘,可以提高效率。如果你的系统有很多修改很多行记录的大事务,可以增大该值。
配置项innodb_flush_log_at_trx_