推荐阅读
- 学习笔记 《 深入理解 Java 虚拟机》
- 学习笔记 《 后端架构设计》
- 学习笔记 《 Java 基础知识进阶》
- 学习笔记 《 Nginx 学习笔记》
- 学习笔记 《 前端开发杂记》
- 学习笔记 《 设计模式学习笔记》
- 学习笔记 《 DevOps 最佳实践指南》
- 学习笔记 《 Netty 入门与实战》
- 学习笔记 《 高性能MYSQL》
- 学习笔记 《 JavaEE 常用框架》
- 学习笔记 《 Java 并发编程学习笔记》
- 学习笔记 《 分布式系统》
- 学习笔记 《 数据结构与算法》
InnoDB是事务安全的存储引擎,设计上采用类似于 Oracle 数据库的架构。InnoDB 是第一个完整支持 ACID 事物的存储引擎。其特点是行级锁支持、支持MVCC、支持外键等等。InnoDB 存储引擎已被大型互联网公司认证为可靠的数据库存储引擎。
1、InnoDB的版本
InnoDB 有多个版本的,一种是静态的版本,这里称之为老版本的InnoDB,一种是动态的InnoDB 版本,通过数据库CLI 可以查看到InnoDB 的版本 show variables like 'innodb_version'
。
mysql> show variables like 'innodb_version';
+----------------+--------+
| Variable_name | Value |
+----------------+--------+
| innodb_version | 8.0.18 |
+----------------+--------+
1 row in set (0.04 sec)
2、InnoDB 的体系架构
在之前的文章中,读者已经熟悉了MySQL的体系架构,这里我们来看一些InnoDB存储引擎的体系架构。下图中简单的展示InnoDB的体系模型图,InnoDB 有多个内存块,多个内存块组成了InnoDB存储引擎的内存池。
2.1 后台线程
后台线程的主要作用是负责刷新内存池中的数据,保证缓冲池中的内存缓存的是最新数据;将已修改数据文件刷新到磁盘文件;保证数据库发生异常时 InnoDB 能恢复到正常运行 的状态。
- Master Thread
主要负责将缓冲池中的数据异步刷新到磁盘,保证数据的一致性,包括脏页的刷新、合并插入缓冲(INSERT BUFFER)、 UNDO页的回收等等
-
IO Thread
InnoDB存储引擎中大量使用AIO(Async IO)来处理写IO的请求,而IO Thread的工作主要就是负责这些IO请求的回调。 InnoDB 1.0版本之前一共有4个IO Thread,分别是write、read、insert buffer和log IO thread。IO Thread 的线程数量可以通过系统变量查询。
mysql> show variables like 'innodb_%_io_threads'
+-------------------------+-------+
| Variable_name | Value |
+-------------------------+-------+
| innodb_read_io_threads | 4 |
| innodb_write_io_threads | 4 |
+-------------------------+-------+
Time: 0.003s
-
Purge Thread
事务被提交后,其所使用的undolog可能不再需要了,purge thread来回收已经使用并分配的undo 页。在InnoDB 1.1 版本之前,undo页的回收也就是purge操作都是在master thread中进行的,InnoDB1.1版本之后,就分配到单独的线程中执行,也就是 purge thread。事实上,PurgeThread 也是支持多线程的,线程的数量可以通过全局变量查询。
mysql> show variables like 'innodb_purge_threads'
+----------------------+-------+
| Variable_name | Value |
+----------------------+-------+
| innodb_purge_threads | 4 |
+----------------------+-------+
Time: 0.013s
- Page Cleaner Thread
page cleaner thread 是在InnoDB 1.2.x版本之后加入的,将原本放在master thread中进行的脏页刷新操作放到了单独的线程中来完成。
2.2 内存池
内存池主要由多个内存块组成,主要由三大部分组成缓冲池、重做日志缓冲、额外内存池。
- 缓冲池
InnoDB 存储引擎是基于磁盘存储的。在计算机系统中,由于磁盘和内存的速度不一致,因此基于磁盘的数据库系统经常使用缓冲的技术来提高数据库性能。
所谓的缓存,就是通过一块内存区域来弥补磁盘速度较慢的缺陷,首先将数据库从磁盘中读取的页放到缓冲池中,这个过程称之为FIX页数据到缓冲池中。下次在读取相同的页的时候,首先判断该页是否存在缓冲池中,存在的话直接使用,否则从磁盘中读取数据。对于写操作,首先会修改缓冲池上的数据,然后在以一定的评率刷新到磁盘上,值得注意的是页从缓冲池刷新到磁盘的操作并不是在页发生更新时候出发,而是通过 CheckPoint 的机制刷新到磁盘上。
综上所述,缓冲池的大小间接着影响着数据库的性能。由于32位的操作系统扥限制,该系统下最多支持3G 的内存,但是随着技术的更新,服务器的内存越来越大,因此强烈建议数据库的服务器都采用64位的配置。对于 InnoDB 存储引擎而言,其缓冲池的大小通过系统变量 innodb_buffer_pool_size
定义,比如
mysql> show variables like 'innodb_buffer_pool_size'
+-------------------------+-----------+
| Variable_name | Value |
+-------------------------+-----------+
| innodb_buffer_pool_size | 134217728 |
+-------------------------+-----------+
1 row in set
Time: 0.013s
具体来看,缓冲池中缓冲的数据类型包括: 索引页,数据页,undo页,插入缓冲,自适应Hash缓冲,数据字典等等。不能简单的认为缓冲池只缓存索引和数据页,之不过他们占用了缓冲池大部分空间而已。
从 InnoDB 1.0.x 开始 InnoDB 存储引擎允许有多个缓冲池实例,每个页根据哈希值平均分配到不同的缓冲池实例中,减少了数据库内部的资源竞争,增加数据库并发处理能力。InnoDB 缓冲池实例的数量可以通过 `innodb_buffer_pool_instances` 查询以及配置。
mysql> show variables like 'innodb_buffer_pool_instances';
+------------------------------+-------+
| Variable_name | Value |
+------------------------------+-------+
| innodb_buffer_pool_instances | 1 |
+------------------------------+-------+
1 row in set
Time: 0.016s
通过查询InnoDB 存储引擎的状态可以查看缓冲池的使用情况。
mysql> show engine innodb status;
-- 省略部分数据
----------------------
BUFFER POOL AND MEMORY
----------------------
Total large memory allocated 137363456
Dictionary memory allocated 859892
Buffer pool size 8192
Free buffers 6423
Database pages 1737
Old database pages 630
Modified db pages 0
Pending reads 0
Pending writes: LRU 0, flush list 0, single page 0
Pages made young 3, not young 0
0.00 youngs/s, 0.00 non-youngs/s
Pages read 919, created 818, written 3337
0.00 reads/s, 0.00 creates/s, 0.00 writes/s
No buffer pool page gets since the last printout
Pages read ahead 0.00/s, evicted without access 0.00/s, Random read ahead 0.00/s
LRU len: 1737, unzip_LRU len: 0
I/O sum[0]:cur[0], unzip sum[0]:cur[0]
从 MySQL 5.6 开始可以通过 MySQL 内置的性能检测表查询缓冲池的使用情况
mysql root@localhost:mysql> use information_schema;
You are now connected to database "information_schema" as user "root"
Time: 0.002s
mysql> select POOL_ID,POOL_SIZE,FREE_BUFFERS,DATABASE_PAGES FROM `INNODB_BUFFER_POOL_STATS`;
+---------+-----------+--------------+----------------+
| POOL_ID | POOL_SIZE | FREE_BUFFERS | DATABASE_PAGES |
+---------+-----------+--------------+----------------+
| 0 | 8192 | 6423 | 1737 |
+---------+-----------+--------------+----------------+
1 row in set
Time: 0.012s
- LRU 策略
数据库中缓冲池是通过 LRU(Lastest Recent Used 最近最少使用) 算法来进行管理的,也就是说最频繁使用的数据(热区数据) 放在LRU 列表的前端,最少使用的放在LRU 列表的尾端。当缓冲池不能存放新的页数据的时候,将LRU 尾部的数据移除缓冲区刷入磁盘中。在 InnoDB 中页的大小是 16KB,这和传统的LRU有所区别的是新的页并非直接放到 LRU 列表的首部,而是直接放在了被称之为 midpoint 位置的地方,在默认配置下,这个值距离LRU 列表首部约 5/8 的位置。midpoint 位置可由 参数 innodb_old_blocks_pct
指定, 比如查询到值为 37 表示 midpoint 距离 LRU 尾部的距离为 37% (约 3/8)。如果将新的页数据放在 LRU 列表首部,则有可能将其他热区数据刷出 LRU 的可能,造成性能降低,因此将新的数据页放在 midpoint 位置,而非朴素 LRU 中放到列表首部。
mysql> show variables like 'innodb_old_blocks_pct';
+-----------------------+-------+
| Variable_name | Value |
+-----------------------+-------+
| innodb_old_blocks_pct | 37 |
+-----------------------+-------+
1 row in set (0.00 sec)
- 重做日志缓冲
redolog 值得是在数据库服务以外宕机的时候保证数据持久化的一种保障。显然 redolog 也是存在缓存的,其大小通过字段 innodb_log_buffer_size
控制,默认大小为 8M。通常情况下,8M 的空间足够记录 redoLog,因为下面几种情况会将重做日志刷新到磁盘中。
- Master Thread 每隔1S 将重做日志缓冲刷新到磁盘中
- 每个事务提交时候,会将重做日志缓存刷新到磁盘中
- 当重做日志缓存小于其大小的 1/2 时,重做日志缓存刷新到磁盘中
mysql> show variables like 'innodb_log_buffer_size';
+------------------------+----------+
| Variable_name | Value |
+------------------------+----------+
| innodb_log_buffer_size | 16777216 |
+------------------------+----------+
1 row in set (0.00 sec)
-- 转换为 M
mysql> select 16777216 / 1024 / 1024;
+------------------------+
| 16777216 / 1024 / 1024 |
+------------------------+
| 16.00000000 |
+------------------------+
1 row in set (0.00 sec)
- 额外的内存池
略