mysql源码分析——InnoDB的内存结构分析

一、基本的数据结构

在InnoDB中,数据的分配和存储也有自己的数据结构,在前面分析过MySql中的内存管理,但是内存管理是有一个不断抽象的过程。在InnoDB中还会有一层自己的内存管理。在InnoDB引擎中的内存结构主要有四大类:
1、Buffer Pool
在MySql中,数据都是存储在磁盘中的,也就是说,从理论上讲,每次做Select查询,都要去磁盘读取相关的数据。但这样的话,频繁的IO操作,一定会大幅降低数据库的可用性。毕竟计算机技术发展到今天,速度瓶颈仍然是IO操作。那么可以通过一定的算法把数据预置在内存中,这样通过这种方式就可以提高访问的速度。相应的,就需要一套机制来管理这些内存来服务于需要访问这些内存的线程(进程),这就是Buffer Pool。
2、Change Buffer
在上面提到的Buffer Pool目的是为了提高读取的IO效率,那么数据库还需要写入呢。同样也需要和IO打交道,这时,Change Buffer就出现了。在MySql5.5以前,它也叫做Insert Buffer,因为它只对INSERT进行了优化,而现在则对UPDATE DELETE 都进行了优化,所以就得改个名字,所以叫做Change Buffer。
在MySql中如果需要更新一个数据,如果数据在内存中,就直接处理即可。但有不少情况,数据并不在内存中,这就需要在不影响数据一致性的前提下把它读取到内存中即Change Buffer。需要注意的是,它只适用于非唯一的普通索引页。
3、Adaptive Hash Index
在前面分析知道,MySql的底层是一个B+树的结构,但过深(虽然一般不超过4层)的路径往往意味着效率的急剧降低。那么,如果InnoDB引擎在实际运行中发现,如果数据操作的SQL语句比较复杂(数据的路径过深,多次回表等),而且可以命中相同的页面的话,InnoDB引擎就会在内存中开辟一块区块建立Adaptive Hash Index(AHI,自适应哈希索引)用来加速SQL操作。
4、Log Buffer
在数据库中,WAL技术(预写日志技术),就是为了保证数据一致性的一个重要环节。那么在InnoDB中就是Redo Log。在Redo Log中,一般通过三层来控制日志的操作,即Log Buffer,FS Page Cache和Redo Log Files。这也就明白了,日志要写入要日志缓冲区,然后再通过策略逐步落盘。

二、Buffer Pool

想弄明白缓冲池就要弄清楚MySql中原来的query cache(查询缓冲),查询缓冲在8.0版本前是用来做为Sql语句查询的缓冲使用的。在8.0后被移除了。要弄清楚这二者的不同,首先要明白两个概念,在MySql中分为Sever层和存储引擎两个层面。而查询缓冲是位于Server层中的。而Buffer
查询缓冲的优势是如果有相同情况下就不再操作存储引擎,提高查询的效率。但它有一个查询时是严格按照语句来匹配的,对大小写同样敏感。这就导致它的效率虽然在某些情况下会大幅提高,但应用的面比较窄。同时,表本身数据的任何修改,都会导致整个缓存的失效。同时,查询缓冲还需要受到锁保护,这在原来计算机比较落后的情况下,还没有什么太大影响,随着计算机技术的不断演进,多核和多CPU的服务器已经成为主流,这对锁的竞争是一个大问题,直接会导致锁竞争的大量出现,严重降低性能。这也是其在MySql8中被删除的一个重要原因。
MySql中的数据页是以16K为基本单位的,同样,Buffer Pool也是以页为基本单位。换句话说,流数据的转换,即从内存到硬盘,就是以这个基本单位的大小为交换基础的。在Buffer Pool中,包含着多个如此的页面。在每个页中,会有一个数据块用来描述此页所性的表空间,数据页的编号,缓存页在Buffer Pool中的地址,这有点类似于进程描述符的作用。有了这些具体的数据,就可以快速定位和查询到指定的数据。
Buffer Pool的大小可以在innodb_buffer_pool_size 参数中进行配置,默认为128M,但实际上会比此值略大一些达到130余M。
那么内存大小范围既定,但实际的数据可能会不断增加变化,这就需要有一个空间管理的算法。对Redis中的内存算法有些熟悉可能会想到一些经典的内存管理算法,比如 LRU,在MySql中,LRU机制略微有些不太给力,所以设计一个冷热数据分享的内存管理,其实就是把常用的数据和不常用的数据分成热冷两种状态,分别使用两个LRU来进行处理。
同样,为了保证数据的访问效率,Buffer Pool使用了两类预读机制,即线性预读(linear read-ahead)和随机预读(randomread-ahead)。线性预读的单位是extent,其包含64个数据页。而随机预读则指如果一个extent中某些页面出现在缓冲中,则把整个余下都加载上来。
在Buffer Pool中有三种基础的数据链表,即LRU链表,flush链表和free链表。其中Flush链表中主要存储的是脏页。

三、Change Buffer

Change Buffer其实做用整体上和Buffer Pool没有啥区别,此数据结构主要是用来做更新与持久化的一个缓冲层的。前面提到过,如果每次更新操作都需要操作IO进行持久化处理,那么速度可想而知,同样,磁盘的寿命也是有限的,反复的不断的持久化,硬盘估计很快就废了。
Change Buffer就是当数据更新时,如果此数据在内存中(Buffer中),直接更新就完事。然后在按照既定方式刷新到磁盘中;否则就在不影响一致性的情况下,将数据读入Buffer,重复刚才的动作。这其是就是一个merge动作。这样做的好处显而易见,一个是操作的速度明显加快,只有在内存无法命中的情况下,才会回到原来的速度,第二个就是可以合理得用策略来降低磁盘IO的次数。
当然,任何手段和方法都是普适的,在一些模式下,如写后马上读,就会导致merge的立刻发生,反而会影响效率。所以在写多读少的情况下,它的优势非常明显;而在读写并行的情况下,正如刚刚分析,反而会降低效率。
那么什么情况下会触发此缓冲机制呢?
1、最典型的就是数据页的访问更新
2、后台线程发现数据库空闲
3、数据库缓冲池不够
4、数据库正常关闭
5、redo log写满(很少发生)
那么什么情况下适合打开InnoDB的写缓冲机制呢?
1、数据库大部分都是非唯一索引
2、业务写多读少,写后不会马上读
Change Buffer的使用有些人会担心会不会引起数据的不一致现象,答案是不会的。原因是正常操作没有这种问题出现,而在数据为异常崩溃时,可以从redo log中恢复数据,同时,Change Buffer可以持久化。

四、ADaptive Hash Index

adaptive Hash Index(自适应哈希索引)看上去更像一个内存的数据结构的应用,只要有足够的Buffer Pool,它就可以让MySql更像一个内存型数据库,说白了就是效率更高。MySql本身并不支持HASH,因为它并不支持用户手动创建哈希索引。但MySql会根据实际情况来判定是否自己创建哈希索引来提高应用效率。
那么在什么场景下一般会创建AHI呢?
1、大量的单行记录查询
2、索引范围查询
3、内存足够
在这种情况下,一般数据大概率会命中相同的页面,此时以索引键值或者其前缀为KEY,以索引记录的页面位置为Value,就可以创建一个哈希索引表。
从上面的情况来看,AHI的建立是通过观察者模式来建立的,HASH索引的效率很高,如果在应用场景合适的情况下,维护一个此数据结构比通过既有的方式去查询还是很有优势的。但如果读写锁的负载很重,并发关联很多且有大量的匹配符操作,就不太适合于使用此种方式。那么就可以关闭AHI来保证数据库的效率。
AHI仍然是B+树的索引,这也好理解,总不能为一个不经常使用的索引专门搞一个数据结构来用,成本有点高,维护也更复杂。
AHI是一种内存数据结构,不会持久化,这个要和其它的索引区别开来。它只能用于等值比较,如=,<>,IN,AND等,而不能排序,其基本是基于主键的搜索,或者说主键搜索几乎都是哈希查找,而非主键索引几乎都不是哈希查询。其起始配置innodb_buffer_pool_size的1/64,然后会根据InnoDB的Buffer Pool自动调整。

五、Log Buffer

MySql中有两个比较重要的日志一个是undo log,另外一个就是今天要分析相关的redo log。MySql中对InnoDB的表进行更改时,首先是更新到日志缓冲区中,然后写入redo logs的日志文件中。而这个日志缓冲区就是要分析的Log Buffer。它的大小由innodb_log_buffer_size 来决定默认是16M。日志缓冲区的内容定时刷新到磁盘。同样,日志缓冲区同样可以节省IO操作。而日志缓冲区的大小可以影响到IO的次数,所以在实际情况中要根据实际情况来正确的配置缓冲区的大小。
前面也提到过,MySql就是靠日志来保证数据的完整性的,一旦遇到天灾人祸,不小心崩溃,日志是可以有效的恢复相关的数据的。一般来说日志分成两大层,即内存缓冲区和磁盘文件。但磁盘文件一般了解的都知道它是有一层系统文件缓冲区的。正常来讲,MySql只控制内存部分,落盘是交给OS的接口来实现。
为了保证数据的安全可以设置innodb_flush_log_at_trx_commit的值来控制事务提交时,刷新Redo log的策略:
1、高性能(innodb_flush_log_at_trx_commit=0)
此策略中每隔一秒,才将Log Buffer中的数据批量write入FS Page Cache,同时MySQL主动fsync。
此策略的缺点在于如果数据库崩溃,一秒内的数据丢失。
2、强一致(innodb_flush_log_at_trx_commit=1)
此策略中每次事务提交,都将Log Buffer中的数据write入FS Page Cache,同时MySQL主动fsync。这种策略,是InnoDB的默认配置,为的是保证事务ACID特性。
此策略的缺点就是性能较差。
3、平衡方案(innodb_flush_log_at_trx_commit=2)
此策略中每次事务提交,都将Log Buffer中的数据write入FS Page Cache;每隔一秒,MySQL主动将FS Page Cache中的数据批量fsync。
此策略的缺点是假如OS崩溃溃,至多有一秒的数据丢失。

六、总结

基础的数据结构是支持应用运行的最根本的手段。掌握了这些基础的数据结构,就可以在理解流程时,明白设计开发者的目的和思路,就更容易掌握整个模块运行的流程。正如前面所说,阅读源代码的目的是,就是掌握别人的优秀的理念并为形成自己软件编程思想的重要方式。
学习如果不为已所用,学习又有何用?照猫画虎,只也是吓唬一下外行人罢了。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值