本文主要基于《高性能MySQL》


文章目录

  • 1、线程缓存
  • 2、表缓存
  • 3、InnoDB数据字典
  • 4、InnoDB缓冲池
  • 5、日志缓冲(redo日志)
  • 6、InnoDB表空间
  • (1)innodb_data_home_dir
  • (2)系统表空间
  • (3)独立表空间
  • (4)表空间碎片
  • (5)回滚日志清理线程
  • (6)双写缓冲
  • (7)插入缓冲


1、线程缓存

线程缓存用于缓存连接线程,保存那些当前没有使用但是准备为后面新的连接服务的线程。当有创建一个新连接时,如果缓存中有可用线程,那么从缓存中取出该线程使用,当连接关闭时,如果缓存中有空间,那么就将该线程放入缓存,如果没有空间,便销毁线程。缓存中每个线程大概占内从256KB。
thread_cache_size变量用于设置mysql可以爆出在缓存中的线程数。

2、表缓存

表缓存用于缓存打开的表对象,该对象包含了对应的表文件.frm的解析结果。查询某张表时,如果表缓存中已经有该表了,那么可以省去解析.frm文件时间。
InnoDB将表缓存分成了两部分:打开表的缓存和表定义缓存,分别由参数table_open_cache和table_definition_cache控制大小,如果发现状态值opened_tables不断在增大,说明打开表缓存不够大,不过临时表的创建和删除也会造成opened_tables不断增大。table_definition_cache定义了内存中可打开的表结构数量,可以简单的理解为缓存打开的frm文件数量。
与线程缓存类似,缓存一个表对象占用的空间也不大。

3、InnoDB数据字典

InnoDB数据字典也就是InnoDB自己的表缓存,线程缓存和上面介绍的表缓存是mysql服务器层的。如何配置数据字典大小我没有找到相关参数。
当InnoDB打开一个表,就增加一个对应的对象到数据字典。每个张可能占用4KB或者更多的内存。当表关闭时也会从数据字段移除它们。所以随着时间推移,数据字段占用的空间会越来越大。不过一般情况下,这不会有什么问题,除非表很多,比如几千上万张表,这才可能会出问题。

4、InnoDB缓冲池

InnoDB缓冲池缓存的内容有:缓存索引、行数据、自适应哈希索引、插入缓冲、锁、数据字典等,InnoDB还使用缓冲池帮助延迟写入,总之InnoDB严重依赖缓冲池,我们需要给缓冲池分配足够的空间。
但这不是说要给缓存池分配很大的空间,空间越大缓冲池里面的脏页数量也会越多,会造成关机的时间越长,同样的空间越大预热也越慢,会造成启动越慢。
我们可以监控缓冲池的使用情况来合理的分配内存,通过配置innodb_buffer_pool_size设置缓冲池的大小。
当缓冲池中的脏页达到一定比例时,mysql会启动刷脏页的操作,这个比例通过参数innodb_max_dirty_pages_pct来指定,该参数最小值为0,最大值为99.99,默认值为 75,该参数只控制脏页百分比,并不会影响刷脏页的速度。

5、日志缓冲(redo日志)

大家都知道数据库事务提交时,事务对表数据的修改不会立马更新到磁盘上对应的数据页,而是将更改的内容记录到事务日志中,并保存到磁盘上,这样将事务提交时的随机写变成了对日志文件的顺序写。下面介绍一下InnoDB的事务日志。
InnoDB的日志文件是环形写的,当写到最后一个文件尾部的时候,会重新转到第一个文件的开头继续写,但是不会覆盖还没应用到磁盘上数据文件的日志记录。InnoDB后台会启动一个线程定时将这些更改写到磁盘上的数据文件,这个线程可以批量组合写入,使得数据以近乎顺序性的写入数据文件。这样本来是随机IO就变成了几乎顺序的IO。
日志文件的大小由innodb_log_file_size和innodb_log_file_in_group两个参数控制,innodb_log_file_in_group用于控制日志文件的个数,默认是2,innodb_log_file_size用于控制每个文件的大小,默认是5M,总的日志文件大小是innodb_log_file_size*innodb_log_file_in_group。可能大家会认为文件才5M太小了,这里需要说明,innodb_log_file_size太大不好,太小也不好。太大了,一旦数据库发生崩溃,恢复时数据库需要做大量的工作,影响恢复时间;太小会使得日志没有空间写入,必须等待将日志中的更改应用到日志文件,这会影响增删改操作。一般的如果机器非常繁忙,有大量的事务操作,需要增大innodb_log_file_size的值。修改该值,需要重启机器。
当InnoDB更改数据时,会写一条变更记录到内存的日志缓冲区,当以下三个条件任一个满足的时候,InnoDB都会刷新日志缓冲区到磁盘日志文件:缓存满、事务提交、每一秒钟。innodb_log_buffer_size用于设置日志缓冲区的大小,默认是1M,增大缓冲区,可以减少IO次数。通常不需要把innodb_log_buffer_size设置的很大,推荐1M-8M。show innodb status中的变量innodb_os_log_written可以监控innodb对日志文件写入了多少数据。参数innodb_flush_log_at_trx_commit用来控制日志缓冲刷新到磁盘的频繁程度。有三个值:

  • 0表示每一秒刷新一次,但是事务提交时不做任何事;
  • 1表示每次事务提交都刷新缓冲到文件,这是默认值,该值可以保证不丢失任何已经提交的事务;
  • 2表示每次提交事务会写缓冲到文件,但是不刷新,然后每秒钟刷新一次。

这里注意三个值之间的区别。

6、InnoDB表空间

InnoDB表空间指的是在磁盘上的一个或多个文件,这些文件包含的内容有:表数据、索引、回滚日志、插入缓冲、双写缓冲等。
分为两类表空间:独立表空间和系统表空间(也叫作共享表空间)。

(1)innodb_data_home_dir

用于设置表空间文件在磁盘上的目录位置,系统表空间和独立表空间都在该目录下,如果不设置innodb_data_home_dir,默认系统表空间和独立表空间都在mysql目录下的data目录下,比如Windows系统,文件在盘符\ProgramData\MySQL\MySQL Server 5.6\data\目录下。

(2)系统表空间

默认情况下,系统表空间指的是磁盘上ibdata1文件,该文件存储双写缓冲,回滚日志,数据字典,插入缓冲等,可以通过innodb_data_file_path配置该文件的文件名和大小。
innodb_data_file_path还可以指定多个文件,格式如下:文件名1:1G;文件名2:1G;文件名3:2G:autoextend。
如果不设置innodb_data_home_dir,可以在innodb_data_file_path中指定路径和文件名,甚至把文件设置到不同的磁盘上。如果配置了多个文件,那么innodb写完一个系统表空间的文件后,再继续写下一个。上面事例中最后的autoextend表示当超过了允许的文件大小,innodb可以自动扩展。如果系统表空间是自动扩展的,也可以通过max限制表空间最大可以扩展到多少,这里有一点注意,系统表空间一旦扩展了,就无法回收,如果想要回收表空间,只能导出数据,关闭ysql,删除文件,重启然后重新导入。

(3)独立表空间

innodb_file_per_table选项可以让每张表使用一个文件,文件名为“表名.ibd”,这里的ibd文件便是独立表空间。innodb_file_per_table有两个值:ON,OFF,默认是ON。
如果设置innodb_file_per_table为OFF,那么InnoDB不会生产独立表空间文件,所有的表数据和索引都会放到innodb_data_file_path指定的文件里面,这会造成当删除表的时候,系统表空间无法回收,而且系统表空间文件可能会非常大。当设置innodb_file_per_table为ON时,InnoDB会在innodb_data_home_dir/数据库名目录下,创建与表名一样的ibd文件。ibd文件中存放表数据和索引,当使用drop时,innodb会将文件删除,这样表空间就可以回收了,而且因为数据都在自己的文件里面,所以迁移数据时,可以直接将文件拷贝到目标位置。
如果启用了innodb_file_per_talbe参数,需要注意的是每张表的表空间内存放的只是数据、索引和插入缓冲Bitmap页,其他数据如:回滚日志、插入缓冲索引页、系统事物信息、双写缓冲(Double write buffer)等还是放在原来的共享表空间内。同时说明了一个问题:即使启用了innodb_file_per_table参数共享表空间还是会不断的增加其大小的。而且在可重复隔离级别下,我们要尽量避免长事务,因为事务长时间不能结束,回滚日志会不断增长,造成系统表空间不断增大。

(4)表空间碎片

当删除表数据时,InnoDB会将文件中对应的数据位置清空,这样文件中这个位置便会留白,如果有大量的删除操作,会造成文件中很多位置空白没有数据,
因为表空间不会回收,这会造成没有数据的空间甚至比有数据的空间还要大。当下次插入时,InnoDB会尽量将数据插入到这些留白的位置,但是这样
也不能确保所有的留白位置都被使用,这样便会出现一些小的无法利用的位置,这些最后便成为碎片。

(5)回滚日志清理线程

InnoDB使用一个清理线程不断的清理已经不再使用的回滚日志,回滚日志都是写入到系统表空间的,清理不及时,会造成系统表空间不断增加。如果有一个很大的回滚日志并且表空间增加非常快,InnoDB会降低处理速度以使清理线程可以跟上。
InnoDB提供了参数innodb_max_purge_lag,该值表示InnoDB开始延迟后面的更新语句之前,可以等待被清除的最大事务量。不过新版本的mysql中,清理线程的性能已经提升了很多,可以有多个清理线程共同清理回滚日志。

(6)双写缓冲

双写缓冲是为了避免表数据页没写完整导致数据损坏。双写缓冲是系统表空间中一段连续的空间,大小共2M,可以保存128个页。事务修改表数据会先修改内存中的数据页,之后InnoDB的线程将脏页写回磁盘,如果启用双写缓冲,InnoDB会先将脏页完整的写入双写缓冲,并刷新磁盘,之后再将脏页写入到磁盘上该页对应的位置。试想下面的场景,如果在将脏页写回到该页对应的磁盘位置时,发生了系统崩溃,那么页在磁盘上已经损坏,系统恢复时发现页已经损坏,那么便从双写缓冲中找到对应的页,然后将损坏页替换。那么有人会问如果写入双写缓冲时发生系统崩溃怎么办?这种情况下InnoDB不会再使用双写缓冲中的损坏页面了,因为事务日志已经记录到磁盘上,磁盘上的原始数据页面是没有损坏的,那么可以回放事务日志来修改磁盘上原始页面的数据。
启用双写缓冲,页面需要在磁盘上写两次,因为双写缓冲是在磁盘上顺序写入,速度很快,所以双写缓冲造成的性能损失非常小。使用双写缓冲还会使得日志文件更加高效,确保数据页不会发生损坏。
如果磁盘已经做了备份可以确保数据页不会发生损坏或者当前数据库是一个备库,那么可以使用参数innodb_doublewrite将双写缓冲关闭。
本质上双写缓冲是最近写回的页面的备份拷贝。

(7)插入缓冲

与双写缓冲一样,插入缓冲也是位于磁盘上,它是专门处理对非聚集索引的插入。
一般对表插入数据后,还需要插入对应的索引,插入索引需要对磁盘随机访问,这会大大影响更新效率,因此mysql提供了插入缓冲。当插入索引时,如果对应的索引页在内存,则直接更新,如果不在内存,则记录一条插入记录放到插入缓冲中,等待后台线程在适当的时机进行Merge操作。注意插入缓冲只保存对非唯一的二级索引的插入记录。那么为什么不保存对唯一索引的插入记录呢?这是因为插入唯一索引时,需要访问唯一索引判断插入的值是否唯一,这样索引页已经在内存中了,直接更新内存即可。
后台线程通过扫描插入缓冲,将多次插入操作一次性合并二级索引中,大大提高了效率。
插入缓冲无法进行配置,其存储于系统表空间中。尽管提升了插入的效率,但是在一些情况下,其还会带来一些问题:

  1. 应用程序执行大量的插入操作时,如果这个过程中数据库发生了宕机,会存在大量的插入缓冲未合并到实际的非聚集索引页中,如果这样,导致实例恢复时间变长,并且恢复会占用大量的磁盘IO。
  2. 写密集的情况下,插入缓冲占用过多的缓冲池内存,默认情况下最大可以占用1/2的缓冲池内存,可能会对其他操作带来影响(InnoDB缓冲池有一段空间是插入缓冲使用的)。

在InnoDB1.0版本之后,引入了Change Buffer,其作用与插入缓冲类似,不同的是,Change Buffer可以缓冲插入/更新/删除操作。Change Buffer也是对非聚集的非唯一索引进行缓冲的。
Change Buffer可以细分为:Insert Buffer、Delete Buffer、Purge Buffer(缓冲在后台发生的物理删除操作)。InnoDB提供了一些配置Change Buffer的参数。

  • innodb_change_buffering:用于配置缓冲哪种类型的更新操作,码值有:all,none,inserts,deletes,changes,purges。默认是all,表示缓冲所有的更新,none表示关闭缓冲,inserts表示只缓冲插入;
  • innodb_change_buffer_max_size:此参数用于控制Change Buffer最大使用内存的数量。默认值为25,它表示Change Buffer最多占用缓冲池的25%,最多可以设置到50%。

如果想了解更多关于插入缓冲的内容,可以参见文章:https://www.jianshu.com/p/fbe559081400

[MySQL]浅谈InnoDB存储引擎(四)插入缓冲MySQL插入缓冲