mysql是被设计为一个单进程多线程架构的开源数据库。可以说mysql的架构是插件式的存储引擎架构,区别于其他数据库最重要的一个特点就是其插件式的表存储引擎。mysql提供了一系列预定义的存储引擎编程接口,开发人员通过这些预定义的接口,可以实现自己的存储引擎。
MySQL数据库的体系结构如下:
而innodb是mysql被使用的最多的存储引擎之一,其特点就是行锁,支持事务,适用于OLTP(On-Line Transaction Processing)的应用。innodb最开始也是由第三方开发的开源存储引擎,后被oracle收购,从mysql5.5.8开始innodb已经是默认的存储引擎了。
查看生产环境使用的阿里RDS的sql 版本: SELECT version(); 结果是 5.5.18.1-log。
查看生产环境使用的rds中的innodb的版本 SELECT * FROM information_schema.plugins 返回结果中有个plugin_name为innodb的显示为1.1。默认的 mysql 5.5版本对应的innodb的版本就是1.1.x,mysql5.6版本对应的innodb的版本就是1.2.x。各个版本内容如下:
老版本的innodb | 支持ACID、行锁、MVCC |
innodb1.0.x | 继承上述版本,加入compress和dynamic页格式 |
innodb1.1.x | 继承上述版本,加入Linux AIO、多回滚段 |
innodb1.2.x | 继承上述版本,支持全文索引,在线索引的添加 |
innodb 体系结构图
主要分为两个大的部分,后台线程和内存池。
后台线程的主要作用是负责刷新内存池中的数据,保证缓冲池中的内存缓存的是最新数据;将已修改数据文件刷新到磁盘文件;保证数据库发生异常时 InnoDB 能恢复到正常运行 的状态。
后台线程主要分为4个部分
主要负责将缓冲池中的数据异步刷新到磁盘,保证数据的一致性,包括脏页的刷新、合并插入缓冲(INSERT BUFFER)、
UNDO页的回收等等
InnoDB存储引擎中大量使用AIO(Async IO)来处理写IO的请求,而IO Thread的工作主要就是负责这些IO请求的回调。
InnoDB 1.0版本之前一共有4个IO Thread,分别是write、read、insert buffer和log IO thread。从1.0.x版本开始 ,read和write
增加到4个,一共10个IO Thread,阿里云的RDS就是这10个IO Thread。
事务被提交后,其所使用的undolog可能不再需要了,purge thread来回收已经使用并分配的undo 页。在InnoDB 1.1 版本之前
undo页的回收也就是purge操作都是在master thread中进行的,InnoDB1.1版本之后,就分配到单独的线程中执行,也就是
purge thread , 尽管现阶段使用的阿里云的RDS是基于mysql 5.5版本的,5.5对应的InnoDB就是1.1.x版本,但是配置的purge
thread是为0的,目测应该是master thread 进行purge操作来回收已经分配并使用的undo页。
page cleaner thread 是在InnoDB1.2.x版本之后加入的,将原本放在master thread中进行的脏页刷新操作放到了单独的线程中
来完成。
内存池主要由多个内存块组成,主要由三大部分组成缓冲池、重做日志缓冲、额外内存池。
缓冲池的作用就是缓存磁盘上的热点数据,用来解决类似CPU快于主存,中间加了层高速缓存的设计。
读取操作:在数据库中进行读取页的操作,首先从磁盘上读取到的页放在缓冲池中,这个过程称为FIX在缓存池中,下一次再调用相同的页时,判断该页是否在缓存池中,如果在就直接取缓存池中该页的数据。
修改操作:对数据库中进行修改,首先修改缓存池中的页,然后再以一定的频率刷新到磁盘上。这里一定要注意,不是缓存池中的页被修改之后就立马将数据刷新到磁盘中,而是通过一种称为CheckPoint的机制刷新回磁盘。
执行 SHOW VARIABLES LIKE "innodb_buffer_pool_size" ,会发现阿里RDS中,只有一个缓存池。
说到内存池,首先就要想到的是内存管理,说到内存管理,之后会涉及到一定的内存回收。
innodb的内存池(缓冲池)是通过LRU算法来进行管理的,什么是LRU呢,最近最少使用算法,即最频繁使用的页在LRU列表的最前端,最少使用的页在LRU列表的最末端。具体的话wiki下。说到LRU,第一反应,就是memcached就是使用LRU的,虽然都是通过LRU算法来管理内存,但具体的还是有区别的。InnoDB存储引擎对传统的LRU做了一些优化,LRU列表中加入了midpoint位置,新读取到的页尽管是最近访问的页,但并不是直接放到LRU李列表的首部,而是放到LRU列表的midpoint的位置。midpoint之前都叫热端,midpoint之后都叫冷端。
为什么不是放到首部呢,假设是放到首部,原本首部放入的是非常频繁的一个sql查询结果,新的查询里面都加入到首部,很有可能会将之前的sql查询结果冲掉了,而这些新的查询实际上都是一次查询结果,并不是很频繁的,这样就反而将很频繁查询的内容,冲掉了,还要重新从磁盘中查询,没有达到缓存的效果。
什么时候冷端的数据会到热端?InnoDB有个参数innodb_old_blocks_time,表示读取到mid位置后的冷端数据要等待多久才会被加入到热端中,这个操作被官方定义为page made young。
数据库刚启动时,LRU列表是空的,内存中的空闲页都会被放置在free 列表中,缓存数据时,LRU先是询问free列表中有没有空闲的页,如果有,free列表中就移出该页,将该页添加到LRU列表中,当free列表中没有空闲的页时,就淘汰LRU列表末尾的页。
innodn 1.0.x版本支持压缩页的功能,即将原本16KB的页压缩为1、2、4和8KB。对于非16KB的页,不是存储在LRU列表中,而是存储在unzip_LRU列表中,尽管阿里的RDS的innnodb是1.1.x版本,但是阿里的RDS是不支持页压缩的,所以可以看到unzip_LRU的大小始终为0。
InnoDB存储引擎在执行事务开始,首先将重做日志信息先放入到这个缓冲区中,然后按照一定的频率刷新到磁盘中,重做日志缓冲一般都不需要设置的很大,因为一般情况下每一秒都会将重做日志缓冲刷新到磁盘中,所以只需要保证每一秒产生的事务量控制在这个缓冲大小即可,默认为8MB。
重做日志缓冲有三种情况会从缓存中刷入到硬盘
1. master thread每一秒都会将重做日志缓冲刷新到磁盘中
2. 事务提交时会将重做日志缓冲刷新到磁盘中
3. 当重做日志缓冲池的剩余空间少于一半时,重做日志缓冲也会刷新到磁盘中
这个地方通常是DBA需要关心的,在innodb引擎中需要对一些数据结构本身进行内存分配时,需要从额外的内存池中进行申请,当然当额外的内存池不够时,会从缓冲池中申请。