内存
InnoDB存储引擎是基于磁盘存储的,并将其中的记录按照页的方式进行管理。因此可将其视为基于磁盘的数据库系统(Disk-base Database)。在数据库系统中,由于CPU速度与磁盘速度之间的鸿沟,基于磁盘的数据库系统通常使用缓冲池技术来提高数据库的整体性能
缓存池
对于InnoDB存储引擎而言,其缓冲池的配置通过参数innodb_buffer_pool_size来设置。
show variables like "innodb_buffer_pool_size"
对于数据库中页的修改操作,则首先修改在缓冲池中的页,然后再以一定的频率刷新到磁盘上。这里需要注意的是,页从缓冲池刷新回磁盘的操作并不是在每次页发生更新时触发,而是通过一种称为Checkpoint的机制刷新回磁盘。同样,这也是为了提高数据库的整体性能。
(配图来自mysql innodb技术内幕 时间有点久了有些不同)
缓存池的组成
最新版本的MySQL(以MySQL 8为例)的内存缓存池,特别是InnoDB存储引擎中的Buffer Pool,是一个关键的内存结构,用于缓存数据和索引,以减少对物理磁盘的I/O操作。Buffer Pool主要包含以下组件:
-
索引页(Index Pages):
- 存储了InnoDB表的索引结构,包括主键索引(聚集索引)和辅助索引(非聚集索引)。
- 这些索引页被加载到Buffer Pool中,以加速对表中数据的查找和访问。
-
数据页(Data Pages):
- 存储了InnoDB表的实际数据行。
- 在InnoDB中,数据是按页存储的,每个数据页通常包含多行数据。
- 当需要读取或修改表中的数据时,相关的数据页会被加载到Buffer Pool中。
-
Undo页:
- 存储了旧版本的数据,用于支持事务的ACID属性中的隔离性(Isolation)和持久性(Durability)。
- 当执行一个事务时,对数据的修改不会立即生效,而是先记录在Undo页中。
- 如果其他事务需要读取被修改的数据,它可以通过Undo页来获取数据修改前的版本,从而实现多版本并发控制(MVCC)。
-
插入缓存(Insert Buffer):
- 是InnoDB中用于优化非聚集索引插入操作的一种机制。
- 当向一个包含非聚集索引的表中插入数据时,如果相关的索引页不在Buffer Pool中,InnoDB不会立即将索引键插入到索引页中,而是将其存储在插入缓存中。
- 当相关的索引页被加载到Buffer Pool时,插入缓存中的索引键会被合并并插入到索引页中。
-
自适应哈希索引(Adaptive Hash Index):
- 是InnoDB存储引擎的一个特性,用于自动根据访问模式创建哈希索引。
- 当某些索引值被频繁访问时,InnoDB会将这些索引值存储在自适应哈希索引中,以加速对这些值的查找。
- 自适应哈希索引是完全自动的,不需要用户手动创建或维护。
-
InnoDB的锁信息(Lock Information):
- InnoDB存储引擎使用锁来确保并发访问时的数据一致性和完整性。
- 在Buffer Pool中,InnoDB会维护锁信息,以跟踪哪些数据页或行被锁定,以及锁的类型(如共享锁或排他锁)。
-
数据字典:
- MySQL数据库启动时,会自动从硬盘中将系统表相关信息加载到Buffer Pool中。
- 有了数据字典,使用
SHOW INDEX
、SHOW TABLES
等相关命令就能查到表、索引相关的信息。
此外,Buffer Pool中的内存空间还被划分为多个固定大小的页(缓存页),并使用多种链表(如LRU链表、free链表、flush链表)来管理这些缓存页。这些链表和缓存页共同协作,以提供高效的数据访问和事务处理能力。
总的来说,最新版本的MySQL的内存缓存池(Buffer Pool)是一个复杂的内存结构,包含了多种组件和机制,以优化数据库的性能和并发处理能力。
从InnoDB 1.0.x版本开始,允许有多个缓冲池实例。每个页根据哈希值平均分配到不同缓冲池实例中。这样做的好处是减少数据库内部的资源竞争,增加数据库的并发处理能力。可以通过参数innodb_buffer_pool_instances来进行配置,该值默认为1。
mysql>SHOW VARIABLES LIKE'innodb_buffer_pool_instances'\G;
可以通过information_schema架构下的表INNODB_BUFFER_POOL_STATS来观察缓冲的状态,如运行下列命令可以看到各个缓冲池的使用状态:
SELECT POOL_ID,POOL_SIZE,FREE_BUFFERS,DATABASE_PAGES FROM INNODB_BUFFER_POOL_STATS\G;
在MySQL数据库中,除了InnoDB存储引擎的Buffer Pool这一重要的内存缓存之外,确实还存在其他类型的内存缓存。以下是一些主要的内存缓存:
-
Key Buffer(或Key Cache):
- 主要用于MyISAM存储引擎。
- 缓存MyISAM表的索引块,以加速索引的读写操作。
- 可以通过参数
key_buffer_size
来配置其大小。
-
Query Cache:
- 用于缓存SELECT查询的结果集。
- 当相同的查询再次执行时,可以直接从Query Cache中读取结果,而无需重新执行查询。
- 需要注意的是,从MySQL 8.0开始,Query Cache已被移除,因为它在某些情况下可能会导致性能问题。
-
Table Cache:
- 用于缓存表的文件描述符和表结构信息。
- 当需要访问一个表时,MySQL会首先检查该表是否已经在Table Cache中。
- 可以通过参数
table_open_cache
(或table_cache
,在旧版本中)来配置其大小。
-
InnoDB的Adaptive Hash Index Cache:
- 虽然前面已经提到了自适应哈希索引,但这里强调的是它所占用的缓存空间。
- 自适应哈希索引会根据访问模式自动创建,并占用一部分内存空间。
-
InnoDB的Log Buffer:
- 用于缓存InnoDB的重做日志(redo log)。
- 当事务提交时,重做日志会先被写入到Log Buffer中,然后再以一定的频率刷新到磁盘上的重做日志文件中。
- 可以通过参数
innodb_log_buffer_size
来配置其大小。
-
其他缓存:
- MySQL还包含了一些其他类型的缓存,如用于存储临时结果的临时表缓存、用于存储排序操作的排序缓存等。
- 这些缓存的大小和配置通常取决于具体的MySQL版本和配置参数。
需要注意的是,随着MySQL版本的更新和存储引擎的改进,某些缓存可能会被移除或替换为更高效的机制。因此,在配置和优化MySQL时,建议参考当前版本的官方文档和最佳实践。
此外,对于内存中的缓存管理,MySQL提供了一系列参数和工具来帮助数据库管理员进行监控和调整。例如,可以使用SHOW VARIABLES
命令来查看当前配置的缓存大小,使用SHOW STATUS
命令来监控缓存的使用情况,以及使用性能分析工具来识别和优化内存使用中的瓶颈。
缓存池的管理
LRU
数据库中的缓冲池是通过LRU(Latest Recent Used,最近最少使用)算法来进行管理的。即最频繁使用的页在LRU列表的前端,而最少使用的页在LRU列表的尾端。当缓冲池不能存放新读取到的页时,将首先释放LRU列表中尾端的页。
稍有不同的是InnoDB存储引擎对传统的LRU算法做了一些优化。在InnoDB的存储引擎中,LRU列表中还加入了midpoint位置。新读取到的页,虽然是最新访问的页,但并不是直接放入到LRU列表的首部,而是放入到LRU列表的midpoint位置。这个算法在InnoDB存储引擎下称为midpoint insertion strategy。在默认配置下,该位置在LRU列表长度的5/8处。midpoint位置可由参数innodb_old_blocks_pct控制
SHOW VARIABLES LIKE'innodb_old_blocks_pct'\G;
参数innodb_old_blocks_pct默认值为37,表示新读取的页插入到LRU列表尾端的37%的位置(差不多3/8的位置)。在InnoDB存储引擎中,把midpoint之后的列表称为old列表,之前的列表称为new列表。可以简单地理解为new列表中的页都是最为活跃的热点数据。
InnoDB存储引擎引入了另一个参数来进一步管理LRU列表,这个参数是innodb_old_blocks_time,用于表示页读取到mid位置后需要等待多久才会被加入到LRU列表的热端。因此当需要执行上述所说的SQL操作时,可以通过下面的方法尽可能使LRU列表中热点数据不被刷出
SET GLOBAL innodb_old_blocks_time=1000;
LRU列表用来管理已经读取的页,但当数据库刚启动时,LRU列表是空的,即没有任何的页。这时页都存放在Free列表中。当需要从缓冲池中分页时,首先从Free列表中查找是否有可用的空闲页,若有则将该页从Free列表中删除,放入到LRU列表中。否则,根据LRU算法,淘汰LRU列表末尾的页,将该内存空间分配给新的页。当页从LRU列表的old部分加入到new部分时,称此时发生的操作为page made young,而因为innodb_old_blocks_time的设置而导致页没有从old部分移动到new部分的操作称为page not made young。可以通过命令SHOW ENGINE INNODB STATUS来观察LRU列表及Free列表的使用情况和运行状态
可以通过表INNODB_BUFFER_PAGE_LRU来观察每个LRU列表中每个页的具体信息,例如通过下面的语句可以看到缓冲池LRU列表中SPACE为1的表的页类型:
mysql>SELECT TABLE_NAME,SPACE,PAGE_NUMBER,PAGE_TYPE FROM INNODB_BUFFER_PAGE_LRU WHERE SPACE=1;
InnoDB存储引擎从1.0.x版本开始支持压缩页的功能,即将原本16KB的页压缩为1KB、2KB、4KB和8KB。而由于页的大小发生了变化,LRU列表也有了些许的改变。对于非16KB的页,是通过unzip_LRU列表进行管理的.
在unzip_LRU列表中对不同压缩页大小的页进行分别管理。其次,通过伙伴算法进行内存的分配。例如对需要从缓冲池中申请页为4KB的大小,其过程如下:
1)检查4KB的unzip_LRU列表,检查是否有可用的空闲页;
2)若有,则直接使用;
3)否则,检查8KB的unzip_LRU列表;
4)若能够得到空闲页,将页分成2个4KB页,存放到4KB的unzip_LRU列表;
5)若不能得到空闲页,从LRU列表中申请一个16KB的页,将页分为1个8KB的页、2个4KB的页,分别存放到对应的unzip_LRU列表中。
可以通过information_schema架构下的表INNODB_BUFFER_PAGE_LRU来观察unzip_LRU列表中的页,如:
mysql>SELECT TABLE_NAME,SPACE,PAGE_NUMBER,COMPRESSED_SIZE FROM INNODB_BUFFER_PAGE_LRU WHERE COMPRESSED_SIZE <> 0;
在LRU列表中的页被修改后,称该页为脏页(dirty page),即缓冲池中的页和磁盘上的页的数据产生了不一致。这时数据库会通过CHECKPOINT机制将脏页刷新回磁盘,而Flush列表中的页即为脏页列表。需要注意的是,脏页既存在于LRU列表中,也存在于Flush列表中。LRU列表用来管理缓冲池中页的可用性,Flush列表用来管理将页刷新回磁盘,二者互不影响。Flush列表也可以通过命令SHOW ENGINE INNODB STATUS来查看.
information_schema架构下并没有类似INNODB_BUFFER_PAGE_LRU的表来显示脏页的数量及脏页的类型,但正如前面所述的那样,脏页同样存在于LRU列表中,故用户可以通过元数据表INNODB_BUFFER_PAGE_LRU来查看,唯一不同的是需要加入OLDEST_MODIFICATION大于0的SQL查询条件
mysql>SELECT TABLE_NAME,SPACE,PAGE_NUMBER,PAGE_TYPE FROM INNODB_BUFFER_PAGE_LRU WHERE OLDEST_MODIFICATION>0;
重做日志缓存
重做日志信息先放入到这个缓冲区,然后按一定频率将其刷新到重做日志文件。重做日志缓冲一般不需要设置得很大,因为一般情况下每一秒钟会将重做日志缓冲刷新到日志文件,因此用户只需要保证每秒产生的事务量在这个缓冲大小之内即可。该值可由配置参数innodb_log_buffer_size控制,默认为8MB:
mysql> SHOW VARIABLES LIKE'innodb_log_buffer_size'\G;
在通常情况下,8MB的重做日志缓冲池足以满足绝大部分的应用,因为重做日志在下列三种情况下会将重做日志缓冲中的内容刷新到外部磁盘的重做日志文件中。
❑Master Thread每一秒将重做日志缓冲刷新到重做日志文件;
❑每个事务提交时会将重做日志缓冲刷新到重做日志文件;
❑当重做日志缓冲池剩余空间小于1/2时,重做日志缓冲刷新到重做日志文件。
在InnoDB存储引擎中,对内存的管理是通过一种称为内存堆(heap)的方式进行的。在对一些数据结构本身的内存进行分配时,需要从额外的内存池中进行申请,当该区域的内存不够时,会从缓冲池中进行申请。例如,分配了缓冲池(innodb_buffer_pool),但是每个缓冲池中的帧缓冲(frame buffer)还有对应的缓冲控制对象(buffer control block),这些对象记录了一些诸如LRU、锁、等待等信息,而这个对象的内存需要从额外内存池中申请。因此,在申请了很大的InnoDB缓冲池时,也应考虑相应地增加这个值。
知识来源
《mysql 技术内幕:innoDB存储引擎》
文心一言