MySQL-InnoDB 存储引擎概述
# 整体架构
(图片来自MySQL官网:https://dev.mysql.com/doc/refman/5.7/en/innodb-architecture.html (opens new window))
# 内存结构
# 缓冲池(Buffer Pool)
Buffer Pool
用于缓存数据和索引,减少磁盘访问,提高查询速度。
Buffer Pool
是一个链表实现的数据页(默认大小16k),每个数据页可以存多行数据。当需要查询数据时,会先从Buffer Pool
中查找,如果没有查到数据,会读取磁盘文件进行查找,然后将查询到的数据写入Buffer Pool
。
由于服务器的内存是有限的,当Buffer Pool
内存不足时,会通过LRU算法(优化算法)淘汰掉最长时间没有使用的数据。
优化的LRU算法:Buffer Pool
划分为young
区和old
区,young
占5 / 8,old
占 3 / 8。当内存不足时,会淘汰掉链表末尾的数据,然后将新查询到的数据放在old
区域的头部。总体流程如下图。
(图中忽略了 Change Buffer
merge 的过程,下面会提到)
监控
SHOW ENGINE INNODB STATUS \G;
----------------------
BUFFER POOL AND MEMORY
----------------------
Total large memory allocated 2198863872
Dictionary memory allocated 776332
Buffer pool size 131072
Free buffers 124908
Database pages 5720
Old database pages 2071
Modified db pages 910
Pending reads 0
Pending writes: LRU 0, flush list 0, single page 0
Pages made young 4, not young 0
0.10 youngs/s, 0.00 non-youngs/s
Pages read 197, created 5523, written 5060
0.00 reads/s, 190.89 creates/s, 244.94 writes/s
Buffer pool hit rate 1000 / 1000, young-making rate 0 / 1000 not
0 / 1000
Pages read ahead 0.00/s, evicted without access 0.00/s, Random read
ahead 0.00/s
LRU len: 5720, unzip_LRU len: 0
I/O sum[0]:cur[0], unzip sum[0]:cur[0]
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
各参数含义,可以查看上面的官方文档,其中一个很重要的指标Buffer pool hit rate
是内存命中率,需要特别关注下。如果对性能要求比较高,最好能达到 99%。
Buffer pool
的大小通过参数innodb_buffer_pool_size
来设置(单位字节),通常可以设置为物理内存的60% ~ 80%。
查询innodb_buffer_pool_size
大小
SELECT @@innodb_buffer_pool_size/1024/1024/1024 as 'buffer_pool_size_GB';
参考:
- https://dev.mysql.com/doc/refman/5.7/en/innodb-buffer-pool.html (opens new window)
- https://dev.mysql.com/doc/refman/5.7/en/innodb-buffer-pool-resize.html (opens new window)
- https://time.geekbang.org/column/article/79407 (opens new window)
# 写(修改)缓冲(Change Buffer)
Change Buffer
用于缓存DML(update、insert、delete)操作,减少磁盘IO。会占用 Buffer Pool
的一部分空间。
当执行一个DML操作时,会先判断Buffer Pool
中是否有对应数据,如果有,则直接更新Buffer Pool
中的数据。如果没有,则将这个操作
缓存到Change Buffer
中。在下一次查询该数据的时候,会从磁盘读取数据(因为之前在Buffer Pool
也没有数据),然后把数据页写入Buffer Pool
,再将Change Buffer
merge到对应数据页(磁盘的数据是旧的,merge后得到正确的值)。除此之外,MySQL 后台线程也会定期 merge(这样可以把多次IO合并成一次,提高性能)。
(便于理解,图中忽略了 BufferPool 内存淘汰策略,可以结合上面的图一起理解)
需要注意的是,如果数据包含唯一索引,是无法使用Change Buffer
的,因为唯一索引需要从磁盘读取数据才能判断数据是否唯一。
另外对于写入数据后马上就需要查询的数据页不适合使用Change Buffer
,因为在下次读取的时候也是需要从磁盘读取数据(Buffer Pool不存在该数据),然后执行Change Buffer
merge。
相关参数
innodb_change_buffer_max_size
占用
Buffer Pool
多少百分比的空间,默认25,即:占Buffer Pool
25%的空间innodb_change_buffering
哪些操作可以使用
Change Buffer
,可以设置的值:all, none, inserts, deletes, changes, purges
监控
SHOW ENGINE INNODB STATUS\G
-- 相关参数含义查看官方文档
-------------------------------------
INSERT BUFFER AND ADAPTIVE HASH INDEX
-------------------------------------
Ibuf: size 1, free list len 0, seg size 2, 0 merges
merged operations:
insert 0, delete mark 0, delete 0
discarded operations:
insert 0, delete mark 0, delete 0
Hash table size 4425293, used cells 32, node heap has 1 buffer(s)
13577.57 hash searches/s, 202.47 non-hash searches/s
2
3
4
5
6
7
8
9
10
11
参考:
- https://dev.mysql.com/doc/refman/5.7/en/innodb-change-buffer.html (opens new window)
- https://time.geekbang.org/column/article/70848 (opens new window)
# 自适应哈希索引(Adaptive Hash Index)
InnoDB 索引使用的是B+Tree,查询复杂度是 O(logN),为了提高查询效率,MySQL 为热点数据构建了一个哈希索引,提高查询效率。
自适应哈希索引只能用于等值查询。
自适应哈希索引更新需要加锁,在5.7版本之前,只有一个锁来控制,5.7版本之后,使用了分区,类似 java 里面的 ConcurrentHashMap 分段锁,减小了锁的粒度,同分区才会有锁竞争。
我们可以通过监控,观察每个分区等待锁的线程数,如果等待线程过多,反而降低了性能,可以调整分区数量或关闭自适应哈希索引。
相关参数
innodb_adaptive_hash_index
: 是否开启自适应哈希索引,默认开启set global innodb_adaptive_hash_index=off/on
innodb_adaptive_hash_index_parts
: 分区数量,默认8
参考:
# 日志缓冲(Log Buffer)
Log Buffer 是写入磁盘日志数据的内存缓存区,作用是合并多条记录到一次磁盘IO中,减少IO次数,提高性能。
例如:在一个事务中需要修改多个记录,可以把多个记录的写操作合并在一次磁盘IO中。当然也可以把多个事务的多个记录合并到一次IO(有数据丢失风险,可以通过参数配置)
相关配置项
innodb_log_buffer_size
缓冲区大小,默认16MB,官方文档:https://dev.mysql.com/doc/refman/5.7/en/innodb-parameters.html#sysvar_innodb_log_buffer_size (opens new window)innodb_flush_log_at_trx_commit
日志刷盘机制,默认是1
,官方文档:https://dev.mysql.com/doc/refman/5.7/en/innodb-parameters.html#sysvar_innodb_flush_log_at_trx_commit- 0: 每秒刷盘,在 mysqld 崩溃的情况下,可能丢失1秒数据
- 1: 在提交事务时刷盘,ACID事务保障,数据不丢失
- 2: 每次事务提交时写入OS Cache,并且每隔1秒调用 flush 写入磁盘文件,在机器断电的情况下会丢失1秒数据
参考:
# 磁盘结构
- 表(Tables)
- 索引(Indexs)
- 表空间(Tablespaces)
- 数据字典(InnoDB Data Dictionary)
- 双写缓冲(Doublewrite Buffer)
- 重做日志(Redo Log)
- 撤销日志(Undo Logs)
https://dev.mysql.com/doc/refman/5.7/en/innodb-on-disk-structures.html (opens new window)