快乐学习
前程无忧、中华英才非你莫属!

MySQL运行过程详解

MySQL服务器启动过程
MySQL作为一个服务器,肯定会在启动之后一直处于运行状态,并且有专门的线程监听客户端的连接及操作。因为mysqld服务器是由C++生成的可执行文件。因为C++跟java是有血缘关系的,所以mysql的运行入口函数,也是main方法。
这个方法在sql/main.cc。里面就一个启动方法。启动过程大致如下:
1、处理配置文件和配置参数
2、日志模块logger统初始化
3、初始化系统内部变量
4、信号系统初始化
5、innodb存储引擎启动
6、终端重定向处理
7、网络系统初始化
8、状态变量初始化
9、binlog检查和初始化
10、服务器监听线程创建,只要服务器不关闭一直等待。
用户连接mysql有三种姿势:命名管道,套接字,共享内存。一般情况下都是用套接字。
当用户打开一个表时候,从系统表中将这个表的信息都读到内存里。包括表名,库名
,列,列默认值,表的字符集,所属存储引擎,对应表空间文件路径,主键信息等等,首先用TABLE_SHARE数据结构来存储,它就是缓存的对象,他是静态的在内存中不允许修改的:,然后通过哈西算法给他打个标签,给TABLE_def_cache函数处理和实例化之后,用户就可以用了,有了这个标签,就会直接返回用户,如果没找到就从系统表里找,并重新构建缓存然后在给他打一个标签。所以看到这里可以回头看看之前的运维内参,就懂了,某些参数设置就非常关键了吧,可以控制这个经常构建的逻辑。在java里面,单例模式,可以很好地节省内存。在这里也是让重新构造 缓存,打标签这个动作,尽量的少发生。就会节约一大批内存空间,也节省了许多代码逻辑得时间消耗。
InnoDB存储引擎启动过程
----》innobase_init 函数开始初始化全局变量
----》innobase_start_or_create_for_mysql :完成启动过程
----》boot:int 和general_init : 初始化同步控制,内存管理、日志恢复变量,和后台线程
----》 buf_pool_init  :缓冲池的初始化
----》log_init :日志初始化 (包括日志写入,LSN管理、检查点、日志刷盘、数据恢复)
----》io_handler_thread:   创建IO异步线程,对缓冲池发出读写请求
----》recv_sys_init :初始化日志恢复系统
----》open_or_create_data_files:  打开和创建ibdata的。 如果文件存在就打开读取文件头信息,比如LSN.并存在就会初始化一个新的文件。
----》undo_tablespaces_init  :回滚段的存储。
----》fsp_header_init :在系统文件ibdata的一开始分配空间,以便可以存储管理一些系统模块、比如事务系统、Inode页面、
----》trx_sys_create_sys_pages : 事务系统存储初始化。事务系统使用的是5号页面,也就是ibdata 的第六个页面。
----》ditct_create: 创建新的数据字典,分配一个ibdata文件中的第八个页面,用来存储数据字典。
----》recv_recovery_from_checkpoint_start(简称:recovery_start):扫描日志文件,将需要恢复的日志一块一块扫描出来。按照页面号归类并且做redo操作。
----》dict_boot:将所有系统表加载到内存。
----》trx_sys_init_at_db_start :初始化事务系统,并将所有回滚段中需要处理的事务,加载进来。为后面的操作做准备!
-----》recv_recovery_from_checkpoint_finish(简称:recovery_finsh):执行回滚操作。
  (recover_start 是redo操作,将所有没有写入的数据页面的日志重做一遍,执行revocery_finsh时,要保证redo恢复完成之后,才能做回滚。
-----》 buf_dblwr_create:两次写缓存来保证数据正确性。
-----》master_thread:每隔一秒做一次后台循环,包含功能是删除废弃表、检查日志空间是否足够、后台合并插入缓存,日志刷盘、做检查点。
-----》srv_purge_coordinator_thread与srv_worker_thread  : 完成PURGE 操作。
-----》buf_flush_page_cleaner_thread:在后台每隔一秒,去刷一次buffer页面,具体刷多少,根据当前系统的负载来决定。
InnoDB存储引擎关闭过程
需要注意的参数:innodb_fast_shutdown    当它的值为零的时候,最保险,会将所有日志、脏页输入到磁盘,完成之后才会关闭数据库,也是最慢关闭的一种。
当为1 的时候,也就是默认的关闭,它不会做全量的PURGE操作和 BUFFER的merge操作。
当为2的时候,只会将已经产生的日志刷入磁盘,其他操作不会做,这样的话相当于异常退出。速度也是最快的。风险也是最大的。如果重启的话,就会做Crash Recover操作,这个时间会很长。
等于2的时候,在数据迁移的时候,可以节省很多时间,数据恢复在新机器上做。
InnoDB日志管理机制
1、buffer pool 用来存储访问过的数据页面,是一块连续的内存,正常会占物理内存的百分之75 到百分之80.
2、innodb存储引擎的数据,简称ibd 中的数据,是按照页 默认为16kB大小,从数据库文件中 读取到buffer pool中。用LRU算法来实现Buffer pool 页面的管理。
3、注意一点:这个非常关键,可以直接提高mysql性能的参数,在5.7.4之前不能动态修改。切记。官方强调的是,buffer pool size 大于1G的时候,就要分配多个innodb_buffer pool_instances =N 来提高并发能力,减少争用,默认是1.,所以这个需要估计多少核CPU,就搞多少个 减去  1的instances。 总得给cpu留点空余的。防止死机自己都恢复不了。
4、 日志容量是固定的,她是循环使用的,如果不够用了,引发的事件是做一个检查点,让最小的LSN向前推,让出一部分空间给新产生的日志来使用。
5、日志大小的优缺点。日志容量设置越大,就避免多次刷盘。日志容量设置的越大,出现的风险是,当数据库挂了,导致日志没有刷盘(LOG Flushed up to 与 Last checkpoint 两个值相差巨大),恢复起来就需要很长的时间。
6、日志容量设置大小,建议与Buffer pool 的总大小匹配即可。
小编的博客里,REDO LOG 和  UNDO LOG出现了好多回。对于数据库管理者而言,它们才是最核心的。也是保证数据库安全的核心技术。
再次介绍,UNDO LOG是干嘛的:它是用来当服务器故障了恢复事务没提交之前的数据.
Undo Log 是为了实现事务的原子性,在MySQL数据库InnoDB存储引擎中,还用Undo Log来实现多版本并发控制(简称:MVCC)。
Undo Log的原理很简单,为了满足事务的原子性,在操作任何数据之前,首先将数据备份到一个地方(这个存储数据备份的地方称为Undo Log).
如果出现了错误或者用户执行了ROLLBACK语句,系统可以利用Undo Log中的备份将数据恢复到事务开始之前的状态.
用Undo Log实现原子性和持久化的事务的简化过程
  假设有A、B两个数据,值分别为1,2。
  A.事务开始.
  B.记录A=1到undo log的内存buffer.
  C.在内存中修改A=3.
  D.记录B=2到undo log的内存buffer.
  E.在内存中修改B=4.
  F.将undo log的buffer写到磁盘。
  G.将内存中修改后的数据写到磁盘。
  H.事务提交
  如果在G,H之间系统崩溃,undo log是完整的,可以用来回滚事务。
  如果在A-F之间系统崩溃,因为数据没有持久化到磁盘。所以磁盘上的数据还是保持在事务开始前的状态。
缺陷:每个事务提交前将数据和Undo Log写入磁盘,这样会导致大量的磁盘IO,因此性能很低。如果能够将数据缓存一段时间,就能减少IO提高性能。但是这样就会丧失事务的持久性。因此引入了另外一种机制来实现持久化,即Redo Log.
再次介绍,REDO LOG它是干嘛的:当事务提交之后,数据没有持久化到表,并且服务器又故障了,这时候用它来恢复事务提交之后的数据。
和Undo Log相反,Redo Log记录的是新数据的备份。在事务提交时,只要将Redo Log持久化即可,不需要将数据持久化。当系统崩溃时,虽然数据没有持久化,但是Redo Log已经持久化。系统可以根据Redo Log的内容,将所有数据恢复到最新的状态.
  Undo + Redo事务的简化过程
 
  假设有A、B两个数据,值分别为1,2.
  A.事务开始.
  B.记录A=1到undo log的内存buffer.
  C.内存中修改A=3.
  D.记录A=3到redo log的内存buffer.
  E.记录B=2到undo log的内存buffer.
  F.内存中修改B=4.
  G.记录B=4到redo log的内存buffer.
  H.将undo log的内存buffer写入磁盘。
  I.将redo log的内存buffer写入磁盘。
  J.事务提交
Undo + Redo事务的特点
  A. 为了保证持久性,必须在事务提交时将Redo Log持久化。
  B. Undo Log要在Redo Log之前写入磁盘。
  B. 数据不需要在事务提交前写入磁盘,而是缓存在内存中。
  C. Redo Log 保证事务的持久性。
  D. Undo Log 保证事务的原子性。
  E. 有一个隐含的特点,数据必须要晚于redo log写入持久存储。这是因为Recovery要依赖redo log. 如果redo log丢失了,系统需要保持事务的数据也没有被更新。
IO性能
  Undo + Redo的设计主要考虑的是提升IO性能。虽说通过缓存数据,减少了写数据的IO. 但是却引入了新的IO,即写Redo Log的IO。如果Redo Log的IO性能不好,就不能起到提高性能的目的。为了保证Redo Log能够有比较好的IO性能,InnoDB 的 Redo Log的设计有以下几个特点:
  A. 尽量保持Redo Log存储在一段连续的空间上。以顺序追加的方式记录Redo Log,通过顺序IO来改善性能。因此在系统第一次启动时就会将日志文件的空间完全分配,从而保证Redo Log文件在存储上的空间有更好的连续性。
  B. 批量写入日志。日志并不是直接写入文件,而是先写入redo log buffer.当需要将日志刷新到磁盘时 (如事务提交),才将许多日志一起写入磁盘,这样可以减少IO次数。
    C. 并发的事务共享Redo Log的存储空间,它们的Redo Log按语句的执行顺序,依次交替的记录在一起,以减少Redo Log的IO次数.
        例如,Redo Log中的记录内容可能是这样的:
             记录1: <trx1, insert …>
             记录2: <trx2, update …>
             记录3: <trx1, delete …>
             记录4: <trx3, update …>
             记录5: <trx2, insert …>
    D. 因为C的原因,当一个事务将Redo Log写入磁盘时,也会将其他未提交的事务的日志写入磁盘。
    E. Redo Log上只进行顺序追加的操作,当一个事务需要回滚时,它的Redo Log记录也不会从Redo Log中删除掉。InnoDB的做法时将回滚操作也记入Redo Log(具体做法往下看)
恢复(Recovery)
 恢复策略
 前面说到未提交的事务和回滚了的事务也会记录Redo Log,因此在进行恢复时,这些事务要进行特殊的的处理.有2中不同的恢复策略:
 A. 进行恢复时,只重做已经提交了的事务。
 B. 进行恢复时,重做所有事务包括未提交的事务和回滚了的事务。然后通过Undo Log回滚那些未提交的事务
 、 
MySQL数据库InnoDB存储引擎使用了B策略, InnoDB存储引擎中的恢复机制有几个特点:
 A. 在重做Redo Log恢复时,没有BEGIN,也没有COMMIT,ROLLBACK的行为,尽管事务ID等事务相关的内容会记入Redo Log,只是被当作要操作的数据的一部分。
 
 B. 使用B策略就必须要将Undo Log持久化,而且必须要在写Redo Log之前将对应的Undo Log写入磁盘。为了降低复杂度将Undo Log看作数据,因此记录Undo Log的操作也会记录到redo log中。这样undo log就可以象数据一样缓存起来,而不用在redo log之前写入磁盘了。
     包含Undo Log操作的Redo Log,看起来是这样的:
     记录1: <trx1, Undo log insert <undo_insert …>>
     记录2: <trx1, insert …>
      记录3: <trx2, Undo log insert <undo_update …>>
     记录4: <trx2, update …>
     记录5: <trx3, Undo log insert <undo_delete …>>
     记录6: <trx3, delete …>
  C. 到这里,还有一个问题没有弄清楚。既然Redo没有事务性,那岂不是会重新执行被回滚了的事务?确实是这样。同时Innodb也会将事务回滚时的操作也记录到redo log中。回滚操作本质上也是对数据进行修改,因此回滚时对数据的操作也会记录到Redo Log中。
     一个回滚了的事务的Redo Log,看起来是这样的:
     记录1: <trx1, Undo log insert <undo_insert …>>
     记录2: <trx1, insert A…>
     记录3: <trx1, Undo log insert <undo_update …>>
     记录4: <trx1, update B…>
     记录5: <trx1, Undo log insert <undo_delete …>>
     记录6: <trx1, delete C…>
     记录7: <trx1, insert C>
     记录8: <trx1, update B to old value>
     记录9: <trx1, delete A>
     一个被回滚了的事务在恢复时的操作就是先把数据更新到新的状态,然后再把数据更新回旧的状态,因此不会破坏数据的一致性.
Undo Log的使用
 
在Redo Log执行完毕后, InnoDB就恢复到了宕机前的状态。那些在宕机前没有完成的事务,还是在没有完成状态。由于已经重起,这些事务需要自动回滚掉才行。因此在redo完成后,InnoDB会自动的根据Undo Log把这些事务给回滚了。这和用户执行ROLLBACK的操作是一样的。
InnoDB存储引擎中相关的函数
  Redo: recv_recovery_from_checkpoint_start()
  Undo: recv_recovery_rollback_active()
  Undo Log的Redo Log: trx_undof_page_add_undo_rec_log()
日志的内容
1、 数据是什么
  从不同的角度和层次来看,我们可以将数据库中的数据看作:
  A. 关系数据
  B. 元组或对象
  C. 存在Page中的二进制序列
2、 物理的日志(Physical Log)
  A. 记录完整的Page
  B. 记录Page中被修改的部分(page中的偏移,内容和长度).
  优点:因为恢复时,完全不依赖原页面上的内容,所以不要求持久化了的数据保持在一个一致的状态。比如在写一个页面到磁盘上时,系统发生故障,页面上的一部数据写入了磁盘,另一部分丢失了。这时仍然可以恢复出正确的数据。
  缺点:Log记录的内容很多,占用很大的空间。如B-Tree的分裂操作,要记录约一个完整Page的内容。
3、 逻辑的日志(Logical Log)
  记录在关系(表)上的一个元组操作。
  A. 插入一行记录。
  B. 修改一行记录。
  C. 删除一行记录。
  逻辑日志比起物理的日志,显得简洁的多。而且占用的空间也要小的多。但是逻辑日志有2个缺点:
  A. 部分执行
     例如:表T有2个索引,在向T插入1条记录时,需要分别向2个B-Tree中插入记录。有可能第一个B-Tree插入成功了,但是第二个B-Tree没有插入成功。在恢复或回滚时,需要处理这些特殊情况。
  B. 操作的一致性问题
     一个插入操作有一个B-Tree的分裂,页A的一半数据移到了B页,A页写入了磁盘,B页没有写入磁盘。如果这时候发生了故障,需要进行恢复,逻辑日志是很难搞定的。逻辑的日志上的‘部分执行’的问题是比较好维护的,但是‘一致性’的问题维护起来是很复杂的。
4、物理和逻辑结合的日志(Physiological Log)
  
这种日志将物理和逻辑日志相结合,取其利,去其害。从而达到一个相对更好的一个状态。这种日志有2个特点:
   A. 物理到page. 将操作细分到页级别。为每个页上的操作单独记日志。比如,一个Insert分别在2个B-Tree的节点上做了插入操作,那么就分别为每一个页的操作记录一条日志。
   B. Page内采用逻辑的日志。比如对一个B-Tree的页内插入一条记录时,物理上来说要修改Page Header的内容(如,页内的记录数要加1),要插入一行数据到某个位置,要修改相邻记录里的链表指针,要修改Slot的属性等。从逻辑上来说,就是在这个页内插入了一行记录。因此Page内的逻辑日志只记录:’这是一个插入操作’和’这行数据的内容‘。
  MySQL数据库InnoDB存储引擎的Redo Log 记录的就是这种物理和逻辑相结合的日志。使用页内的逻辑日志,可以减少日志占用的空间。但是它毕竟还是逻辑日志,上面提到的2个问题能够避免吗?
  A. 页面内的部分执行的情况可以认为不存在了。因为整个页面的操作是原子操作,在完成之前是不会写到磁盘上的。
  B. 操作一致性的问题仍然存在。如果在写一个Page到磁盘时发生了故障,可能导致Page Header的记录数被加1了,但是数据没有刷新到磁盘上,总之页面上的数据不一致了。好在这个问题被缩小到了一个页面的范围内,因此比较容易解决。InnoDB存储引擎中用Double Write的方法来解决这个问题。
5、 Double Write
   Double Write的思路很简单:
   A. 在覆盖磁盘上的数据前,先将Page的内容写入到磁盘上的其他地方(InnoDB存储引擎中的doublewrite buffer,这里的buffer不是内存空间,是持久存储上的空间).
   B. 然后再将Page的内容覆盖到磁盘上原来的数据。
       如果在A步骤时系统故障,原来的数据没有被覆盖,还是完整的。如果在B步骤时系统故障,原来的数据不完整了,但是新数据已经被完整的写入了doublewrite buffer.因此系统恢复时就可以用doublewrite buffer中的新Page来覆盖这个不完整的page.
   Double write 显然会曾加磁盘的IO。直觉上IO次数增加了1倍,但是性能损失并不是很大。Peter在 innodb-double-write中说性能损失不超过5-10%。应该是因为多数情况下使用了批量写入的缘故。
  A. Double write buffer是一段连续的存储空间,可以顺序写入。
  B. Double write有自己的写buffer.先将多个要做doublewrite的page写入内存的buffer,然后再一起写到磁盘上。
   另外doublewrite的功能也可以通过MySQL的参数关闭。
   DoubleWrite的代码在:buf0dblwr.cc. buf_flush_write_block_low()调用buf_dblwr_write_single_page()或 buf_dblwr_add_to_batch()来实现doublewrite.
6、Checksum
   MySQL启动时会检测页面数据是否是完整的一致的。检测页面是否一致的功能是靠Checksum来完成的,每个页面修改完成后都会记算一个页面的checksum。这个checksum存放在页面的尾部.每次从磁盘读一个页到内存时,都需要检测页的一致性。函数buf_page_is_corrupted()是用来检测page的一致性的.
7、 InnoDB Redo Log的日志类型
  InnoDB redo log的格式可以概括为:
  <Space ID>+<Page NO.>+<操作类型>+<数据>.
  Redo Log记录的页面操作大致可以分为以下几种类型:
 
A. 在页面上写入N个字节的内容,这些可以看作是物理的Log.
     MLOG_1BYTE
     MLOG_2BYTES,
     MLOG_4BYTES,
     MLOG_8BYTES,
     MLOG_WRITE_STRING
     各种Page链表的指针修改,以及文件头,段页等的内容的修改都是以这种方式记录的日志。
  B. 页面上的记录操作。
     MLOG_REC_*,
     MLOG_LIST_*,
     MLOG_COMP_REC_*,
     MLOG_COMP_LIST_*
     这些日志记录了对B-Tree页的INSER, DELETE, UPDATE操作和分裂合并操作。
  C. 文件和Page操作
     MLOG_FILE_CREATE,
     MLOG_FILE_RENAME,
     MLOG_FILE_DELETE,
     MLOG_PAGE_CREATE,
     MLOG_PAGE_REORGANIZE
      MLOG_INIT_FILE_PAGE,
  D. Undo Log操作
     MLOG_UNDO_*
     InnoDB中将undo log的操作也记入了redo log. 为什么要这样做,在前面‘恢复’已经说了.
   这里只提到了部分Redo Log的类型,完整的定义在mtr0mtr.h(MySQL-5.7以后移了mtr0types.h)文件中. 在代码中搜索类型的定义,可以很容易的找到都在哪些地方使用了。
8、 Innodb 的 mini-transaction
   虽说Redo Log将数据的操作细分到了页面级别。但是有些在多个页面上的操作是逻辑上不可分裂的。比如B-Tree的分裂操作,对父节点和2个子节点的修改。当进行恢复时,要么全部恢复,要么全部不恢复,不能只恢复其中的部分页面。InnoDB中通过mini-transaction(MTR)来保证这些不可再分的操作的原子性。
   A. 不可分割的操作要在同一个mtr中完成。
   B. 这些操作的Redo日志记录不是直接写入redo log buffer,而是记录在mtr的log buffer中.
   C. 当所有的操作完毕后, mtr一次性的将所有日志写入redo log buffer中.因此一个mtr中的所有日志在redo log中是在一起的.
   D. mtr在将这些日志写入redo log buffer时,会加上结尾标记.
   F. 当做recovery时, InnoDB先根据结尾标记检查mtr的redo 记录是否完整.确认mtr的redo记录是完整的,才能对这个mtr的操作进行恢复。
9、 InnoDB Undo Log的日志类型
  MySQL数据库InnoDB存储引擎的undo log采用了逻辑的日志。
  InnoDB undo log的格式可以概括为:<操作类型>+<Table ID>+<数据>.
  A. 从表中删除一行记录
     TRX_UNDO_DEL_MARK_REC(将主键记入日志)
     在删除一条记录时,并不是真正的将数据从数据库中删除,只是标记为已删除.这样做的好处是Undo Log中不用记录整行的信息.在undo时操作也变得很简单.
  B. 向表中插入一行记录
     TRX_UNDO_INSERT_REC(仅将主键记入日志)
     TRX_UNDO_UPD_DEL_REC(将主键记入日志) 当表中有一条被标记为删除的记录和要插入的数据主键相同时, 实际的操作是更新这个被标记为删除的记录。
  C. 更新表中的一条记录
     TRX_UNDO_UPD_EXIST_REC(将主键和被更新了的字段内容记入日志)
     TRX_UNDO_DEL_MARK_REC和TRX_UNDO_INSERT_REC,当更新主键字段时,实际执行的过程是删除旧的记录然后,再插入一条新的记录。
   因为undo log还要被MVCC和Purge使用,所以还有TRX_ID和DATA_ROLL_PTR等特殊的内容记录在日志中。TRX_UNDO_INSERT_REC不需要记录这些内容.因为MVCC中不可能引用一个不存在的数据。这也是InnoDB将INSERT和UPDATE、DELETE的undo log分开存放的原因。事务提交后,INSERT的undo占用的空间就可以立即释放了.而其他操作的undo log因为要被MVCC使用,需要保留一段时间.
  这些类型定义在:trx0rec.h.
  记录日志的过程在:trx_undo_page_report_insert()和trx_undo_page_report_modify()中。
  Undo操作在row0undo.c, row0uins.c和row0umod.c中, 入口函数是row_undo().
10、 Undo Log 逻辑日志的一致性问题
  前面说了逻辑日志的一致性问题是很复杂的,为什么undo log要用逻辑日志呢?
  因为redo log使用了physiological日志和MTR,恢复时重做完redo log后,就可以保证操作的一致。在执行undo时,只需要考虑部分执行的问题就可以了。部分执行的问题只涉及到数据记录是否在B+Tree上存在,或者是否已经更新到了B+Tree上,因此比较好处理。举个例子,回滚一个INSERT操作:
    A. 检查数据是否已经存在于Clustered B+Tree上,如果没有则数据还没来得及更新,所以不需要做任何操作。
    B. 如果Clustered B+Tree上存在,则检查Secondary B+Tree上的索引记录是否已经存在,如果有,则删除。如果没有则不需要做任何操作。
    C. 最后删除Clustered B+Tree上的记录即可。     
Checkpoint
          理论上来说,如果MySQL数据库InnoDB存储引擎的buffer足够大,就不需要将数据本身持久化。将全部的redo log重新执行一遍就可以恢复所有的数据。但是随着时间的积累,Redo Log会变的很大很大。如果每次都从第一条记录开始恢复,恢复的过程就会很慢,从而无法被容忍。为了减少恢复的时间,就引入了Checkpoint机制。
  在了解checkpoint原理之前,先看两个名词:
1、脏页(dirty page)
  如果一个数据页在内存中修改了,但是还没有刷新到磁盘。这个数据页就称作脏页。
2、 日志顺序号(Log Sequence Number)
  LSN是日志空间中每条日志的结束点,用字节偏移量来表示。在Checkpoint和恢复时使用。
3、 Checkpoint 原理
假设在某个时间点,所有的脏页都被刷新到了磁盘上.这个时间点之前的所有Redo Log就不需要重做了。系统记录下这个时间点时redo log的结尾位置作为checkpoint. 在进行恢复时,从这个checkpoint的位置开始即可。Checkpoint点之前的日志也就不再需要了,可以被清除掉。为了更好的利用日志空间,InnoDB并不会删除以前的Redo Log文件. InnoDB用几个Redo Log文件首尾相连,构建了一个环形缓存(circular buffer)的日志空间。
4、有了Checkpoint之后的Recovery
   A. 首先要定期的将Checkpoint写入磁盘中某个地方.
   B. 做Recovery时,从磁盘中读出Checkpoint.
   C. 根据Checkpoint中的LSN找到Redo Log相应的位置,开始执行Redo Log.
5、Sharp Checkpoint
   对于繁忙的系统来说,很少会出现这样的的一个时间点。为了能创造出这样一个时间点,最简单的办法就是:
    A. 在某个时间开始停止一切更新操作
    B. 所有的脏页被刷新到磁盘
    C. 记录当前Redo Log的结尾位置到磁盘上.
    D. Checkpoint结束,继续更新操作。
Sharp Checkpoint:
 这个方法称作Sharp Checkpoint,显然对于繁忙的系统, 这种方法是不合适的。能不能在checkpoint时不停止用户的操作呢?
 
Fuzzy Checkpoint
  现在我们来看看,不停止更新操作的Checkpoint如何做:
   A. 选取当前的Redo Log结束位置作为checkpoint点。
   B. 将所有checkpoint点之前的脏页写入磁盘.
   C. 将checkpoint点的位置持久化到磁盘上.
   如下图所示,因为在刷脏页的同时用户还在更新数据,LSN1前的某个脏页在刷到持久存储之前就有可能会被LSN1之后的某个操作又给修改了。当刷脏页到磁盘时,LSN1后的部分操作(R1,R2对应的操作)就会被刷入磁盘。停止更新操作做checkpoint时(Sharp Checkpoint),持久存储中存储的数据是某个确切时间点的内存数据的快照。而不停止更新操作做checkpoint时,持久存储中存储的数据不是某个确切时间点的内存数据的快照。因此被称作 Fuzzy Checkpoint.
1、幂等(Idempotence)规则
   如上图所示,checkpoint 在LSN1位置,当checkpoint完成时R1,R2对应的修改也被刷到了持久存储。恢复时要从LSN1位置开始,包括R1, R2在内。虽然,R1,R2的数据已经被刷入持久存储中了,R1,R2两个Redo记录仍然会被重新执。重新执行后,数据还能正确吗?
   这就要求InnoDB的Redo Log要满足幂等规则。幂等规则要求无论redo log被重复执行了多少次,数据始终正确。
    - 物理日志天然满足幂等规则
    - 逻辑日志需要特殊处理才能支持幂等规则
    前面说过InnoDB的Redo Log是物理到页,页内是逻辑日志。因此需要特殊处理,才能满足幂等规则
2、 数据页的最新(最大)LSN
  为了满足幂等规则,InnoDB中每个数据页上都记录有一个LSN。每次更新数据页时,将LSN修改为当前操作的redo log的LSN。在恢复时,如果数据页的LSN大于等于当前redo log的LSN,则跳过此日志。
 3、异步Checkpoint
   实现了幂等规则后,脏页就可以在任何时间,以任何顺序写入持久存储了。InnoDB的buffer pool有一套单独的机制来刷脏页。因此很多情况下checkpoint时,并不需要写脏页到存储。只是将所有脏页的最小的LSN记做checkpoint.这被称作“异步checkpoint"(不刷脏页到持久存储)
  checkpoint的实现在log0log.c.
  log_checkpoint()实现异步checkpoint.
4、同步Checkpoint
  InnoDB的buffer pool通过LRU的算法来决定哪些脏页应该被写入持久存储。如果包含最小LSN的页面频繁的被更新,它就不会被刷到存储上。这样就可能导致checkpoint点很长一段时间无法前进,甚至导致日志空间被占满。这时就要按照LSN由小到大的顺序写一部分脏页到持久存储。这被称做"同步Checkpoint"(要刷脏页到持久存储).
  log_checkpoint_margin().
  log_calc_max_ages()用来计算,‘判断是否要执行同步checkpoint’用到的参数.
缓存池(Buffer Pool)
 学习到这里,我更倾向于说这是一个”Redo+Undo+Buffer”的模式。为了提搞IO性能,脏页缓存在buffer中,Redo log也要先缓存在内存中,doublewrite也有内存buffer.
 InnoDB实现了一套Buffer 机制,称作Buffer pool,将存储在文件中的数据以页为单位映射到内存中.
1、 Buffer Pool的页分类
  Buffer pool内的页分为三种:
  A. 未被使用的页(空白的buffer),没有映射到一个数据文件中页。
  B. 净页,映射到了一个数据文件页,而且没有被修改过。内容和数据文件的页一样。
  C. 脏页,映射到了一个数据文件页,并且数据被修改过。内容和数据文件的页不一样。
2、 Buffer Pool的LRU页表
  InnoDB维护了两个LRU链表。当空间不足时,用来决定哪些脏页应该被首先写入磁盘,哪些净页应该被释放掉。
  A. buffer_pool->LRU,普通LRU链表,记录所有数据缓冲页。
  B. buffer_pool->unzip_LRU,是压缩页(row_format=compressed)解压后数据缓冲页LRU链表。
   LRU链表中的页面按最近一次的访问的时间顺序排列,头部是最近一次被访问的页面,尾部是最早一次被访问的页面。无论是读还是写一个页面上的数据,都要先获取这个页面。因此可以在获取页面时,维护LRU链表.当获取一个页面后,将其放到LRU链表的头部即可。
  buf_page_get_gen()和buf_page_get_zip()用来获取一个页面,他们调用
  buf_unzip_LRU_add_block()和buf_page_set_accessed_make_young()来维护LRU链表。
3、 flush_list
  同步checkpoint时,需要根据数据页修改的先后顺序来将脏页写入持久存储。因此除了LRU链表,buffer pool中还有一个按脏页修改先后顺序排列的链表,叫flush_list.当需要同步checkpoint时,根据flush_list中页的顺序刷数据到持久存储。
  A. 一个页只在flush_list中出现1次,因为一个页面只需要写一次。
  B. 按页面最早一次被修改的顺序排列。
Mini-Transaction(MTR)
前面提到Redo Log将数据的操作细分到了页面级别。但是有些在多个页面上的操作是逻辑上不可分裂的。InnoDB中用Mini-Transaction来表示这些不可再细分的逻辑操作。
1、MTR的一致性
  为了满足MTR的一致性,MTR做了如下的设计:
  A. MTR的所有日志被封装在一起,当MTR提交时一起写入redo log buffer.这样做有2个好处:
     * 减少并发MTR对redo log buffer 的竞争。
     * 连续的存储在一起,恢复时的处理过程更简单。
  B. InnoDB在redo log的层面,将一个MTR中的所有日志作为Redo log的最小单元。在恢复时,一个MTR中的所有日志必须是完整的才能进行恢复。
2、 MTR日志的封装
  为了在日志文件中区分不同的MTR,MTR将MLOG_SINGLE_REC_FLAG或MLOG_MULTI_REC_END写入redo log(mtr_log_reserve_and_write()).
  A. 如果MTR的日志中只有一行记录,在日志的开始处添加MLOG_SINGLE_REC_FLAG,表示MTR中只有一条记录。
  B. 如果MTR的日志中有多行记录,在日志的结尾处添加一个类型为MLOG_MULTI_REC_END的日志,代表MTR的日志到此结束.
3、MTR的LSN
  A. 因为在将日志写入redo log buffer时,才能获得LSN。所以修改数据时,并没有修改页上的LSN。需要在MTR获得LSN后统一修改。
  B. 一个MTR只有一个LSN. 一个MTR内修改的所有页的LSN相同。这样checkpoint就不会出现在MTR的中间。
  C. 在获得LSN后,如果被MTR修改的脏页不在buffer pool的flush_list里,就会被添加进去。看mtr_memo_slot_note_modification()和buf_flush_note_modification().
4、 页级锁
  MTR提交时才写日志到redo log的做法,决定了MTR要使用页级锁。
  A. 一个页面不能同时被多个活动的MTR修改。
  B. MTR中数据页的锁,直到MTR提交时(日志写入redo log buffer)后才释放。
      锁对象存储在mtr的memo中。调用mtr_s_lock和mtr_x_lock来加锁时,锁对象被保存到memo中。解锁在mtr_memo_slot_release()中完成。
5、MTR的ROLLBACK
  看完MTR的代码发现mtr没有记录undo日志,也不能rollback. MTR都是很小的操作单元,而且每个MTR都有明确的操作目标,因此比较容易保证其正确性。
  A. 因为页面操作是在内存中完成,并且页面有固定的格式,因此很多的页面操作是不会失败的。InnoDB存储引擎中的很多写页面的函数都没有返回值.
  B. 在对任何页面操作前,先要检查是否可能发生错误。如果可能发生错误就不能往下执行。如,当插入一行记录到B-Tree的节点时,首先检查页面有足够的空间。
  C. 使用更大粒度的锁(如B-Tree的锁),并且按照一定的顺序加锁。这样才能不导致死锁问题。以上是自己看代码后的大概印象,不一定说到了正点上。MTR模块的代码虽简单,但是MTR在其他模块大量的使用。要透彻的理解MTR,估计还得要看其他模块的代码,整理出来大部分MTR操作过程才行.
  参考
  A. Database Systems: The Complete Book (2nd Edition)
  B. Transaction Processing: Concepts and Techniques
  C. how-innodb-performs-a-checkpoint
  D. InnoDB fuzzy checkpoints
  E. Heikki Tuuri Innodb answers – Part I
  F. Heikki Tuuri Innodb answers – Part II
  G:公众号:MySQL源码阅读,作者:宋利兵     (相关log的文章)
    H:MySQL运维内参(InnoDB日志管理机制)
打赏
赞(0) 打赏
未经允许不得转载:同乐学堂 » MySQL运行过程详解

特别的技术,给特别的你!

联系QQ:1071235258QQ群:710045715

觉得文章有用就打赏一下文章作者

非常感谢你的打赏,我们将继续给力更多优质内容,让我们一起创建更加美好的网络世界!

支付宝扫一扫打赏

微信扫一扫打赏

error: Sorry,暂时内容不可复制!