1.缓冲池(buffer pool)
缓冲池是主内存中的一个区域,用于在访问表和索引数据时对其进行缓存。缓冲池允许直接从内存访问常用数据,从而加快处理速度。在专用服务器上,最多 80% 的物理内存通常分配给缓冲池。 InnoDB 为了提高大容量读取操作的效率,缓冲池被划分为可能包含多行的页。为了提高缓存管理的效率,缓冲池被实现为页面的链接列表;很少使用的数据使用最近最少使用的 (LRU) 算法的变体从缓存中老化。
1.缓冲池LRU 算法
缓冲池使用 LRU 算法的变体作为列表进行管理。当需要空间来向缓冲池添加新页面时,将逐出最近最少使用的页面,并将新页面添加到列表的中间。此中点插入策略将列表视为两个子列表:
①在头部,最近访问过的新("年轻")页面的子列表
②在尾部,是最近访问较少的旧页面的子列表
默认情况下,该算法按如下方式运行:
①3/8 的缓冲池用于存储旧的子列表。
②列表的中点是新子列表的尾部与旧子列表的头部连接处。
③将页面读入缓冲池时,它最初将其插入到中点(旧子列表的头部)。可以读取页,因为用户启动的操作(如 SQL 查询)需要该页,或者作为自动执行的预读操作的一部分读取页。
④访问旧子列表中的页面会使它"年轻",将其移动到新子列表的头部。如果由于用户启动的操作需要该页而被读取,则第一次访问会立即发生,并且该页将变为年轻。如果由于预读操作而读取了页面,则不会立即进行第一次访问,并且在逐出页面之前可能根本不会发生。
⑤当数据库运行时,缓冲池中未被访问的页会向列表尾部移动而"老化"。新旧子列表中的页面都会随着其他页面变为新页面而老化。旧子列表中的页面也会随着页面插入到中点而老化。最终,仍未使用的页面到达旧子列表的尾部并被逐出。
2.缓冲池的配置
理想情况下,您可以将缓冲池的大小设置为尽可能大的值,从而为服务器上的其他进程留出足够的内存,以便在不进行过度分页的情况下运行。缓冲池越大,就越像内存中的数据库,从磁盘读取一次数据,然后在后续读取期间从内存访问数据。
使用 Innodb_Buffer_pool_size 参数配置缓冲池大小。
3.缓冲池监控
我们可以使用命令 SHOW ENGINE INNODB STATUS 查看缓冲池的状态信息。
mysql> SHOW ENGINE INNODB STATUS\G
4.InnoDB 缓冲池参数意义
名字 |
描述 |
Total memory allocated |
为缓冲池分配的总内存(以字节为单位) |
additional pool allocated |
为其他池分配的总内存(以字节为单位) |
Dictionary memory allocated |
为数据字典分配的总内存(以字节为单位)。InnoDB |
Buffer pool size |
分配给缓冲池的总大小(以页为单位) |
Free buffers |
缓冲池空闲列表的总大小(以页为单位) |
Database pages |
缓冲池 LRU 列表的总大小(以页为单位) |
Old database pages |
缓冲池旧 LRU 子列表的总大小(以页为单位) |
Modified db pages |
缓冲池中当前修改的页数 |
Pending reads |
等待读入缓冲池的缓冲池页数 |
Pending writes LRU |
缓冲池中要从 LRU 列表底部写入的旧脏页数 |
Pending writes flush list |
检查点期间要刷新的缓冲池页数 |
Pending writes single page |
缓冲池中挂起的独立页写入次数 |
Pages made young |
在缓冲池 LRU 列表中年轻的总页数(移至"新"页的子列表的开头) |
Pages made not young |
在缓冲池 LRU 列表中未变年轻的页面总数(保留在"旧"子列表中但未变为 年轻的页面) |
youngs/s |
对缓冲池 LRU 列表中导致页面变年轻的旧页面的每秒平均访问 |
non-youngs/s |
对缓冲池 LRU 列表中导致页面不年轻的旧页面的每秒平均访问量 |
Pages read |
从缓冲池读取的总页数 |
Pages created |
在缓冲池中创建的总页数 |
Pages written |
从缓冲池写入的总页数 |
reads/s |
每秒读取缓冲池页数的每秒平均数 |
creates/s |
每秒创建的平均缓冲池页数 |
writes/s |
缓冲池页每秒写入的平均数 |
Buffer pool hit rate |
从缓冲池读取的页与从磁盘存储读取的页的缓冲池页命中率 |
young-making rate |
页面访问导致页面年轻的平均命中率 |
not (young-making rate) |
页面访问的平均命中率不会导致页面变年轻 |
Pages read ahead |
每秒预读操作的平均值 |
Pages evicted without access |
未从缓冲池访问而被逐出的页面的每秒平均值 |
Random read ahead |
随机预读操作的每秒平均值 |
LRU len |
缓冲池 LRU 列表的总大小(以页为单位) |
unzip_LRU len |
缓冲池的长度(以页为单位)unzip_LRU列表 |
I/O sum |
访问的缓冲池 LRU 列表页的总数 |
I/O cur |
在当前时间间隔内访问的缓冲池 LRU 列表页的总数 |
I/O unzip sum |
缓冲池unzip_LRU解压缩页的总数 |
I/O unzip cur |
缓冲池unzip_LRU的总数,列出在当前时间间隔内解压缩的页数 |
2.修改缓冲区(change buffer)
修改缓冲区早期版本中叫做插入缓冲区。当二级索引(非主键索引)页不在缓冲池中时,它会缓存这些页的更改。缓冲的更改可能由INSERT、UPDATE或DELETE操作 导致,稍后在页面由其他读取操作加载到缓冲池中时合并。
与聚簇索引(主键索引)不同,二级索引通常是非唯一的,并且插入到二级索引中以相对随机的顺序发生。同样,删除和更新可能会影响不在索引树中相邻的二级索引页。当受影响的页被其他操作读入缓冲池时,稍后合并缓存的更改可避免从磁盘将二级索引页读入缓冲池所需的大量随机访问 I/O。
在系统基本空闲或关机缓慢期间运行的清除操作会定期将更新的索引页写入磁盘。清除操作可以比立即将每个值写入磁盘更有效地写入一系列索引值的磁盘块。
当有许多受影响的行和大量二级索引需要更新时,更改缓冲区合并可能需要几个小时。在此期间,磁盘I/O 会增加,这可能会导致磁盘绑定查询的速度显著降低。更改缓冲区合并也可能在事务提交后继续发生,甚至在服务器关闭和重新启动之后。
修改缓冲区也分为内存区域和磁盘区域两部分,在内存中,修改缓冲区占用缓冲池的一部分。在磁盘上,修改改缓冲区是系统表空间的一部分,当数据库服务器关闭时,将在其中缓冲索引更改。
1.change buffer 控制参数
修改改缓冲区中缓存的操作类型由 innodb_change_buffering 变量控制。
命令行格式 |
--innodb-change-buffering=value |
系统变量 |
innodb_change_buffering |
范围 |
全局 |
动态 |
是 |
类型 |
枚举 |
默认值 |
all |
有效值 |
none inserts deletes changes purges all |
配置更改缓冲区大小:
innodb_change_buffer_max_size 参数允许将更改缓冲区的最大大小配置为缓冲池总大小的百分比。
默认情况下,设置为 25。最大设置为 50。修改操作多的场合考虑增大此参数,查询多的场合减小此参数。
2.监控修改缓冲区
也可以使用 SHOW ENGINE INNODB STATUS 命令查看修改缓冲区状态。
参见:INSERT BUFFER AND ADAPTIVE HASH INDEX部分内容。
3.自适应hash索引
哈希(hash)是一种非常快的查找方法,在一般情况下这种查找的时间复杂度为O(1),即一般仅需要一次查找就能定位数据。而B+树的查找次数,取决于B+树的高度,在生产环境中,B+树的高度一般为3~4层,故需要3~4次的查询。InnoDB存储引擎会监控对表上各索引页的查询。如果观察到建立哈希索引可以带来速度提升,则建立哈希索引,称之为自适应哈希索引(Adaptive Hash Index,AHI) AHI是通过缓冲池的B+树页构造而来,因此建立的速度很快,而且不需要对整张表构建哈希索引。 InnoDB存储引擎会自动根据访问的频率和模式来自动地为某些热点页建立哈希索引。
自适应哈希索引由 innodb_adaptive_hash_index 变量控制。也可以服务器启动时由 --skip-innodb- adaptive-hash-index 关闭。自适应哈希索引在查询过程中会被自动启用,将索引值转换为某种指针来加快查询速度。 InnoDB引擎自动监控索引搜索,如果注意到查询可以从构建哈希索引中受益,则会自动执行此操作。
4.Log Buffer重做日志缓冲
日志缓冲区是保存要写入磁盘上日志文件的数据的内存区域。
①日志缓冲区大小由 innodb_log_buffer_size 变量定义。默认大小为 16MB。大型日志缓冲区使大型事务能够运行,而无需在事务提交之前将重做日志数据写入磁盘。因此,如果您有更新、插入或删除许多行的事务,则增加日志缓冲区的大小将节省磁盘 I/O。
②日志缓冲区的内容会定期刷新到磁盘。 innodb_flush_log_at_timeout 变量控制日志刷新频率,默认每1s刷新一次缓冲区。
③innodb_flush_log_at_trx_commit 变量控制如何将日志缓冲区的内容写入和刷新到磁盘。
1.Redo log的作用
Redo log的主要作用是用于数据库的崩溃恢复。
redo log记录数据库的每个动作,以便于当数据库崩溃后可以根据redo log恢复数据库的状态。记录的动作包括新建数据页、数据页的修改内容、写undo log等。
redo log同样是保证数据持久性的重要环节。
2.Redo log的执行流程
1. 修改数据时,先从磁盘上读取数据页到buffer pool中,如果此数据页在内存中无需此操作。
2. 生成 写 undo log 操作 的redo log,此log记录到log buffer中。
3. 执行写undo log操作,记录修改数据页的逆操作,以便执行回滚操作。
4. 生成修改数据页的redo log,此log记录到log buffer中。
5. 修改buffer pool中数据页。此操作造成内存中的数据页和磁盘中的数据页不一致,从而形成脏页。
6. 数据页修改完毕后,执行commit操作。
7. 将redo log buffer中的内容flush到磁盘中,如果落盘成功则commit成功,如果落盘失败则commit失败,需要执行回滚操作。
3.redo log如何保证事务的持久性
InnoDB是事务型的存储引擎,其通过Force Log at Commit 机制实现事务的持久性。即当事务提交时,先将 redo log buffer 写入到 redo log file 进行持久化,待所有log成功写入文件系统后commit操才算完成。这种作法也被称为 Write-Ahead Log(预先日志持久化),在持久化一个数据页之前,先把内存中数据页的修改以日志形式进行持久化。
由于redo log打开文件时没有使用 O_DIRECT 选项,因此redo log执行写入时,先写到文件系统缓存。为了确保redo log写入到磁盘,必须进行一次 fsync操做。fsync是一种系统调用操做,其fsync的效率取决于磁盘的性能,所以磁盘的性能也影响了事务提交的性能,也就是数据库的性能。
一般如果在Linux内核中读写一个文件,其IO流程都需要经过Kernel内的page cache层次,若想要使用自己开发的缓存系统,那么就可以在打开这个文件的时候,对该文件加以O_DIRECT的标志位,这样一来就可以让程序对该文件的IO直接在磁盘上进行,从而避开了Kernel的page cache,进而对IO流程里的块数据进行拦截,让其流入到自己开发的缓存系统内。
上面提到的Force Log at Commit机制就是靠InnoDB存储引擎提供的参数
innodb_flush_log_at_trx_commit 来控制的,该参数能够控制 redo log刷新到磁盘的策略,设置该参数值也能够容许用户设置非持久性的状况发生,具体以下:
当属性值为0时,事务提交时,不会对重做日志进行写入操作,而是等待主线程按时写入每秒写入一次;
当属性值为1时,事务提交时,会将重做日志写入文件系统缓存,并且调用文件系统的fsync,将文件系统缓冲中的数据真正写入磁盘存储,确保不会出现数据丢失;
当属性值为2时,事务提交时,也会将日志文件写入文件系统缓存,但是不会调用fsync,而是让文件系统自己去判断何时将缓存写入磁盘。
日志的刷盘机制如下图所示:
innodb_flush_log_at_trx_commit 是InnoDB性能调优的一个基础参数,涉及InnoDB的写入效率和数据安全。当参数值为0时,写入效率最高,但是数据安全最低;参数值为1时,写入效率最低,但是数据安全最高;参数值为2时,二者都是中等水平。一般建议将该属性值设置为1,以获 得较高的数据安全性,而且也只有设置为1,才能保证事务的持久性。
4.重要参数总结
innodb_log_buffer_size:redo log buffer 的大小。
innodb_flush_log_at_timeout:控制 redo log buffer 写入磁盘的频率。默认每秒一次。
innodb_flush_log_at_trx_commit:当 commit 时将日志写入磁盘的行为
0:commit 不会将日志写入文件,等待定时刷新的操作再将日志写入磁盘。
1:commit 时,将日志写入操作系统缓存,再调用 fsync 操作,真正将数据写入磁盘(默认值)
2:commit 时,将日志写入操作系统缓存,不调用 fsync 操作,等待操作系统刷新缓冲区。
0、2 都有丢失数据的风险。1 是最安全,性能也最差的。