mysql从0到0.1系列:SQL是怎么执行的(下)

1.update/insert 语句的执行流程

1.1 流程

1.1.1 insert 流程

在上一篇咱们已经说过,一条SELECT语句执行须要的几个步骤,实际上update/insert语句也差很少,不一样的是多出了redo logbin log两个重要的部分。 这里再写一下整个流程:html

  1. 建立链接

经过TCP/IP链接Mysqlmysql

  1. 解析器

这里mysql经过词法和语法解析会知道这里是update语句。sql

  1. 优化器

生成相应的执行计划,选择最优的执行计划数据库

  1. 执行器

在这一步会去open table,若是该table上有MDL写锁,则等待。若是没有,则加在该表上加MDL读锁。缓存

ps: 这里涉及到一个参数open_tables若是open_tables接近table_cache而且Opened_tables在增长,就表明mysql打开新表的时候会从磁盘读取,没法从缓存拿,也就是会重复的打开.frm文件markdown

以上是mysql server层的执行步骤,咱们主要关注引擎层作的事情。并发

  1. 获取锁信息

经过元数据信息(open_table的时候获取到的),去lock info里查出是否会有相关的锁信息,并把这条insert语句锁信息写入到lock info里oop

innodb1.png 2. 判断数据页是否在innodb buffer中 不在的话须要从磁盘中加载到innodb buffer中性能

  1. 分配undo段,记录undo log优化

  2. 记录undo log 产生的redo log

这里产生的redo log 其实就是undo segment的改动

  1. 对inndb buffer中的数据页进行更新

这里是直接在内存中更新而不是磁盘。

  1. 记录redolog

inndb buffer中的修改记录到redo log buffer里

  1. 插入binlog cache

修改的信息,会按照event的格式,记录到binlog cache中。

  1. 插入change buffer

只有涉及到非聚簇索引的惟一索引而且数据页不在innodb_buffer中才会插入change buffer

  1. 二阶段提交过程

此时在sql中能够当作已经commit

  • prepare

将binlog_cache里的进行flush以及sync操做

  • commit

因为以前该事务产生的redo log已经sync到磁盘了(这里根据innodb_flush_log_at_trx_commit 设置的不一样状况会不同)。因此这步只是在redo log里标记commit。

以上就是整个插入流程,这里仅在mysql 5.6以及innodb引擎的环境下

1.1.2 update 流程

这里update 的流程和insert流程基本同样,暂不赘述,若是有疑问能够留言讨论。

1.1.3 总体流程图

innodb-插入流程 [2].png

2 change buffer

从1.0.x 的版本innodb 引入了change buffer 在以前的版本也称为insert buffer,change buffer 能够看做insert buffer 的升级。

2.1 做用

change buffer 最主要的功能就是加速非聚簇索引的操做,若是咱们以前对数据库有了解就知道,其实影响数据库性能的很关键的一个因素就是磁盘的随机读,而mysql中大部分的优化其实都是在减小磁盘的随机读,这里的change buffer 也是这样。以一个插入过程为例,涉及到两个部分,聚簇索引的插入和非聚簇索引的插入

  • 聚簇索引

这里通常来讲若是聚簇索引是自增主键的话,这里插入就是一个顺序写,就不会涉及到磁盘的随机读,所以很快的。固然若是你这里不设置为自增主键(UUID)就会产生磁盘的随机读,指定值也是。

  • 非汇集索引

非聚簇索引这里也涉及了两种状况,惟一索引和非惟一索引。

  • 惟一索引

惟一索引会回表判断是否冲突,因此这里必定是一个随机读的操做。

  • 非惟一索引

因为数据页的存放实际上是按主键顺序存放的,因此当我插入非惟一索引的时候,其实也会产生随机读。不过有时候非惟一索引也能够是顺序的。

在某些状况下,辅助索引的插入依然是顺序的,或者说是比较顺序的,好比用户购买表中的时间字段。在一般状况下,用户购买时间是一个辅助索引,用来根据时间条件进行查询。可是在插入时倒是根据时间的递增而插入的,所以插入也是"较为"顺序的。——《MySQL 技术内幕 innodb 引擎》

这里咱们不妨思考一下,哪一个步骤产生的随机读能够被优化呢,惟一索引因为须要判断是否冲突须要回表,好像并不能优化,而非惟一索引只是由于B+树的特性致使会产生随机读,这里也许能够优化。 change buffer 正是这样作的,对于非惟一索引的操做实际上是先放在change buffer中而不是直接进入数据页。这里的过程是这样的。判断目标页是否在innodb buffer中,若是在,直接操做innodb buffer,不然就直接放在change buffer 中,这样就不须要从磁盘随机读页数据到innodb buffer中。在下次查询须要访问这个数据页的时候,将数据页读入内存,而后执行 change buffer 中与这个页有关的操做。

2.2 原理

首先,咱们须要知道的是,change buffer 的结构其实是一颗B+树,而且是存放在共享表空间ibdata1中,这里有个容易混淆的概念,change buffer 也是一个数据页,因此也会被加载到inndb_buffer中,而且持久化在ibdata1,因此会存在一部分数据在inndb_buffer中的状况,同时这个change buffer页的改动记录在redo log里。

2.2.1 结构

因为change buffer 是一个B+树的结构,因此这里也区分了叶子节点和非叶子节点。

  • 子节点

由space,marker,offset三个字段组成

  1. space

记录的是表空间id,每一个表都会有一个惟一的space。

  1. marker

保留字段,兼容老版本的insert buffer。

  1. offset

数据因此在页的偏移量。

change_buffer1.png

  • 叶子节点

除了非叶子节点的三个字段以外,还多了metadata,以及实际插入的字段。

  1. metadata

保存了三个字段分别是:

IBUF_REC_OFFSET_COUNTER:数器,用来排序记录,以进入insert buffer的顺序

IBUF_REC_OFFSET_TYPE:操做类型(ibuf_op_t)

IBUF_REC_OFFSET_FLAGS:标志位,当前只有IBUF_REC_COMPACT

change_buffer2.png

2.2.2 merge过程

那么经过上面的内容咱们能够知道,change buffer,其实就至关于一个缓冲池的概念,那么既然是缓冲池就有一个向数据页merge的过程,也就是合并到真正的非聚簇索引中来。 通常来讲,触发merge过程主要有这几种状况:

  1. 加载非聚簇索引页到innodb buffer中
  2. Master Thread定时merge
  3. buffer bitmap 判断辅助索引页空间不足1/32

其中第三点涉及到一个buffer bitmap的概念,这是用来标记辅助索引页空间的。建议你们能够直接去看看《MySQL 技术内幕 innodb 引擎》,这里不赘述了。

2.3 一些问题

2.3.1 如何设置 change buffer

change buffer 的大小,能够经过参数 innodb_change_buffer_max_size 来动态设置。这个参数设置为50的时候,表示 change buffer 的大小最多只能占用innodb buffer的50%。

2.3.2 change buffer的应用场景

因为读操做会触发change buffer的merge过程,因此当读不少的时候,change buffer的效果不会很明显。而且会产生change buffer 的维护代价。所以,对于写多读少的业务来讲,页面在写完之后立刻被访问到的几率比较小,此时 change buffer 的使用效果最好。这种业务模型常见的就是帐单类、日志类的系统。

3 redo log

前面咱们屡次提到redo log,能够发现,基本上因此会写磁盘的操做都会先写redo log,这是由于,若是每一次的更新操做都须要写进磁盘,而后磁盘也要找到对应的那条记录,而后再更新,整个过程 IO 成本、查找成本都很高。

IO成本就是寻址时间和上线文切换所须要的时间,最主要是用户态和内核态的上下文切换。咱们知道用户态是没法直接访问磁盘等硬件上的数据的,只能经过操做系统去调内核态的接口,用内核态的线程去访问。 这里的上下文切换指的是同进程的线程上下文切换,所谓上下文就是线程运行须要的环境信息。 首先,用户态线程须要一些中间计算结果保存CPU寄存器,保存CPU指令的地址到程序计数器(执行顺序保证),还要保存栈的信息等一些线程私有的信息。 而后切换到内核态的线程执行,就须要把线程的私有信息从寄存器,程序计数器里读出来,而后执行读磁盘上的数据。读完后返回,又要把线程的信息写进寄存器和程序计数器。 切换到用户态后,用户态线程又要读以前保存的线程执行的环境信息出来,恢复执行。这个过程主要是消耗时间资源。

3.1 原理

redo log由两部分组成:

  1. redo log buffer
  2. redo log file

其中redo log buffer 是在内存中的,若是redo log 在没有写入file中的时候断电,其实redo log buffer的数据就会丢失。可是这种状况其实极少发生,redo log buffer是接近实时地写入磁盘,当会话发出commit语句时,会实时执行redo log buffer写操做。(注意,这里并非说把更新的数据写入磁盘了,而是把redo log 写入了)

当有一条记录须要更新的时候,InnoDB 引擎就会先把记录写到 redo log里面,并更新内存,这个时候更新就算完成了。同时,InnoDB 引擎会在适当的时候,将这个操做记录更新到磁盘里面。

因为redo log是固定大小的,因此实际上在redo log 快要写满时也会对前面的数据进行刷盘。而且 redo log 是循环写的,因此是一个向后写入,向前刷盘的过程。这个也称做checkpoint 技术。

3.2 配置

innodb_flush_log_at_trx_commit={0|1|2} , 指定什么时候将事务日志刷到磁盘,默认为1。

  • 0表示每秒将"redo log buffer"同步到"os buffer"且从"os buffer"刷到磁盘日志文件中。
  • 1表示每事务提交都将"redo log buffer"同步到"os buffer"且从"os buffer"刷到磁盘日志文件中。
  • 2表示每事务提交都将"redo log buffer"同步到"os buffer"但每秒才从"os buffer"刷到磁盘日志文件中。

3.3 WAL

WAL 的全称是 Write-Ahead Logging,它的关键点就是先写日志,再写磁盘。不只仅是MySQL,不少涉及到写磁盘的系统都会使用这一技术包括,zookeeper,ES等。

3.3.1 WAL的优势

  1. WAL 的优势读和写能够彻底地并发执行,不会互相阻塞(可是写之间仍然不能并发)。
  2. WAL 在大多数状况下,拥有更好的性能(由于无需每次写入时都要写两个文件)。
  3. 磁盘 I/O 行为更容易被预测。使用更少的 fsync()操做,减小系统脆弱的问题。提高性能

4 undo log

上面咱们说到的redo log 能够称为重作日志,由于redo log 是记录数据页的改动,因此能够根据redo log 重作数据页。而undo log则是回滚日志,当一个事务失败以后,就能够经过undo log 进行回滚。在事务篇中再详细说。

5 bin log

除了redo log 以外,mysql还有一个比较重要的日志,bin log(归档日志)。 redo log与bin log的不一样点:

  1. redo log 是innodb特有的日志,而bin log 是mysql server层的日志。
  2. redo log 记录的是数据页的修改,而bin log 记录的是SQL 的逻辑。
  3. redo log 是循环写,而bin log 是追加写。

binlog有两种模式,statement 格式的话是记sql语句, row格式会记录行的内容,记两条,更新前和更新后都有。 

6 二阶段提交

两阶段提交主要是为了保证crash-safe,若是不使用“两阶段提交”,那么数据库的状态就有可能和用它的日志恢复出来的库的状态不一致。


我的博客

西西弗的石头

做者水平有限,如有错误遗漏,请指出。

参考文章

1.详细分析MySQL事务日志(redo log和undo log)

2.MySQL 45讲

参考书籍

  1. 《MySQL 技术内幕 innodb存储引擎》
相关文章
相关标签/搜索