共计 3046 个字符,预计需要花费 8 分钟才能阅读完成。
后面的文章咱们曾经对 MySQL 的查问语句的执行流程进行了阐明,感兴趣的能够去看看:
面试官:MySQL 是如何执行一条查问语句的?
本篇文章咱们来聊聊 MySQL 更新语句的执行原理。
更新流程和查问流程有什么不同呢?
根本流程也是统一的,也就是说,它也要通过解析器、优化器的解决,最初交给执行器。
区别就在于拿到符合条件的数据之后的操作。
养成好习惯,先三连走一波,码字不易,感激你们的每一个点赞、关注和珍藏。
缓冲池 Buffer Pool
首先,InnnoDB 的数据都是放在磁盘上的,存储引擎要操作数据,必须先把磁盘外面的数据加载到内存外面。
这里就有个问题,是不是咱们须要的数据多大,咱们就一次从磁盘加载多少数据到内存呢?
磁盘 I/O 的读写绝对于内存的操作来说是很慢的。如果咱们须要的数据扩散在磁盘的不同的中央,那就意味着会产生很屡次的 I/O 操作。
所以,无论是操作系统的文件管理系统也好,还是存储引擎也好,都有一个预读取的概念。也就是说,当磁盘上的一块数据被读取的时候,很有可能它左近的地位也会马上被读取到,这个就叫做局部性原理。那么这样,咱们罗唆每次多读取一点,而不是用多少读多少。
咱们设定了一个存储引擎从磁盘读取数据到内存的最小的单位,叫做页。操作系统也有页的概念。操作系统的页大小个别是 4K,而在 InnoDB 外面,这个最小的单位默认是 16KB 大小,它是一个逻辑单位。
咱们要操作的数据就在这样的页外面,数据所在的页叫数据页。咱们对于数据页的操作,不是每次都间接操作磁盘,因为磁盘的速度太慢了。
应用了一种缓冲池的技术,也就是把磁盘读到的页放到一块内存区域外面。下一次读取 雷同的页,先判断是不是在这个内存区域外面,如果是,就间接读取,不必再次拜访磁盘。这个内存区域就叫 Buffer Pool。
接下来让咱们先来看下缓冲池在整个 mysql 架构里处于什么样的中央,有一个宏观的意识。
批改数据的时候,先批改缓冲池(Buffer Pool)外面的页。内存的数据页和磁盘数据不统一的时候,咱们把它叫做脏页。InnoDB 外面有专门的后盾线程把 Buffer Pool 的数据写入到磁盘,每隔一段时间就一次性地把多个批改写入磁盘,这个动作就叫做刷脏。
InnoDB 内存构造
上图中能够看到很多的文件(redo log、binlog、undo log),上面咱们就来剖析一下它们。
redo log
思考一个问题:如果 Buffer Pool 外面的脏页还没有刷入磁盘时,数据库宕机或者重启,这些数据将会失落。
为了防止这个问题,InnoDB 把所有对页面的批改操作专门写入一个日志文件,并且在数据库启动时从这个文件进行复原操作(实现 crash-safe)——用它来实现事务的持久性。
这个文件就是磁盘的 redo log(叫做重做日志),对应于 /var/lib/mysql/
目录下的 ib_logfile0
和 ib_logfile1
,每个 48M。
这 种 日 志 和 磁 盘 配 合 的 整 个 过 程,其 实 就 是 MySQL 里 的 WAL 技 术(Write-Ahead Logging),它的关键点就是先写日志,再写磁盘。
问题:同样是写磁盘,为什么不间接写到 db file 外面去?为什么先写日志再写磁盘?
咱们先来理解一下随机 I/O 和程序 I/O 的概念。
如果咱们所须要的数据是随机扩散在磁盘上不同页的不同扇区中,那么找到相应的数据须要等到磁臂旋转到指定的页,而后盘片寻找到对应的扇区,能力找到咱们所须要 的一块数据,一次进行此过程直到找完所有数据,这个就是随机 IO,读取数据速度较慢。
假如咱们曾经找到了第一块数据,并且其余所需的数据就在这一块数据后边,那么就不须要从新寻址,能够顺次拿到咱们所需的数据,这个就叫程序 IO。
刷盘是随机 I/O,而记录日志是程序 I/O(间断写的),程序 I/O 效率更高。因而先把批改写入日志文件,在保障了内存数据的安全性的状况下,能够提早刷盘机会,进而晋升零碎吞吐。
这个 redo log 有什么特点?
- redo log 是 InnoDB 存储引擎实现的,并不是所有存储引擎都有。反对解体复原是 InnoDB 的一个个性。
- 不是记录数据页更新之后的状态,而是记录这个页做了什么改变,属于物理日志。
- redo log 的大小是固定的,后面的内容会被笼罩,一旦写满,就会触发 redo log 到磁盘的同步,以便腾出空间记录前面的批改。
除了 redo log 之外,还有一个跟批改无关的日志,叫做 undo log。redo log 和 undo log 与事务密切相关,统称为事务日志。
undo log
undo log(撤销日志或回滚日志)记录了事务产生之前的数据状态(不包含 select)。如果批改数据时出现异常,能够用 undo log 来实现回滚操作(放弃原子性)。
在执行 undo 的时候,仅仅是将数据从逻辑上复原至事务之前的状态,而不是从物理页面上操作实现的,属于逻辑格局的日志。
undo Log 的数据默认在零碎表空间 ibdata1 文件中,因为共享表空间不会主动膨胀,也能够独自创立一个 undo 表空间。
有了这些日志之后,咱们来总结一下一个 mysql 更新语句执行的流程,这是一个简化的过程。name 原值为
mayun
;
update user set name = 'jiangwang' where id=1;
- 事务开始,从内存或磁盘取到这条数据,返回给 Server 的执行器;
- 执行器批改这一行数据的值为 jiangwang;
- 记录 name=mayun 到 undo log;
- 记录 name=jiangwang 到 redo log;
- 调用存储引擎接口,在内存(Buffer Pool)中批改 name=jiangwang;
- 事务提交。
内存和磁盘之间,工作着很多后盾线程,什么是后盾线程呢?
后盾线程的次要作用是负责刷新内存池中的数据和把批改的数据页刷新到磁盘。后盾线程分为:master thread
,IO thread
,purge thread
,page cleaner thread
。
Binlog
除了 InnoDB 架构中的日志文件,MySQL 的 Server 层也有一个日志文件,叫做 binlog,它能够被所有的存储引擎应用。
binlog 以事件的模式记录了所有的 DDL 和 DML 语句(因为它记录的是操作而不是数据值,属于逻辑日志),能够用来做 主从复制和数据恢复。
跟 redo log 不一样,它的文件内容是能够追加的,没有固定大小限度。
在开启了 binlog 性能的状况下,咱们能够把 binlog 导出成 SQL 语句,把所有的操作重放一遍,来实现数据的复原。
binlog 的另一个性能就是用来实现主从复制,它的原理就是从服务器读取主服务器的 binlog,而后执行一遍。
有了这两个日志之后,咱们来看一下一条更新语句是怎么执行的(redo 不能一次写入了):
例如一条语句:update user set name='小马' where id=1;
- 先查问到这条数据,如果有缓存,也会用到缓存。
- 把 name 改成
小马
,而后调用引擎的 API 接口,写入这一行数据到内存,同时记录 redo log。这时 redo log 进入 prepare 状态,而后通知执行器,执行实现了,能够随时提交。 - 执行器收到告诉后记录 binlog,而后调用存储引擎接口,设置 redo log 为 commit 状态。
- 更新实现。
小结
MySQL 的更新语句的执行流程的原理,下面也曾经说得很分明了,最初咱们总结一下重点:
- 先记录到内存(buffer pool),再写日志文件。
- 记录 redo log 分为两个阶段(prepare 和 commit)。
- 存储引擎和 server 别离记录不同的日志。
- 先记录 redo,再记录 binlog。