Geek漫游指南

MySQL-InnoDB引擎介绍

date
slug
mysql-innodb-engine -introduction
author
status
Public
tags
技术分享
summary
type
Post
thumbnail
category
💻 Backend
updatedAt
Oct 29, 2023 10:08 AM
🌑
SQL 优化

InnoDB

左边是内存结构,右边是磁盘结构
左边是内存结构,右边是磁盘结构

Buffer Pool

Buffer Pool是InnoDB的一块缓存表和索引数据的内存区域,用于提高访问速度。一般会分配百分之80%的物理内存
SELECT @@innodb_buffer_pool_size/1024/1024/1024;
 
Note
The buffer pool size must be equal to or a multiple of innodb_buffer_pool_chunk_size* innodb_buffer_pool_instances.
Changing those variable settings requires restarting the server.

效率

为了应对大容量的读取操作,Buffer Pool将多行记录划分到了Page(缓冲页)中。
 

内部管理

为了有效应对缓冲管理,Buffer Pool使用了了链表页的结构。缓冲池使用 LRU 算法的变体作为列表进行管理。在缓冲中很少使用的数据会使用LRU算法移除。
  • New Sublist中的Head块是最近访问的新页(Young)
  • Old Sublist中的Tail块是最近访问较少的页
Buffer Pool 链表页结构
Buffer Pool 链表页结构
  1. LRU算法将经常用的页保留在New Sublist,不常用的页会在Old Sublist。Old Sublist中的页等待被淘汰。
  1. 新的缓冲页将进入缓冲链表页的中间节点位置(Midpoint Insertion),该节点为New Sublist尾节点和OldSublist头节点的边界。
  1. 一个缓冲页由一个用户发起操作,比如SQL Query或者InnoDB自动发起的预读(read-ahead)操作。
  1. 在Old Sublist访问一个缓冲页会将这个页标记为”Young“,然后它将移动到New Sublist的Head中。移动的触发时机取决于是用户操作还是InnoDB的预读。如果一个页是因为用户发起的操作读取的,在第一次访问这个缓存页后,会被立即移动到New Sublist中的Head中。而如果这个缓页是因为预读操作读取的,则第一次访问之后不会立即发生移动,可能这个页面在被LRU淘汰之前根本都不会发生。
  1. 随着数据库的运行,缓冲池中未被访问的页会通过向列表尾部移动 “老化”(即被淘汰的概率加大)。New Sublist的页会随着其他页的更新而被老化。Old Sublist的页也会随着插到中间点的页而老化。最终,一个未使用的页面到达Old Sublist的尾部并被淘汰。
 
默认情况下,通过SQL查询读取的页面会立即移动到New Sublist中,这意味着它们在缓冲池中的停留时间将会很长。一个mysqldump操作或者一个不带where条件的查询句引起的全表扫描,会将大量的数据加载到缓冲池中,并且淘汰相等数量的就数据。即使这些新加载进来的大量数据以后从未被使用。 类似地,由后台进程预读取加载到缓冲池中的页,即使只被访问过一次,这些页都会移动到New Sublist的Head中。这些场景都会将经常使用的页推向Old Sublist。
 
查看Buffer Pool页数: show status like'innodb_buffer_pool_pages_data';
 
  1. Innodb_buffer_pool_read_ahead:通过预读(后台线程)读取INNODB缓冲池中的数据页数
  1. innodb_buffer_pool_read_ahead_evicted:预读数据页清理的页面未查询访问、无效预读页面

InnoDB预读操作

一种I/O请求,它将一组缓冲页异步读取到Buffer Pool里,以防止这些页将很快被用到。
这个请求会在一个范围内包含所有页面。InnoDB以64个Page为一个extent。而page和extent是接下来两种预读算法的基本单位。
 
预读有两种模式
  1. 线性读取 (linear read-ahead)
    1. 使用extent单位,基于缓冲池中按顺序访问的页面预测很快可能需要哪些页面的技术,一个extent中被顺序读取的page超过了threshold,InnoDB将会自动异步读取下一个extent到Buffer Pool中。
  1. 随机读取 (random read-ahead)
    1. 使用extent中page为单位,它会根据Buffer Pool已经存在的缓冲页来预测哪些缓冲页很快会被需要用到,而不管这些页面的读取顺序如何。如果在缓冲池中找到来自同一范围的 13 个连续页面,则 InnoDB 异步发出请求以预取该范围的剩余页面。该功能5.6以后是默认关闭的, 要启用此功能,请将配置变量 innodb_random_read_ahead 设置为 ON。
 
线性预读方法有一个重要的变量,用于控制是否将下一个extent预读到缓冲池中,并且通过使用配置参数innodb_read_ahead_threshold来控制触发 InnoDB 执行预读操作的时间。
如果某个extent中的顺序读取页面超过或等于参数变量,InnoDB会将下一个扩展数据块异步读取到缓冲池中,Innodb_read_ahead_阈值可以设置为0-64的任意值(因为一个extent中只有64页),默认值为56,该值越高,访问模式检查越严格。
 
Mysql>Show variables like 'Innodb_read_ahead_threshold';+-----------------------------+-------+|Variable_name|Value|+-----------------------------+-------+|Innodb_read_ahead_threshold| About |+-----------------------------+-------+
 
随机预读是指当在缓冲池中找到同一扩展数据块中的某个页面时,InnoDB 会将该扩展数据块中的剩余页面读取到缓冲池中。
Mysql>Show variables like 'Innodb_random_read_ahead';+--------------------------+-------+|Variable_name|Value|+--------------------------+-------+|Innodb_random_read_ahead| OFF |+--------------------------+-------+
 
你可以通过显示引擎 InnoDB 状态来显示统计信息\g
mysql> show engine InnoDB status\g -- -------------------- BUFFER POOL and MEMORY -- -------------------- ... Pages read ahead 0.00 / s, evicted without access 0.00 / s, Random read ahead 0.00 / s ...
 
通过两个状态值评估预读算法的有效性
Mysql>Show Global Status like '%read_ahead%';+---------------------------------------+-------+|Variable_name|Value|+---------------------------------------+-------+|Innodb_buffer_pool_read_ahead_rnd| 0 ||Innodb_buffer_pool_read_ahead| 2303 ||innodb_buffer_pool_read_ahead_evicted| 0 |+---------------------------------------+-------+3Rowsinch Set(0.01Sec
 
 
预读失败问题可以引申到缓冲池污染问题,InnoDB 采用时间窗口(Time Window)机制解决缓冲池污染问题:对于 Old SubList 中的数据页,必须在 Old SubList 中停留到达指定时间之后再次被访问到,才能转移到 New SubList 中,默认窗口大小是 1s。
 
 
每个页面至少应当存储两个记录,如果一个页面的大小为16k,那么一条记录最大不得超过8k,事实上应当更小,因为还有文件头、页头、文件尾等部分需要存储。如果变长的数据过长,导致索引页无法容纳两条记录,会将长度过长的字段的内容存储到外部存储页(blob page)。
 
总结:
新的缓冲页会被用户发起的SQL查询或者预读操作插入到LRU列表中间的位置,当它被第一次访问的时候,会移动到Head块中。因为从未访问过的页面永远不会进入到LRU头部,并且LRU使用最小化(非严格的LRU淘汰机制)缓冲链表的方法淘汰掉最少使用的缓冲页数据。
 
 
 
 
 

Change Buffer

notion image
Change Buffer是Buffer Pool其中一块区域。当辅助索引不存在Buffer Pool中时(如果存在则直接在Buffer Pool中更新),它会缓存住修改数据页的操作。将来这个数据页被加载到Buffer Pool时,Change Buffer会将修改合并到缓冲页中。
相比较将一个一个将页刷入到磁盘,Change Buffer将定期在系统空闲时间将一系列索引值写入到磁盘,这样可以避免从磁盘将辅助索引页读入Buffer Pool所需的大量随机I/O访问。
在内存中,Change Buffer占了Buffer Pool的一部分(默认25%)。在磁盘中则是系统表空间的一部分。
如果辅助索引包含降序索引列或主键包含降序索引列,则不支持Change Buffer。
 

合并过程触发

  1. 访问缓冲页
  1. 系统后台定期merge
  1. 数据库slowdown
 

为什么Change Buffer只能应用于普通索引,而不是唯一索引?

  1. 对于唯一索引来说,需要有一个判断插入数据的列是否唯一的过程,而这个过程只能在内存中做。这个过程不可避免的需要从磁盘读取数据到Buffer Pool中。
  1. 如果是普通索引,没有这个判断过程,所以不需要把数据页加载到Buffer Pool里。
将数据从磁盘读入内存涉及随机 IO 的访问,是数据库里面成本最高的操作之一。change buffer因为减少了随机磁盘访问,所以对更新性能的提升是会很明显的。
 

Change Buffer 使用场景

  1. 普通索引
  1. 写多读少
如果数据写入之后需要被立刻访问,那它就不适合Change Buffer,因为这将触发merge过程。