赞
踩
mysql执行sql都是先数据到内存中,然后再将内存中的修改过的数据刷到磁盘中,Innodb的内存结构是映射磁盘的页结构的,那都会有哪些划分呢?有内存数据,则需要保证缓存一致性问题,Innodb是怎么做到的呢?mysql的IO线程,Innodb的三大特性啊这些等等,都来探究下。
一个程序要想高速,那么必不可少的要用到内存,mysql也不例外,不然用磁盘的速度就好比乌龟的速度,都不知要被淘汰多少次了。既然用到了内存,那么必然要对内存进行管理。
Innodb先向我们电脑申请一块连续的运行内存,把它命名为Buffer Pool(缓存池),然后把后续需要到的mysql数据加载到Buffer Pool这块内存中,那么Innodb就面临第一个问题,该申请多大的内存空间呢?
可以使用下面命令查看Innodb默认分配的大小:
mysql> show variables like '%innodb_buffer_pool_size%';
可以看到Innodb分配的内存其实是很小的,128M能存储的数据并不多,特别是当数据量非常大的时候。
Innodb的一个页内存是16kb,134217728/1024/16 = 8192个,总共能够存储8千多个页。
q : 建议分配多少的内存给buffer pool呢?
官网buffer pool内存给了建议: 建议分配物理机内存的80%大小,但是我觉得太大了,给个70%就好了,下图也有说,系统给分配的空间会比buffer pool的大小大10%,所以我们还得留下空间预防。
图中也说了:当buffer pool的大小大于1G,设置innodb_buffer_pool_instances的参数大于1会提高服务器的伸缩性,这个参数是指buffer pool的实例,即个数。
系统分配buffer pool内存时是可以按块分配的,默认每块是128M,系统参数是innodb_buffer_pool_chunk_size,在扩容的时候也是按着这个大小来的。
这时候头脑里应该有这样的一个架构:
下方是官方的Innodb架构图:
架构图中的左边就是buffer pool内存。
其实内存划分不止是只有buffer pool,只是buffer pool是最主要的核心,简单列一下其他的内存区域用途:
1. Innodb buffer pool
用来缓存表数据,索引,插入缓存,数据字典等信息。
2. Innodb log buffer
用来缓存事务的数据,即redo log buffer数据
3. sort buffer
用于sql语句在内存中的临时排序
4. join buffer
用于表连接,用于BKA
BKA (Batched Key Access):是一种用于表连接的技术,收集第一张表需要进行join的列id,然后排序,将这些id集合作为key发送给MRR接口,使得随机查询变为顺序查询,提高join连接效率。
5. read_rnd_buffer
用于Innodb随机读的,做MRR
MRR (Multi-Range Read Optimization):是一种将随机I/O变为顺序I/O的技术,将普通索引上的需要回表的主键id先存储在read_rnd_buffer缓存中,然后排序,最后将排好序的id集合去访问表中数据,这样就把随机I/O变为了顺序I/O。
6. tmp_table_size
如果sql排序或者分组时没有用到索引,则会使用该临时表空间
接下来就好好填补下buffer pool。
在磁盘上存储的就是页,将页加载到内存中就是buffer,即buffer pool是由一个个buffer组成的。
刚开始这些buffer都是空闲的,使用久了就会演变成3种类型:
free buffer:空闲buffer,就是未被使用的。
clean buffer:和磁盘上页的数据一样的buffer。
dirty buffer:buffer上的数据和磁盘上页的不一样。
当我们的mysql启动的时候呢,buffer pool中就只有一堆free buffer,用链表将这些buffer连接起来进行管理,这个链表就叫做free list,可以思考下,一个链表,会将buffer作为链表元素吗?
一个buffer占16kb,如果使用buffer作为链表元素,那么这个链表得多占内存啊,所以Innodb给每个buffer派发一个身份证,叫做控制块,控制块里边会存着表空间号,页号,缓存页在buffer pool中的地址等等能标识buffer的信息,控制块也是在buffer pool中,而且是在最前面的。所以我们这个架构图要改动下:
链表就是存储着这些控制块,控制块大概是buffer的5%大小,这部分空间不在buffer pool中,所以我们申请的内存实际上比配置参数的要大些。
有sql进来时,如果是select语句,则通过线程I/O将查询到的页加载到free buffer中,此时的free buffer就会变成clean buffer,
clean buffer也有链表进行管理,这个链表叫做lru list,该链表是依据最近最少使用淘汰策略进行管理的,同时也是起着缓存的作用。
看上图:
既然lru list是有缓存的作用,查询sql的时候会将来这边查找,但是Innodb需要将链表遍历一边吗?
很显然不能这么干
缓存页的哈希检索:
Innodb通过将表空间号+页号作为key,缓存页作为value,建立一个哈希表,可以通过哈希表来判断一个页是否已经被加载进内存中
q:lru list做缓存,肯定是缓存热点数据,经常要访问的,那么相信大家都有这样的疑惑:你是不是经常写select * from table;这样的语句,打比方lru list有2000个页,那条语句却能查出1900甚至2900个页的数据,热点数据不就全没了吗?怎么办,怎么办,急,在线等!
innodb会将数据分为冷热数据,热数据会放在lru list链表的前面,冷数据会放在链表的后面,
mysql> show variables like '%innodb_old_block%';
可以看到innodb_old_blocks_pct的参数是37,表示分配给冷数据的页占lru list的37%,整个链表的3/8,通过select *语句查询出来的页都存放在lru list链表的尾部。如下图分布
我们做全表扫描的时候,很多数据其实是不需要的,基本都是短时间内访问完就不访问的,但是在mysql里,有一个这样的约定,每访问一次页就算一次访问数据,那么一整个页的数据都扫描完,那岂不是一条数据短时间内被频繁访问,但是这样的数据我们不能把它们当初热数据,所以mysql针对此有一个策略:在某个时间范围内多次访问数据也只当是访问一次,这个时间间隔是由innodb_old_blocks_time参数决定的,即上边查询出来的参数,值是1000毫秒。这样就能有效的预防全表扫描造成的缓存失效。
进一步优化缓存:
想想,因为buffer入链表是用头插法的,如果一个尾巴的热点数据被访问到就要将它插入到头部是不是不太合理(为哈需要插入头部?在末尾的数据是有可能被淘汰的,所以尾部的数据被访问到就要保持数据的热度)?所以mysql将热数据分为2部分,前部分占3/4,后部分占1/4,只有访问到后部分的数据才会将数据查到头节点,这样就避免了频繁调整链表,提升性能。
当有update、insert、delete语句过来的时候,那必然会导致buffer中的数据与磁盘页中的数据不一致,这时的clean buffer就会变成dirty buffer,关于dirty buffer,Innodb也使用一个链表来管理它,它叫做flush list。
这些脏数据既然是我们的需要更新到DB的,那必然不能长时间停留在内存中。
mysql后台会有专门的IO线程去刷数据到磁盘中,这个下文再说,在我们修改数据的时候,是将clean buffer变为dirty buffer的,所以在lru list链表中是有可能存储脏数据的:
在多线程的情况下呢,mysql修改链表数据是需要上锁的,这时候可以考虑使用多个buffer pool实例,这样就互不干扰了,可以通过参数innodb_buffer_pool_instances来设置,实例的大小就是将之前的一个实例进行均分。
可能大家都注意到了mysql给的架构图里有个change buffer和log buffer,那么这两个是干嘛用的呢?
先不管change buffer,我们来看看log buffer,其实我们应该叫它为redo log buffer,就是用来记录redo log文件刷盘前的事务改动的数据,可以使用redo log来做数据恢复,这里就不讨论redo log了,在我们执行完update,insert,delete语句后,数据改动在redo log这,我们需要怎么刷新数据到磁盘中才能保证数据的不丢失呢?
redo log的刷盘条件有3种:
插入缓存(change buffer),两次写(double write),自适应哈希索引(adaptive hash index)是Innodb的三大特性,这些特性能够让Innodb存储引擎有更好的性能和可靠性。
最影响数据库性能的就是IO问题了,而插入缓存就是将随机IO变为顺序IO,提高I/O效率。它的原理就是判断在普通索引上的DML语句是否在缓存buffer中,如果在则直接插入,否则就保留在change buffer中,将多个DML语句合并在一起,然后一起插入,提高了普通索引的插入性能。
对于change buffer有两个参数:
innodb_change_buffer_max_size 是指占buffer pool的比例,默认是25%。
innodb_change_buffering 是指change buffer缓存的数据类型,有以下几种:
插入缓存是提高普通索引的插入性能,而两次写则是提高数据写入的安全性。 所谓的double write,是分为两次写操作,而double write也有个专门的存储区域,在 MySQL 8.0.20 之前,双写缓冲存储区位于InnoDB系统表空间中。从 MySQL 8.0.20 开始,双写缓冲区存储区位于双写文件中。
在buffer中的数据要刷数据入磁盘中时,会先将数据写到双写缓冲区中,然后才从双写缓冲区中将数据写入磁盘数据文件中。虽然写了两次,但是并不需要双倍的I/O消耗,因为在第一次写的时候,会在双写缓冲区中形成一个大顺序写的块,所以在第二次写的时候只需要一次操作系统的fsync()调用,即两次写需要的是 一倍的I/O + 一次fsync()调用。
之所以有双写缓冲区的存在,是为了保证写入操作系统中的页数据不完整时,这时候mysql实例宕机了,则会造成页数据的不完整,这时候就会丢失掉这个页的数据,虽然redo log可以恢复数据,但是恢复不了缺失页的数据,所以就需要双写缓冲区中该页的完整数据,先使用双写缓冲区的数据恢复该页的数据,然后使用redo log恢复全部的数据。
自适应哈希索引是Innodb中的一种自发性行为机制,Innodb会检测到如果某查询能够通过哈希索引来提高查询效率的话,则会自动建立哈希索引,可以通过一下参数innodb_adaptive_hash_index来控制:
我们还会看到innodb_adaptive_hash_index_parts参数,因为自适应哈希索引是分区的,这个参数就是分区数,这是为了减少索引竞争带来的性能消耗。
慢慢的丰富了架构图如下:
任何程序都少不了我们的光荣劳动人民 - 线程,Innodb存储引擎也是多线程工作的,下面就来介绍在这些幕后默默付出的工作线程:
就先到吧。
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。