MYSQL架构和Innodb存储引擎

1、MySQL体系结构

(1)模块详解

                     

  Connector:用来支持各种语言和SQL的交互,如java的JDBC。

  Management Services & Utilties:系统管理和控制工具,包括备份恢复、MySQL复制、集群等。

  Connection Pool:连接池,管理需要缓冲的资源。

  SQL Interface:用来接收用户的SQL命令,返回用户需要的查询结果。

  Parser:用来解析SQL语句。

  Optimizer:查询优化器。

  Cache & Buffer:查询缓存,除了行记录的缓存,还有表缓存、权限缓存等。

  Pluggable Storage Engines:插件式存储引擎,提供API给服务层使用。

(2)架构分层

  总体上,可以把MySQL分成三层,跟客户端对接的连接层,真正执行操作的服务层,和跟硬件打交道的存储引擎层(参考MyBatis:接口、核心、基础)。

      

  连接层:客户端要连接到MySQL服务器的3306端口,必须要与服务器建立连接,管理所有的连接,验证客户端的身份和权限,都是在连接层完成。

  服务层:连接层将SQL语句交给服务层,这里面又包含一系列的流程:

      比如查询缓存的判断、根据SQL调用相应的接口,对SQL语句进行词法和语法的解析(比如关键字怎么识别,别名怎么识别,语法有没有错误等等)。然后就是优化器,MySQL底层会根据一定的规则对我们的 SQL语句进行优化,最后再交给执行器去执行。

  存储引擎层:存储引擎是数据真正存放的地方,会跟内存和磁盘交互。

2、一条查询SQL执行流程

    

 3、一条更新SQL执行流程

   update操作包括更新、插入和删除。其基本流程和查询一致,也要经过解析器、优化器的处理,最后交给执行器。区别在于拿到符合条件的数据之后的操作:

    

4、InnoDB存储引擎

  Innodb内存结构和磁盘结构如下:

       

(1)缓冲池Buffer Pool

  Innodb的数据都是放在磁盘上的,Innodb操作数据有一个最小的逻辑单位,叫做页(索引页和数据页)。由于磁盘速度慢,因此对于数据的操作不是每次都直接操作磁盘,Innodb使用了一种缓冲池技术,把从磁盘读到的页放到一块内存区域,就是Buffer Pool。

  下一次读取相同的页,先判断是不是在缓冲池里面,如果是,就直接读取,不用再次访问磁盘。修改数据的时候,先修改缓冲池里面的页。内存的数据页和磁盘数据不一致的时候,我们把它叫做脏页。InnoDB里面有专门的后台线程把Buffer Pool的数据写入到磁盘,每隔一段时间就一次性地把多个修改写入磁盘,这个动作就叫做刷脏

  Buffer Pool主要分为3个部分:Buffer Pool、Change Buffer、Adaptive Hash Index,另外还有一个(redo)Log Buffer。

* Buffer Pool

  Buffer Pool缓存的是页数据,包括数据页和索引页。默认大小是128M,可以调整。内存的缓冲池写满了怎么办?

  Innodb和redis类似,采用LRU算法来管理缓冲池(链表实现,不是传统的LRU,分成了young和old),经淘汰保留下来的就是热点数据。

* Change Buffer

  如果这个数据页不是唯一索引,不存在数据重复的情况,也就不需要从磁盘加载索引页判断数据是不是重复(唯一性检查)。这种情况下可以先把修改记录在内存的缓冲池中,从而提升更新语句(Insert、Delete、Update)的执行速度。这一区域就是Change Buffer。5.5之前叫Insert Buffer,现在也能支持delete和update。

  最后把Change Buffer记录到数据页的操作叫做merge,什么情况会发生merge呢:这个数据页被访问、后台线程刷脏操作、数据库shut down、redo log写满时触发。

  场景:数据库大部分索引都是非唯一索引,并且业务是读多写少,不会在写数据后立刻读取。可以通过innodb_change_buffer_max_size来调整Change Buffer占缓冲池的比例。

* Adaptive Hash Index

  对于一些热点数据页,Innodb对自动建立自适应Hash索引,也就是在B+Tree索引的基础上建立Hash索引。因为hash索引是以KV形式检索数据,所以查找速度很快。

* (redo)Log Buffer

  思考一个问题:如果BufferPool里面的脏页还没有刷入磁盘时,数据库宕机或者重启,这些数据丢失。如果写操作写到一半,甚至可能会破坏数据文件导致数据库不可用。

  为了避免这个问题,Innodb把所有对数据页的修改操作专门写入一个日志文件,并且在数据库启动时从这个文件进行恢复操作——用它来支持事务的持久性。

      

   这个文件就是磁盘的redo log,对应于/var/lib/mysql目录下的ib_logfile0和ib_logfile1,每个48M。这种日志和磁盘配合的过程在MySQL里叫WAL(write-ahead logging),关键点就是先写日志,再写磁盘。这么做主要是因为数据刷盘是随机IO,而写日志是顺序IO,速度是很快的,因此先把修改写入日志,可以盐池刷盘时机,进而提升系统吞吐能力。

  当然redo log也不是每一次都直接写入磁盘,在Buffer Pool里面有一块内存区域(Log Buffer)专门用来保存即将要写入日志文件的数据,默认16M,它一样可以节省磁盘IO。log buffer写入磁盘的时机,由一个参数(innodb_flush_log_at_trx_commit)控制,默认是1。

      

   redo log的特点:1)记录的是数据页做了什么改动,2)redo log大小是固定的,前面内容会被覆盖,如下图,checkpoint是当前要覆盖的位置。如果writepos跟checkpoint重叠,说明redo log已经写满,这时候需要同步redo log到磁盘中。

                                        

(2)磁盘结构

  表空间可以看做是Innodb逻辑结构的最高层,所有数据都存放在表空间中,Innodb的表空间分为5大类:

* 系统表空间 System tablespace

  系统表空间是一个共享表空间,包含数据字典、双写缓冲、Change Buffer和Undo Logs。如果没有指定file-per-table(独占表空间开关),也包含用户创建的表和索引数据。

  数据字典:有内部系统表组成,存储表和索引的元数据(定义信息)。

  双写缓冲:(Innodb的一大特性)

    InnoDB的页和操作系统的页大小不一致,InnoDB页大小一般为16K,操作系统页大小为4K,InnoDB的页写入到磁盘时,一个页需要分4次写。

         

     如果存储引擎正在写入页的数据到磁盘时发生了宕机,可能出现页只写了一部分的情况,比如只写了4K,就宕机了,这种情况叫做部分写失效(partialpagewrite),可能会导致数据丢失不是有redolog吗?但是有个问题,如果这个页本身已经损坏了,用它来做崩溃恢复是没有意义的。所以在对于应用redolog之前,需要一个页的副本。如果出现了写入失效,就用页的副本来还原这个页,然后再应用redolog。这个页的副本就是doublewrite,InnoDB的双写技术。通过它实现了数据页的可靠性因为doublewrite是顺序写入的,不会带来很大的开销。

* 独占表空间 file-per-table tablespaces

  可以让每张表独占一个表空间。这个开关通过innodb_file_per_table设置,默认开启但是其他类的数据,如回滚(undo)信息,插入缓冲索引页、系统事务信息,二次写缓冲(Double write buffer)等还是存放在原来的共享表空间内。

* 通用表空间 general tablespaces

  通用表空间也是一种共享的表空间,跟ibdata1类似。可以创建一个通用的表空间,用来存储不同数据库的表,数据路径和文件可以自定义。语法:

    create  tablespace  ts2673  add  datafile  '/var/lib/mysql/ts2673.ibd'  file_block_size=16K  engine=innodb;
  在创建表的时候可以指定表空间,用ALTER修改表空间可以转移表空间。
    create  table  t2673(idinteger)  tablespace  ts2673;

* 临时表空间 temporary tablespaces

  存储临时表的数据,包括用户创建的临时表,和磁盘的内部临时表。对应数据目录下的ibtmp1文件。当数据服务器正常关闭时,该表空间被删除,下次重新产生。

* undo log tablespace

  undolog(撤销日志或回滚日志)记录了事务发生之前的数据状态(不包括select) 。如果修改数据时出现异常,可以用undo log来实现回滚操作(保持原子性)。在执行undo 的时候,仅仅是将数据从逻辑上恢复至事务之前的状态,而不是从物理页面上操作实现的,属于逻辑格式的日志。

  总结一下一个更新操作的流程,这是一个简化的过程。name原值是jjy,updateusersetname='penyuyan'whereid=1:

    1、事务开始,从内存或磁盘取到这条数据,返回给Server 的执行器;

    2、执行器修改这一行数据的值为penyuyan;

    3、记录name=jjy到undo log;

      4、记录name=penyuyan到redo log;

        5、调用存储引擎接口,在内存(Buffer Pool)中修改 name=penyuyan;

        6、事务提交

(3)后台线程

  后台线程的主要作用是负责刷新内存池中的数据和把修改的数据页刷新到磁盘。后台线程分为:master thread,IO thread,purge thread,page cleaner thread。

  master thread负责刷新缓存数据到磁盘并协调调度其它后台进程。

  IOthread 分为 insert buffer、 log、 read、 write进程。分别用来处理insert buffer、重做日志、读写请求的IO回调。

  purge thread用来回收undo 页。

  page cleaner thread用来刷新脏页。

5、Binlog

  binlog以事件的形式记录了所有的DDL和DML语句(因为它记录的是操作而不是数据值,属于逻辑日志),可以用来做主从复制和数据恢复。跟redo log不一样,它的文件内容是可以追加的,没有固定大小限制。

  在开启了binlog功能的情况下,我们可以把binlog导出成SQL语句,把所有的操作重放一遍,来实现数据的恢复。binlog的另一个功能就是用来实现主从复制,它的原理就是从服务器读取主服务器的binlog,然后执行一遍。

原文地址:https://www.cnblogs.com/jing-yi/p/12851900.html