总的来说,InnoDB有两部分内容,一个是内存,一个是磁盘,两者间使用OS给的API连接,具体结构如下图所示。
In-Memory Structres
从图里也看到了,内存里主要就四个东西Buffer Pool, Change Buffer, Adaptive Hash Index和Log Buffer
Buffer Pool
Buffer池,涉及到频繁分配内存的应用都会有个Buffer池,看看InnoDB怎么个实现方式。
存什么
Buffer Pool里存的都是InnoDB缓存的表和索引,主要是减少访问热数据时带来的读磁盘,从而提升整个系统的运行速度。在专用的机器上,最多会有80%的内存被用于Buffer Pool。
怎么存
Buffer Pool里的内存被分配为多页,页与页之间构成一个链表,使用LRU算法排布数据。所以,知道如何将热表放在Buffer Pool里是MySQL调优的重要手段。
LRU算法
InnoDB用的LRU算法如下
- 页和页构成链表
- 空间不足时,删除最不常访问的页,将新页插入到链表中部,并将链表分为新链(最常访问)和旧链(最不常访问)
- 当访问旧页(位于旧链的页)时,该页被插到新链首
- 页不会被主动地往链尾移动,它只会因为其他页变新而被挤到链尾,最终被挤出去
那么问题来了,什么是链表中部呢?新插入的数据将链表分为前5/8和后3/8.其中,前5/8表示的是最常访问的数据,后3/8表示的是最不常访问的数据,并不是真的1/2位置。
那么问题又来了,什么时候插数据进去呢?当用户进行SQL查询的时候,或者MySQL进行read-ahead(由上层应用发起的提前加载可能出现的页的操作)时。此外,当用户进行SQL查询时,已有页会变新,但如果只是read-ahead,那只会把缺的页读进来,但只有再次被读时,才会变新。
注意事项
- 表格扫描,或者其他会导致MySQLDump的操作,会把一整个表都读入到Buffer Pool中,影响性能
Change Buffer
由于辅助索引没有唯一性要求,且大部分修改都比较随机,所以需要一个数据结构来统筹规划一下修改,避免修改过于零散导致磁盘IO性能损失太多。例如一部分依据辅助索引进行判断的删改查。需要注意的是,Change Buffer并不只是Buffer Pool的配套功能,磁盘中也有一个Change Buffer,用以避免零散的磁盘IO。
存什么
Change Buffer是一个特殊的数据结构,旨在缓存那些对辅助索引进行修改,但主索引中的相关数据却并没有在Buffer Pool中的情况,例如Insert, Update和Delete。当相关页被读入Buffer Pool的时候,又将具体的修改应用上去(可能是有人要读相关内容,或者只是Purge线程清理数据)。
Adaptive Hash Index
Adaptive Hash Index通过牺牲一部分传统功能和可靠性,向外界提供类似于Hash表的接口,方便开发。主要的思想是,将查询过程中需要用到的B+树节点缓存下来,避免因为上层节点而频繁IO。
这个Index由谁建
显然,如果一个表能经常地被映射到内存里面,那性能提升就比较明显,这个Index由InnoDB内部的一套分析机制决定,按照性能提升最大的方式建索引。
注意
- 在某些工作负载下,Hash索引的查找速度可能并不咋地,而且像Like之类的泛查找并不能从中获益,甚至还会导致Hash索引速度变慢,所以可以有选择地将其关闭。
- Hash索引是分区的,不同索引会被分到不同的区,具体多少个区,每个区有多大都有参数进行调整。
Log Buffer
Log Buffer中存的是一些需要被flush进磁盘的内容,这部分内容会被周期性地写进磁盘中。如果一个事务修改的数据太多,会导致Log Buffer满了,然后就得停下更新,写磁盘,然后继续更新,这样会影响性能。这样的情况下,Log Buffer大一些对身体好。此外,由于事务的隔离等原因,所以这个Log Buffer也有多种写的方式,具体见参数innodb_flush_log_at_trx_commit。
On-Disk Structures
Tables, Indexs, Tablespaces, Doublewrite Buffer, Redo Log & Undo Logs
Tables
表没啥好说的,就一个抽象概念,需要有主键,要使用个别炫酷的数据类型还需要开启相应的配置。
主键
主键就写一下定义好了
- 查询中最常使用的列
- 没有前导空格
- 没有重复值
- 几乎不会变
Indexs
Clustered Index
每一个InnoDB表都有一个主索引(使用主键作为key),有三种可选的主键
- 手动指定PRIMARY KEY
- 第一个UNIQUE类型,且非空的列
- InnoDB自动加上的GEN_CLUST_INDEX隐藏列(6-byte)
Secondary Indexs
辅助索引就可以有很多个了,最终辅助索引得到的只是相关数据的主键,所以,主键越长,辅助索引越大
Tablespaces
The System Tablespaces
Change buffer的存储位置,也包括系统空间内的表和索引数据,该空间下可以有多个数据文件,数量和尺寸均可以通过参数调整。
File-Per-Table Tablespaces
一个表,一个文件。
优势
- 磁盘空间可以得到及时回收,如果用共享文件,比如System就不能。特别是例如Alter Table这样的表复制操作,效果更加显著。
- 删表速度更快
- 可以把表建在不同的盘上,提升IO
- 支持更多的数据类型
- 减少系统恢复时间
- 单表备份
- 单表尺寸检测
- 多表同时写入
- 单表尺寸可以大到64T
缺点
- 空间复用率低
- fsync只能针对单文件,这就导致很多表。就会有很多fsync
- mysqld需要位置一堆文件描述符
- 碎片化明显
- 在删表的时候,会在Buffer Pool中扫描是否存在需要删掉的内容,这个过程有点长
General Tablespaces
这是一个使用Create tablespace生成的共享空间,和File-Per-Table差不多
一些好处
- 多表共享一个文件
- 节约内存
- 便于管理
- 支持多种数据类型
Undo Tablespaces
用于存放Undo logs,位于undo log segments是rollback segments的一部分。
默认有两个undo tablespaces,undo tablespaces至少要两个,以应对删除undo tablespaces的情况。(有的时候Undo tablespaces太大了,需要定期清理一下,该过程可以自动也可以手动,但是在系统没有什么负载的时候清理)
默认位于MySQL根目录下。
Temporary Tablespaces
Session Temporary Tablespaces
用于存储用户创建的临时表,以及跨表查询的优化。
一个Session最多可以到临时表池中申请两个临时表空间,一个用于存储用户的查询结果,另外一个用于存储优化器生成的临时表。
临时表池中开始时有10个表,其空间不会缩小,但会在不够时自动扩容。其内容在关闭或重启时自动清空。
Global Temporary Tablespaces
用于存储rollback segments中,与用户创建的临时表有关的内容。在系统关闭时自动清空,系统启动时又自动创建,如果不能创建,MySQL就会启动失败。一般由于上一次创建的Global Temporary Tablespaces还存在,此时手动删掉再重启即可。
系统离线时移动表文件
过于技术和实际化,没啥好看的
Doublewrite Buffer
在数据从buffer pool写到磁盘之前,需要在Doublewrite Buffer中缓存一下。因为要找到正确的写入页又比较花数,在这个过程中一旦crash,需要写入的内容就没了。虽然诗句被写了两次,但由于Doublewrite Buffer是大块连续写,还用上了fsync,所以速度非常快。在旧版本中,Doublewrite Buffer位于系统表空间中;而新版本则位于专门的Doublewrite Buffer文件中。
Redo Log
Redo Log主要用于将未完成的事务从crash中恢复过来,具体是将buffer pool里尚未被写入磁盘的内容持久化下来,出现意外后,内存中的内容就会丢失,重启时读入持久化的结果,将系统状态恢复到崩溃之前的状态。里面一般存的是页的修改,而不是命令。在需要恢复的时候,系统会再执行相关语句一遍,从而将系统恢复到崩溃之前的状态。
Undo Logs
Undo Logs是单个事务的undo log的集合。每一条undo log里都包含了如何将该事务对主索引的修改复原的内容,如果另外一个事务需要看历史数据,则使用undo log里的内容,找到历史数据的内容。
存在哪里
Undo logs位于undo log segments里,其位于rollback segments的一部分,具体是undo tablespaces和global temporary tablespace中。Global temporary tablespace中的部分用于恢复用户的临时表,这些logs是不会存在Redo Log里的,他们只会用于回滚操作,以避免不必要的IO。
Undo logs也是可以加密的,具体细节以后看。
数量
每一个undo tablespaces和global temporary tablespace都默认包含128个rollback segments(可以通过参数指定).每一个rollback segments支持的事务数,由其内部的undo slots数量和每个事务需要的undo logs数共同决定。一个rollback segments正好一页,而一个undo slot占16byte,所以很容易就能算出来undo slots的数量。
一个事务最多可以分到4个undo logs,分别用于处理
- INSERT定义表
- UPDATE/DELETE定义表
- INSERT临时表
- UPDATE/DELETE临时表
undo log一旦被指定,将伴随该事务的整个生命周期,中途不会被指定给其他事务。
从undo logs数量估算并发事务上限
- 如果每个事务都不会既INSERT又DELETE: (innodb_page_size / 16) innodb_rollback_segments number of undo tablespaces
- 每个事务既INSERT又DELETE: (innodb_page_size / 16 / 2) innodb_rollback_segments number of undo tablespaces
- 事务只会在临时表中INSERT:(innodb_page_size / 16) * innodb_rollback_segments
- 事务在临时表中既INSERT又DELETE: (innodb_page_size / 16 / 2) * innodb_rollback_segments
和binlog的关系
binlog是MySQL最重要的日志,由高层的MySQL组件记录,共有两种,包括Statement,以事件的形式记录了所有DDL,DML以及运行用时;Row,记录每一条数据的修改情况。此外,给二进制日志是事务安全的。两个最主要的用途,主从复制,数据恢复
什么是事务安全的呢?