一、概述
1.总体架构
-
网络接入层
- 作用:主要负责连接管理、授权认证、安全等等,每个客户端连接都对应着服务器上的一个线程,服务器上维护了一个线程池,避免为每个连接都创建销毁一个线程,当客户端连接到MySQL服务器时,服务器对其进行认证,可以通过用户名与密码认证,也可以通过SSL证书进行认证,登录认证后,服务器还会验证客户端是否有执行某个查询的操作权限,这一层并不是MySQL所特有的技术
- 此层组件:
- 线程池:用于管理用户连接线程,避免资源浪费
-
服务层
-
作用:MySQL的核心,负责查询解析,SQL执行计划分析,SQL执行计划优化,查询缓存,跨存储引擎的功能也都在这一层实现,如存储过程,触发器,视图等,构图如下:
-
此层组件
- 缓存区:用于存放表缓存、记录缓存、key缓存、查询缓存、权限缓存等,在解析查询之前,服务器会先检查查询缓存,如果能找到对应的查询,服务器不必进行查询解析、优化和执行的过程,直接返回缓存中的结果集
- 解析器和预处理器:用于解析和验证SQL语句,判读其是否符合规范
- 查询优化器:优化器会根据存储引擎提供容量或某个具体操作的开销信息来评估执行花费的时间并选出最好的执行计划,而索引的选择就是有它决定的
- 执行引擎:查询执行引擎会根据执行计划给出的指令调用存储引擎的接口得出结果
-
-
插件式存储引擎层:负责MySQL中数据的存储与提取, 服务器中的查询执行引擎通过API与存储引擎进行通信,通过接口屏蔽了不同存储引擎之间的差异,存储引擎是针对表的而不是数据库,所以不同的表可以使用不同的引擎
-
物理文件层:将数据库的数据存储在文件系统之上,并完成与存储引擎的交互,文件包括数据文件与日志文件
2.主要引擎概述
- InnoDB:支持事务、外键、非锁定读,有行锁设计,通过使用多版本控制(MVCC)来获得高并发性,并实现了4种隔离界别,同时也通过间隙锁策略避免了幻读现象的产生,默认是可重复读隔离界别,对于表中数据的存储,InnoDB存储引擎采用聚集的方式存储,及每张表中的数据都按主键的顺序存放,如果没有显示申明主键则InnoDB存储引擎会主动为每一张表创建一个6字节的主键。
- MyISAM:不支持事务、外键、非锁定读,是表锁设计,对于表采用非聚集方式存储,数据与主键、索引是分开存储的,索引的结果值为数据的引用地址。
注意:在MySQL5.6以后InnoDB与MyISAM都支持全文索引,数据量的增加也确实会使数据库的性能下降,且不是线性下降。
3.InnoDB体系架构
-
后台线程池:用于维护所有后台线程线程,后台线程主要作用是复制刷新内存池中的数据并将已修改的数据刷新到磁盘中。如下为MySQL中常见的后台线程
- Master Thread,核心的后台线程,负责将内存池中的缓存数据一般刷新到磁盘中,保证数据的一致性,包括脏页的刷新、合并插入缓冲、undo页的回收、redo log缓冲的刷新入磁盘等
- IO Thread,负责异步IO请求的回调,在Linux平台下其数量只有四个,分别对应写IO、读IO、插入缓冲IO及日志IO
- Purge Thread,事务提交后其所使用的uodo log可能不在需要,因此需要Purge Thread来回收已经使用并分配的uodo页,同时使用Purge Thread来回收uodo页也能减轻Master Thread的工作,从而提高CPU的使用率以及存储引擎的性能
- Page Cleaner Thread,负责脏页的刷新,用于减轻Master Thread的工作及减轻对与用户查询线程的阻塞,进一步提高InnoDB存储引擎的性能,在MySQL5.6以后默认也为4个该线程
-
后台内存池:缓存磁盘上的数据,方便快速地读取,同时在对磁盘文件的数据修改之前在此内存池中缓存;重做日志(redo log)缓存。内存结构如下
缓冲池
- InnoDB读取数据时会先查看缓冲池中是否有对应的数据页,没有就从磁盘中读入到缓冲池中,然后在基于缓冲池中的数据页进行对应操作,数据也该也是基于缓冲池中的数据页进行的,之后会按照一定频率刷新会磁盘
- 缓冲池可以通过innodb_buffer_pool_size来设置其大小,一般越大查询及修改效率也越高
- 缓冲池中不仅存放了数据页与索引页,还存放了undo页、插入缓冲、自适应哈希索引、InnoDB存储的锁信息、数据字典信息等
- 缓冲池中页的默认大小为16KB,使用LRU(最近最少使用)算法进行管理,而被修改后的页暨脏页除了存在于LRU列表中还存在于Flush列表中,LRU列表用于管理缓冲池中页的可用性,Flush列表则用来管理将脏页刷新会磁盘,二者是互不影响的
重做日志缓冲:用于存放重做日志(redo log)信息,重做日志缓冲的默认大小为8MB,触发重做日志缓冲将重做日志信息刷入重做日志文件的情况如下:
-
Master Thread以秒为单位定时刷新,这也就保证了再大的事务提交的时间也很短
-
每个事物提交时
-
当重做日志缓冲池剩余空间小于二分之一时
-
额外内存池:用于存放LRU、锁、等待等信息,因此在申请很大的InnoDB缓冲池时应考虑增大额外缓冲池的大小
-
Checkpoint技术:将缓冲池中的脏页刷会磁盘,因为有检查点,所以哪怕除了问题也只需要重做缓存点之后的重做日志信息,缩短了数据库恢复时间,同时在一定程度上保证了缓冲池中不会有太多脏页,触发情况如下
- 将所有脏页刷新回磁盘
- 数据库关闭时触发
- 将部分脏页刷新回磁盘
- Master Thread以秒或十秒为单位异步将Flush列表中的脏页刷入磁盘
- 重做日志文件不可用或缓冲池大小不够时会触发
- 脏页数量过多时
- 将所有脏页刷新回磁盘
4.InnoDB关键特性
-
插入缓冲
- 使用原因:Innodb数据表本身就是一个聚集索引表,表中的记录按照聚集索引顺序存储,多条数据插入时索引会按聚集索引自增插入,顺序写磁盘,速度是有保证的;但对于非聚集索引,多条数据插入时索引插入就不那么顺利了,因为这多条记录在非聚集索引中并不是相邻的,所以在插入非聚集索引叶节点时,为随机插入,性能不高;
- 描述:当对辅助非唯一索引进行插入时,如果该非聚集索引页在缓存中则直接插入,如果不在则先存入到插入缓冲中,然后按照一定频率进行插入缓冲与辅助索引子节点的合并操作,由此将多个同非聚集索引叶节点的插入合并到一个IO操作中,减少对非聚集索引页的随机访问花费的时间,提供非聚集索引的插入性能
- 存在问题
- 可能导致数据库宕机后实例恢复时间变长,如果应用程序执行大量的插入和更新操作,且涉及非唯一的聚集索引,一旦出现宕机,这时就有大量内存中的插入缓冲区数据没有合并至索引页中,导致实例恢复时间会很长
- 在写密集的情况下,插入缓冲会占用过多的缓冲池内存,默认情况下最大可以占用1/2,这在实际应用中会带来一定的问题
- 注意点:除了插入缓冲外还有修改缓冲、删除缓冲,也是总用于非聚集索引的修改和删除,效果类似
- 插入缓冲结构:插入缓冲是一棵B+树,其key值包含了能唯一标识一张表的space id及标识对应页的偏移值,该key值使得归属于相同表同一索引页的索引操作会被缓存在一起,便于之后合并为一次操作,当InnoDB发现该辅助索引的对应索引页不再缓冲中时就会在插入缓冲中的B+树上创建一个基于该表与该索引页构建key值的节点,而相关操作则会记录在对应节点下的叶子节点中
-
两次写
-
产生原因:因为redo log起作用的前提是拥有对应页的副本及相应的重做日志,但当页损坏或重做日志丢失时,redo log就无法生效了,因此加入了两次写来保证数据页的可靠性
-
具体过程
-
注意点:两次写写入的页数与次数比差不多在64:1左右,高峰时比值会自动调节,因此系统的写入压力并不是很高
-
-
自适应哈希索引
- 使用原因:B+树的查询次数是取决于B+树的高度的,而在生产环境中B+树的高度一般为3
4层,因此需要34次查询,而哈希一般只需要一次查询就可以了,所以使用哈希可以提高查询效率 - 具体过程:InnoDB持续监控表上各索引页的查询,如果发现建立哈希索引可以提高效率时就会基于缓冲池中的对应索引页建立哈希索引,之后对应索引页的查询、修改都会基于该哈希索引进行,从而减少查询次数,提高查询、修改效率
- 注意点:哈希索引对应索引页的索引,而不是一张表的完整索引,一般是对热点数据建立哈希索引而不是所有数据
- 使用原因:B+树的查询次数是取决于B+树的高度的,而在生产环境中B+树的高度一般为3
-
异步IO
-
刷新邻接页:当要刷新一个脏页时,InnoDB会检查该页所在区的所有页,如果是脏页就一起刷新,便于异步IO将这些刷新合并为一个IO操作,对于机械磁盘能显著的提供效率,而对于固态硬盘则无太大提升,可以将innodb_flush_neighbors设置为0来关闭该特性
二、数据库相关文件
1.概述
- 参数文件:用于设定MySQL其他文件的所处位置、初始化的参数、某种内存结构的大小等
- 日志文件:用来记录MySQL实例对某种条件做出响应时写入的文件,如错误日志文件、二进制日志文件等
- MySQL表结构文件:用来存放MySQL表结构定义文件
- 存储引擎文件:用于存储对应表的记录、索引等信息
2.参数文件
- 描述:MySQL在启动时一般会先去读取一个参数文件来将默认设置改为用户指定的设置,如果不存在该配置文件则直接使用默认配置,在Linux中,配置文件一般为/etc/my.cnf
- 文件内参数格式:一般为key=value,例如innodb_pool_size=1G
- 文件内参数类型:静态参数只能在启动时通过参数文件修改,而动态参数可以在MySQL实例运行时通过set global/session key=value来修改,动态参数的修改只会在MySQL实例的此次生命周期中起效
3.日志文件
- 错误日志:记录了MySQL启动、运行、关闭过程中的一些重要信息,包括正确的、警告的、错误的信息,可以通过show global variables like "log_error"来定位该文件的位置
- 慢查询日志:用于记录超过阈值的查询,可以通过set global slow_query_log=on来开启,通过set global long_query_time=5来将阈值设为5秒,通过slow_query_log_file来查看慢查询日志的位置
- 二进制日志:记录了对MySQL数据库的所有修改操作,不包括select与show这类操作,二进制日志可以用于数据恢复、主从复制、审计是否有对数据库的攻击,可以通过在my.cnf添加log-bin[=name]来启动二进制日志,若未指定name则以主机名加序号为日志名,存放在数据库数据所在的datadir目录,可以通过show global variables like "datadir"
4.表结构定义文件:以frm为后缀的指明MySQL表结构的文件,归属于MySQL而非存储引擎
5.InnoDB存储引擎文件
-
表空间文件
-
描述:用于存放数据与索引等绝大部分数据,分为独立表空间与共享表空间,前者归属于某张具体的表,而后者被所有表一同使用
-
设置:
- 可以通过innodb_data_file_path来设置该文件所处位置及大小,默认情况下所有表的数据都存放在一个表空间文件中
- 可以将innodb_file_per_table设置为ON,如此InnoDB存储引擎会为每一个表都创建一个独立表空间文件,这些文件默认会以ibd为后缀,由于所有数据不再存放在一个文件中,效率会高一些,灾难恢复、空间回收也会变得更容易
- 可以通过innodb_data_file_path来设置该文件所处位置及大小,默认情况下所有表的数据都存放在一个表空间文件中
-
-
重做日志文件
- 描述:用于记录InnoDB存储引擎的事务日志,一般会出现在数据目录下,分别以ib_logfile0、ib_logfile1命名,InnoDB在写满第一个文件后就会转去写第二个,写满了第二个后又会回来写第一个,由于重做日志的写入是以磁盘写入的最小单位为大小来写入的,因此不会出现写成功一半、失败一半的情况,所以不需要两次写
- 设置:可以通过innodb_log_file_size来设置重做日志大小,重做日志不能太小,否则会导致InnoDB来回切换书写重做日志的文件,影响性能,也不能太大,太大会使得恢复时间过长,不过一般设置大一点较好
- 描述:用于记录InnoDB存储引擎的事务日志,一般会出现在数据目录下,分别以ib_logfile0、ib_logfile1命名,InnoDB在写满第一个文件后就会转去写第二个,写满了第二个后又会回来写第一个,由于重做日志的写入是以磁盘写入的最小单位为大小来写入的,因此不会出现写成功一半、失败一半的情况,所以不需要两次写
6.undo log、redo log、bin log的区别及作用
- redo log,物理日志,记录该数据页更新的内容,循环写,属于存储引擎层面,用于服务器宕机或者介质故障后的数据恢复,用于保证事务的持久性
- bin log,逻辑日志,记录的是这个更新语句的原始逻辑,追加写,属于数据库层面,用于主从复制及数据恢复
- uodo log,逻辑日志,记录了事务提交前的操作,属于存储引擎层面,用于数据回滚或配合MVCC提供非锁定读,配合锁保证了事务的原子性及隔离性
三、表
1.InnoDB表空间存储结构
- 表空间,逻辑结构的最高层,几乎所有信息都存放在表空间中,其中数据、索引、插入缓冲Bitmap存放在每张表自己的表空间中,而其他信息如undo log、插入缓冲索引页等则存放在共享表空间中,表空间的下属逻辑结构为段
- 段,构成表空间的直接元素,常见的段有数据段、索引段、回滚段等,段完全由存储引擎来管理,段的下属逻辑结构为区
- 区,多个区共同构成一个段,区的大小为1MB,其大小无法人为控制,InnoDB存储引擎一般一次会从磁盘中申请4~5个区来保证区中页的连续性,区的下属逻辑结构为页
- 页,一般为64个连续的页构成一个完整的区,页的大小默认为16KB,可以认为修改,页是InnoDB磁盘管理的最小单位,常见页有数据页、索引页、undo页、事务数据页、插入缓存位图页等,页的下属逻辑结构为行
- 行,对应表中的行暨一条记录,一页最多存放7992行记录
2.InnoDB行记录格式
- Compact行记录格式
- 变长字段长度列表:用于记录变长字段的长度,如果该变长字段列长度小于255字节则用1个字节表示,否则用2字节表示变长字段长度,注意char存储方式与varchar类似,也是通过变长字段长度列表来指明长度,并且对于未能占满长度的字符还会填充0x20
- NULL标志位:用于表示该行数据是否有NULL值,有则用1表示,NULL标志位占1个字节,注意如果数据列为NULL则无需在显示,如此更加NULL标志位便能自动补上NULL值
- 头部信息列:占5个字节,该行是否被删除、记录类型、下一条记录的相对位置等
- 具体数据
- 事务ID列:记录了该行的事务ID,用于MVVC,占6个字节
- 回滚指针列:用于指明回滚数据(该行uodo log)所处位置,占7个字节
- 行溢出:当一行数据长度大于一定值时会导致行溢出,InnoDB会将该行部分数据存放在Blob页中以确保每一数据页至少存放了两行记录
3.页结构
4.约束:关系型数据库与文件系统的一大区别就是关系型数据库提供了约束来保证数据的完整性,如外键、主键、唯一索引等,约束会限制数据的产生、销毁等,如唯一索引对应的唯一约束使得数据必须是唯一的,限制了数据的产生
5.视图:虚表,没有实际的物理存储结构,通过SQL查询来定义,可以作为抽象存储结构
6.分区:对于在线事务处理类型的应用如博客、电子商务、网络游戏等数据库分区并不能起到太大的作用,而对于在线分析处理类型的应用如数据仓库、数据集市等则可通过数据库分区减少分析时数据的IO等
四、锁与事务
1.基础概念
- 锁:此锁为lock,是用于锁定事务操控的相关资源来保证多个用户的事务操作相互隔离,存在于锁管理的哈希表中,有死锁检查机制,一般锁会在事务提交后才释放,不过具体情况与隔离级别有关
- 锁粒度
- 行锁,InnoDB的默认锁级别,会给事务将要操作的行加上锁,锁粒度最小,发生冲突的概率最低,支持的并发度很高,但消耗的系统资源也很高,还可能出现死锁并且加锁的速度也比较慢
- 表锁,InnoDB也支持的锁级别,会给事务操作的表加上锁,锁粒度最大,发生冲突的概率也最高,支持的并发度很低,但消耗的系统资源少,不会出现死锁,加锁速度也快
- 加锁机制
- 乐观锁,不加实际锁,适用于并发度较低的情形,由编程人员实现,一般会乐观地认为不会发生冲突,在更新时再检查版本号或时间戳是否与预期的一致,是就将事务提交,否则进行相应处理
- 悲观锁,加实际锁,适用于并发度较高的情形,由数据库自己实现,一般会悲观地认为所有操作都会发生冲突,所以直接给对应的资源加上锁以确保能避免冲突
- 锁算法(InnoDB特有)
- 记录锁,为某行记录加锁,会封锁该行的索引记录
- 间隙锁,为一个间隙加锁,会锁定该间隙,间隙是指两个记录间的空隙
- 临键锁,记录锁与间隙锁的组合,锁住的范围是一个左开右闭的区间而不是像间隙锁那样的开区间,可以解决当前读的幻读问题,但也必须在可重复读的隔离界别下才起效,InnoDB在可重复读的隔离级别下默认会使用临键锁,如果发现锁住对象的索引具有唯一性且查询的索引包含了该唯一索引的所有列时临键锁会降级为记录锁
- 当前读与快照读
- 当前读,锁定读,会给将要读取的数据加上共享锁以保证其他并发事务不会修改该数据,在InnoDB存储引擎中只有显示的加锁的操作才会使用当前读
- 快照读,不锁定读,读取快照版本而非最新版本,快照版本是通过undo日志实现,由于没有事务需要修改过时的数据,所以不用加锁,快照读对于不同的隔离级别读取的快照版本是不同的,如可重复读隔离级别读取的是事务开始时的快照版本,提交读隔离界别读取的则是最新一份快照数据
2.InnoDB的一些特殊的锁
- 意向锁,用于表明该表的某行已经加了行锁,使得对该表加表锁时不需要去遍历整张表来确定该表加了行锁没,提高了加表锁的效率
- 自增锁,InnoDB的自增值是加了特殊的锁,因此可以在并发情况下正常运行
3.MVVC
- 描述:InnoDB使用多版本并发控制来实现提交读与可重复读两种隔离级别,通过锁实现序列化,而未提交读则直接读取最新的数据行即可
- 基础思想:写操作更新最新版本的快照而读操作则去对旧版本的快照,如此读写就不互斥了,也就不用加读锁了,从而实现了非锁定读,提高了读的效率
- 具体实现:MySQL实例拥有一个递增的系统版本号,每开始一个新的事务,系统版本号就会自动递增,而事务又有一个事务版本号记录着事务开始时的系统版本号,每一行数据又有一个事务ID表明该行记录是否被事务占据着,此外每行数据还有一个回滚日志指针指向着该行数据存储在uodo log中的回滚日志,当发现某行记录被事务占据着的时候就可以通过回滚日志指针去获取该行记录的回滚信息,之后根据隔离级别来选择读取对应版本的数据
4.事务分类
- 扁平事务:操作以保存点为间隔分层次,同一层次的操作要么都执行,要么都回滚
- 嵌套事务:为由若干事务组成的一棵树,子事务可以提交和回滚,只是子事务的提交不会马上生效,直至顶层事务提交后才会生效 (InnoDB未实现,spring的事务管理实现了该对分布式事务的支持)
- 分布式事务:在分布式环境下运行的扁平化事务
5.事务相关日志描述
- redo log:用于恢复提交事务修改的页操作来保证事务的持久性,InnoDB在事务提交完成前会将所有日志按照顺序写的方式、以磁盘扇区大小 (512字节) 为单位写入到重做日志文件中
- uodo log:用于回滚记录到某个特定版本来帮助事务回滚及辅助MVVC功能的实现,存储在共享表空间的undo段中,undo log中存放的日志是逻辑日志,其恢复就是将数据库逻辑操作取消,因此之前操作参生的一些物理结果可能不会被恢复,如之前插入了过多记录使得表空间增大了而在恢复后表空间并不会回归为原来的大小
注意:InnoDB启动时无论上次数据库是否正常关闭都会尝试进行恢复操作,而redo log是物理日志,恢复速度会快一些。