下图显示了构成InnoDB存储引擎体系结构的内存和磁盘结构。
InnoDB内存包含Buffer Pool
, Change Buffer
, Adaptive Hash Index
, Redo Log Buffer
Buffer pool是内存中的一个主要区域,用于在访问时缓存表和索引数据。
Buffer pool 允许直接从内存中处理经常使用的数据,从而加快处理速度, 在专用MySQL服务器上,通常会将最多80%的物理内存分配给Buffer pool。
为了提高高容量读取操作的效率,buffer pool 被分成可以容纳多行的page。
为了提高buffer管理的效率,buffer pool 被实现为链接的页面列表;
使用LRU算法的变体,很少使用的数据在cache中被淘汰。
使用最近最少使用(LRU)算法的变体将buffer pool 以列表进行管理。 该算法采用“中点插入法”:当插入一个新page时,移除表尾最近最少使用的page,在中点插入新page。
这个中点将链表分为两部分:
靠近表头的一部分,为young区,这里的page是最近使用的节点
靠近表尾的一部分,为old区,这里的page是最近少使用的
该算法通过链表中的page的使用热度来维持各page的位置,其中old区的page为链表满的时候移除的候选区。
默认情况下,算法操作如下:
链表的3/8被设置为old区
中点不是链表的中间点,而是old区的表头节点,即old区与young区的相邻的那个节点
当读取的数据不在缓冲池里的时候,读取到的page需要插入到链表中,插入点为中点,但是插入的新节点为old区的节点,如果此时old区满了得话,移除表尾的page(LRU节点)
当读取old区的page时,该节点将变成“young”节点:此节点移动到young区的表头(young区的头部那里)
在数据库操作中,被访问的节点将移除到young的表头,这样一来,在young区中的未被访问的节点将逐渐往表尾移动,当移动过中点,将变为old区的节点。而old区的节点若被访问到将变为young节点移动到表头,而old区中的为被访问的节点依旧往表尾移动,当表满时,表尾那个page将会被淘汰掉
参考博客如何在MySQL中分配innodb_buffer_pool_size
change buffer是一种特殊的数据结构,当要修改的辅助索引页不在buffer pool中时,用来cache对辅助索引页的修改。对辅助索引页的操作可能是insert、update和delete操作。等到相关的索引页被读入buffer pool中后,才会使用change buffer中的内容对辅助索引页进行修改(即merge操作)。
和聚集索引不同,辅助索引通常是不唯一的,插入辅助索引通常也是随机的。同样,对辅助索引的删除、更新也通常是不连续的。等到相关的索引页被读入buffer pool中后,才会使用change buffer中的内容对辅助索引页进行修改(即merge操作)可以避免大量的磁盘随机访问I/O。
间歇性的,在系统空闲或关闭过程中,会执行purge操作,将新的索引页写入磁盘。purge操作一次写多个索引值会比每次修改后就立即写入磁盘的效率高。
如果被更新的辅助索引行比较多,对change buffer的merge可能需要好几个小时。在merge过程中,磁盘的I/O会增加,可能会引起其他查询的性能的降低。merge操作也可能发生在事务提交后。事实上,即使在实例重启后,还会可能发生merge操作。
在内存中,change buffer会占用buffer pool的空间;在物理磁盘上,change buffer是system tablespace的一部分,所以对索引的修改在数据库重启后仍然存在change buffer中。
change buffer中缓存的数据类型由innodb_change_buffering
变量控制。
如果索引包含降序索引列或主键包含降序索引列,则不支持对辅助索引进行更改缓冲。
可以通过参数innodb_change_buffering
来控制是否启用change buffer。
`all`: 默认值。开启buffer inserts、delete-marking operations、purges
`none`: 不开启change buffer
`inserts`: 只是开启buffer insert操作
`deletes`: 只是开delete-marking操作
`changes`: 开启buffer insert操作和delete-marking操作
`purges`: 对只是在后台执行的物理删除操作开启buffer功能
从5.6.2开始,参数innodb_change_buffer_max_size
设置了change buffer可以占用buffer pool的百分比,默认是25,最大可以设置为50。
mysql> show variables like '%change%';
+-------------------------------+-------+
| Variable_name | Value |
+-------------------------------+-------+
| innodb_change_buffer_max_size | 25 |
| innodb_change_buffering | all |
| session_track_state_change | OFF |
+-------------------------------+-------+
3 rows in set (0.00 sec)
mysql> SHOW ENGINE INNODB STATUS\G
...
-------------------------------------
INSERT BUFFER AND ADAPTIVE HASH INDEX
-------------------------------------
Ibuf: size 1, free list len 11, seg size 13, 573 merges
merged operations:
insert 304, delete mark 411, delete 0
discarded operations:
insert 0, delete mark 0, delete 0
Hash table size 664091, node heap has 0 buffer(s)
Hash table size 664091, node heap has 0 buffer(s)
Hash table size 664091, node heap has 0 buffer(s)
Hash table size 664091, node heap has 0 buffer(s)
Hash table size 664091, node heap has 0 buffer(s)
Hash table size 664091, node heap has 57 buffer(s)
Hash table size 664091, node heap has 0 buffer(s)
Hash table size 664091, node heap has 0 buffer(s)
15000.00 hash searches/s, 43000.00 non-hash searches/s
...
information_schema.innodb_metrics
表提供了change buffer的统计信息名称和说明:
mysql> SELECT NAME, COMMENT FROM INFORMATION_SCHEMA.INNODB_METRICS WHERE NAME LIKE '%ibuf%'
-> ;
+-----------------------------------------+-------------------------------------------------------------+
| NAME | COMMENT |
+-----------------------------------------+-------------------------------------------------------------+
| buffer_page_read_index_ibuf_leaf | Number of Insert Buffer Index Leaf Pages read |
| buffer_page_read_index_ibuf_non_leaf | Number of Insert Buffer Index Non-Leaf Pages read |
| buffer_page_read_ibuf_free_list | Number of Insert Buffer Free List Pages read |
| buffer_page_read_ibuf_bitmap | Number of Insert Buffer Bitmap Pages read |
| buffer_page_written_index_ibuf_leaf | Number of Insert Buffer Index Leaf Pages written |
| buffer_page_written_index_ibuf_non_leaf | Number of Insert Buffer Index Non-Leaf Pages written |
| buffer_page_written_ibuf_free_list | Number of Insert Buffer Free List Pages written |
| buffer_page_written_ibuf_bitmap | Number of Insert Buffer Bitmap Pages written |
| ibuf_merges_insert | Number of inserted records merged by change buffering |
| ibuf_merges_delete_mark | Number of deleted records merged by change buffering |
| ibuf_merges_delete | Number of purge records merged by change buffering |
| ibuf_merges_discard_insert | Number of insert merged operations discarded |
| ibuf_merges_discard_delete_mark | Number of deleted merged operations discarded |
| ibuf_merges_discard_delete | Number of purge merged operations discarded |
| ibuf_merges | Number of change buffer merges |
| ibuf_size | Change buffer size in pages |
| innodb_ibuf_merge_usec | Time (in microseconds) spent to process change buffer merge |
+-----------------------------------------+-------------------------------------------------------------+
17 rows in set (0.01 sec)
information_schema.innodb_buffer_page
提供了buffer pool中每个页的元数据,包含change buffer 索引页和change buffer位图页。change buffer页中page_type.ibuf_index表示change buffer索引页;page_type.ibuf_bitmap表示change buffer位图页。
提醒:查看innodb_buffer_page会有很大的性能开销。最好是在空闲时间或测试环境执行。
mysql> SELECT (SELECT COUNT(*) FROM INFORMATION_SCHEMA.INNODB_BUFFER_PAGE
-> WHERE PAGE_TYPE LIKE 'IBUF%') AS change_buffer_pages,
-> (SELECT COUNT(*) FROM INFORMATION_SCHEMA.INNODB_BUFFER_PAGE) AS total_pages,
-> (SELECT ((change_buffer_pages/total_pages)*100))
-> AS change_buffer_page_percentage;
+---------------------+-------------+-------------------------------+
| change_buffer_pages | total_pages | change_buffer_page_percentage |
+---------------------+-------------+-------------------------------+
| 57 | 163820 | 0.0348 |
+---------------------+-------------+-------------------------------+
1 row in set (0.93 sec)
performance_schema
还为高级性能监控提供了change buffer mutex的等待指令:
mysql> SELECT * FROM performance_schema.setup_instruments
-> WHERE NAME LIKE '%wait/synch/mutex/innodb/ibuf%';
+-------------------------------------------------------+---------+-------+
| NAME | ENABLED | TIMED |
+-------------------------------------------------------+---------+-------+
| wait/synch/mutex/innodb/ibuf_bitmap_mutex | YES | YES |
| wait/synch/mutex/innodb/ibuf_mutex | YES | YES |
| wait/synch/mutex/innodb/ibuf_pessimistic_insert_mutex | YES | YES |
+-------------------------------------------------------+---------+-------+
3 rows in set (0.00 sec)
Adaptive Hash Index功能使InnoDB能够在系统上执行更多类似于内存数据库的工作,并为缓冲池提供适当的工作负载和足够的内存,而不会牺牲事务功能或可靠性。自适应哈希索引功能由innodb_adaptive_hash_index
变量启用,或在服务器启动时由--skip-innodb_adaptive_hash_index
关闭。
Adaptive Hash Index 的限制:
只能用于等值比较,例如=, <=>,in
无法用于排序
有冲突可能
MySQL自动管理,人为无法干预。
InnoDB存储引擎首先将重做日志信息
放入到redo log buffer,然后按一定频率将其刷新到 redo log file中。
日志缓冲区大小由innodb_log_buffer_size
变量定义。 默认大小为16MB。
日志缓冲区的内容会定期刷新到磁盘上的日志文件中。 大型日志缓冲区使大型事务能够运行,而无需在事务提交之前将重做日志写入磁盘。 因此,如果您有更新,插入或删除许多行的事务,则使日志缓冲区更大可以节省磁盘I / O.
redo log在下列三种情况会将redo log buffer中的内容刷新到外部磁盘的redo log file中:
innodb_flush_log_at_trx_commit
变量控制如何写入日志缓冲区的内容并刷新到磁盘。 innodb_flush_log_at_timeout
变量控制日志刷新频率,默认1s
参考博客MySQL redo log 与 binlog 的区别
参考官方文档Tables
参考官方文档Indexes
参考博客InnoDB表空间
InnoDB数据字典由内部系统表组成,其中包含用于跟踪对象(如表,索引和表列)的元数据。 元数据实际位于InnoDB系统表空间中。 由于历史原因,数据字典元数据在某种程度上与存储在InnoDB表元数据文件.frm文件
中的信息重叠。
doublewrite
缓冲区是位于系统表空间中的存储区域,InnoDB在页面写入数据文件中的正确位置之前写入从InnoDB缓冲池刷新的页面。只有在将页面刷新并写入双写缓冲区后,InnoDB才会将页面写入其正确的位置。如果在页面写入过程中存在操作系统,存储子系统或mysqld进程崩溃,InnoDB稍后可以在崩溃恢复期间从doublewrite缓冲区中找到该页面的良好副本。
虽然数据总是写入两次,但双写缓冲区不需要两倍的I/O开销或两倍的I/O操作。数据作为一个大的顺序块写入doublewrite缓冲区本身,对操作系统进行单个fsync()调用。
在大多数情况下,默认情况下启用doublewrite缓冲区。要禁用doublewrite缓冲区,请将innodb_doublewrite
设置为0。
doublewrite由两部分组成,一部分是内存中的doublewrite,大小为2MB,另一部分是物理磁盘上共享表空间中连续128个页,即2个区(extent),大小同样为2MB。在对缓冲池的脏页进行刷新时,并不直接写磁盘,而是会通过memcpy函数将脏页先复制到内存中的doublewrite buffer,之后通过doublewrite buffer再分两次,每次1MB顺序地写入共享表空间的物理磁盘上,然后马上调用fsync函数,同步磁盘,避免缓冲写带来的问题。在这个过程中,因为doublewrite页是连续的,因此这个过程是顺序写的,开销并不是很大。在完成doublewrite页的写入后,再将doublewrite buffer的页写入各个表空间文件中,此时的写入则是离散的。
参考博客InnoDB表空间
参考博客InnoDB表空间
InnoDB存储引擎是多线程的模型,因此其后台有多个不同的后台线程,负责处理不同的任务。
Master Thread 是一个非常核心的后台线程,主要将负责缓冲池中的数据异步刷新到磁盘,保证数据的一致性,包括脏页的刷新、合并Change Buffer、UNDO页的回收等。
Master thread具有最高的线程优先级别,其内部由多个循环(loop)组成:主循环(loop),后台循环(background loop),刷新循环(flush loop),暂停循环(suspend loop)。
InnoDB 1.0.x版本之前
的Master Thread的操作如下:
每一秒的操作如下:
每十秒的操作如下:
background loop会执行以下操作:
若当前没有用户活动(数据库空闲)或者数据库关闭(shutdown),就会切换到这个循环。
InnoDB 1.2.x版本之前
的Master Thread的改进操作如下:
innodb_io_capacity
。innodb_adaptive_flushing
(自适应地刷新),该值影响每秒刷新脏页的数量。通过函数buf_flush_get_desired_flush_rate(判断产生redo log的速度来决定最合适的刷新脏页数量。)来判断需要刷新脏页的数量。innodb_purge_batch_size
,控制每次full purge回收undo页的数量。InnoDB 1.2.x版本
的Master Thread的改进操作如下:
在InnoDB存储引擎中大量使用了AIO(Async IO)来处理写IO请求,这样可以极大提高数据库的性能。
根据参数innodb_read_io_threads
及innodb_write_io_threads
来设置读写进程。
事务提交后,其所用的undolog可能不再需要,因此需要purge thread来回收已经使用并分配的undo页。
通过参数innodb_purge_threads
来控制是否启用独立的Purge thread,如果不启用则Purge操作在Master thread线程完成。(>0表示启用,如需要4个Purge thread,可以设置为4).
Page Cleaner Thread 是在InnoDB 1.2.x版本引入的,其作用是将之前版本中脏页刷新操作都放入到单独的线程来完成。
目的也是为了减轻Master Thread的工作及对于用户查询线程的阻塞,进一步提高InnoDB存储引擎的性能。
通过参数innodb_page_cleaners
控制启用多个线程来刷新脏块。