在上一篇咱们已经说过,一条SELECT语句执行须要的几个步骤,实际上update/insert语句也差很少,不一样的是多出了redo log和bin log两个重要的部分。 这里再写一下整个流程:html
经过TCP/IP链接Mysqlmysql
这里mysql经过词法和语法解析会知道这里是update语句。sql
生成相应的执行计划,选择最优的执行计划数据库
在这一步会去open table,若是该table上有MDL写锁,则等待。若是没有,则加在该表上加MDL读锁。缓存
ps: 这里涉及到一个参数open_tables若是open_tables接近table_cache而且Opened_tables在增长,就表明mysql打开新表的时候会从磁盘读取,没法从缓存拿,也就是会重复的打开.frm文件markdown
以上是mysql server层的执行步骤,咱们主要关注引擎层作的事情。并发
经过元数据信息(open_table的时候获取到的),去lock info里查出是否会有相关的锁信息,并把这条insert语句锁信息写入到lock info里oop
2. 判断数据页是否在innodb buffer中 不在的话须要从磁盘中加载到innodb buffer中性能
分配undo段,记录undo log优化
记录undo log 产生的redo log
这里产生的redo log 其实就是undo segment的改动
这里是直接在内存中更新而不是磁盘。
inndb buffer中的修改记录到redo log buffer里
修改的信息,会按照event的格式,记录到binlog cache中。
只有涉及到非聚簇索引的惟一索引而且数据页不在innodb_buffer中才会插入change buffer
此时在sql中能够当作已经commit
将binlog_cache里的进行flush以及sync操做
因为以前该事务产生的redo log已经sync到磁盘了(这里根据innodb_flush_log_at_trx_commit 设置的不一样状况会不同)。因此这步只是在redo log里标记commit。
以上就是整个插入流程,这里仅在mysql 5.6以及innodb引擎的环境下
这里update 的流程和insert流程基本同样,暂不赘述,若是有疑问能够留言讨论。
从1.0.x 的版本innodb 引入了change buffer 在以前的版本也称为insert buffer,change buffer 能够看做insert buffer 的升级。
change buffer 最主要的功能就是加速非聚簇索引的操做,若是咱们以前对数据库有了解就知道,其实影响数据库性能的很关键的一个因素就是磁盘的随机读,而mysql中大部分的优化其实都是在减小磁盘的随机读,这里的change buffer 也是这样。以一个插入过程为例,涉及到两个部分,聚簇索引的插入和非聚簇索引的插入
这里通常来讲若是聚簇索引是自增主键的话,这里插入就是一个顺序写,就不会涉及到磁盘的随机读,所以很快的。固然若是你这里不设置为自增主键(UUID)就会产生磁盘的随机读,指定值也是。
非聚簇索引这里也涉及了两种状况,惟一索引和非惟一索引。
惟一索引会回表判断是否冲突,因此这里必定是一个随机读的操做。
因为数据页的存放实际上是按主键顺序存放的,因此当我插入非惟一索引的时候,其实也会产生随机读。不过有时候非惟一索引也能够是顺序的。
在某些状况下,辅助索引的插入依然是顺序的,或者说是比较顺序的,好比用户购买表中的时间字段。在一般状况下,用户购买时间是一个辅助索引,用来根据时间条件进行查询。可是在插入时倒是根据时间的递增而插入的,所以插入也是"较为"顺序的。——《MySQL 技术内幕 innodb 引擎》
这里咱们不妨思考一下,哪一个步骤产生的随机读能够被优化呢,惟一索引因为须要判断是否冲突须要回表,好像并不能优化,而非惟一索引只是由于B+树的特性致使会产生随机读,这里也许能够优化。 change buffer 正是这样作的,对于非惟一索引的操做实际上是先放在change buffer中而不是直接进入数据页。这里的过程是这样的。判断目标页是否在innodb buffer中,若是在,直接操做innodb buffer,不然就直接放在change buffer 中,这样就不须要从磁盘随机读页数据到innodb buffer中。在下次查询须要访问这个数据页的时候,将数据页读入内存,而后执行 change buffer 中与这个页有关的操做。
首先,咱们须要知道的是,change buffer 的结构其实是一颗B+树,而且是存放在共享表空间ibdata1中,这里有个容易混淆的概念,change buffer 也是一个数据页,因此也会被加载到inndb_buffer中,而且持久化在ibdata1,因此会存在一部分数据在inndb_buffer中的状况,同时这个change buffer页的改动记录在redo log里。
因为change buffer 是一个B+树的结构,因此这里也区分了叶子节点和非叶子节点。
由space,marker,offset三个字段组成
记录的是表空间id,每一个表都会有一个惟一的space。
保留字段,兼容老版本的insert buffer。
数据因此在页的偏移量。
除了非叶子节点的三个字段以外,还多了metadata,以及实际插入的字段。
保存了三个字段分别是:
IBUF_REC_OFFSET_COUNTER:数器,用来排序记录,以进入insert buffer的顺序
IBUF_REC_OFFSET_TYPE:操做类型(ibuf_op_t)
IBUF_REC_OFFSET_FLAGS:标志位,当前只有IBUF_REC_COMPACT
那么经过上面的内容咱们能够知道,change buffer,其实就至关于一个缓冲池的概念,那么既然是缓冲池就有一个向数据页merge的过程,也就是合并到真正的非聚簇索引中来。 通常来讲,触发merge过程主要有这几种状况:
其中第三点涉及到一个buffer bitmap的概念,这是用来标记辅助索引页空间的。建议你们能够直接去看看《MySQL 技术内幕 innodb 引擎》,这里不赘述了。
change buffer 的大小,能够经过参数 innodb_change_buffer_max_size 来动态设置。这个参数设置为50的时候,表示 change buffer 的大小最多只能占用innodb buffer的50%。
因为读操做会触发change buffer的merge过程,因此当读不少的时候,change buffer的效果不会很明显。而且会产生change buffer 的维护代价。所以,对于写多读少的业务来讲,页面在写完之后立刻被访问到的几率比较小,此时 change buffer 的使用效果最好。这种业务模型常见的就是帐单类、日志类的系统。
前面咱们屡次提到redo log,能够发现,基本上因此会写磁盘的操做都会先写redo log,这是由于,若是每一次的更新操做都须要写进磁盘,而后磁盘也要找到对应的那条记录,而后再更新,整个过程 IO 成本、查找成本都很高。
IO成本就是寻址时间和上线文切换所须要的时间,最主要是用户态和内核态的上下文切换。咱们知道用户态是没法直接访问磁盘等硬件上的数据的,只能经过操做系统去调内核态的接口,用内核态的线程去访问。 这里的上下文切换指的是同进程的线程上下文切换,所谓上下文就是线程运行须要的环境信息。 首先,用户态线程须要一些中间计算结果保存CPU寄存器,保存CPU指令的地址到程序计数器(执行顺序保证),还要保存栈的信息等一些线程私有的信息。 而后切换到内核态的线程执行,就须要把线程的私有信息从寄存器,程序计数器里读出来,而后执行读磁盘上的数据。读完后返回,又要把线程的信息写进寄存器和程序计数器。 切换到用户态后,用户态线程又要读以前保存的线程执行的环境信息出来,恢复执行。这个过程主要是消耗时间资源。
redo log由两部分组成:
其中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 技术。
innodb_flush_log_at_trx_commit={0|1|2} , 指定什么时候将事务日志刷到磁盘,默认为1。
WAL 的全称是 Write-Ahead Logging,它的关键点就是先写日志,再写磁盘。不只仅是MySQL,不少涉及到写磁盘的系统都会使用这一技术包括,zookeeper,ES等。
上面咱们说到的redo log 能够称为重作日志,由于redo log 是记录数据页的改动,因此能够根据redo log 重作数据页。而undo log则是回滚日志,当一个事务失败以后,就能够经过undo log 进行回滚。在事务篇中再详细说。
除了redo log 以外,mysql还有一个比较重要的日志,bin log(归档日志)。 redo log与bin log的不一样点:
binlog有两种模式,statement 格式的话是记sql语句, row格式会记录行的内容,记两条,更新前和更新后都有。
两阶段提交主要是为了保证crash-safe,若是不使用“两阶段提交”,那么数据库的状态就有可能和用它的日志恢复出来的库的状态不一致。
我的博客
做者水平有限,如有错误遗漏,请指出。
参考文章
1.详细分析MySQL事务日志(redo log和undo log)
参考书籍