您的位置 首页 java

一条SQL更新语句是如何执行的?

比较喜欢的一段话:不经一番寒彻骨,怎得梅花扑鼻香,学习是枯燥的请大家坚持!

对于一个 SQL语句 的更新来说,前面的流程都可以说类似的,通过解析器进行语法分析,优化器优化,执行引擎去执行,这个都没有什么问题,重点在于多了一点东西,那就是redo_log、undo_log和binlog。

执行过程

一条SQL更新语句是如何执行的?

  • 客户端发送更新命令到 MySQL 服务器,经过处理连接、解析优化等步骤;
  • Server层向 InnoDB 存储引擎要id=1的这条记录;
  • 存储引擎先从缓存中查找这条记录,有的话直接返回,没有则从磁盘加载到缓存中然后返回;
  • Server层执行器修改这条记录的name字段值;
  • 存储引擎更新修改到内存中;
  • 存储引擎记录redo日志,并将状态设置为prepare状态;
  • 存储引擎通知执行器,修改完毕,可以进行事务提交;
  • Server先写了个binlog;
  • Server提交事务;
  • 存储引擎将redo日志中和当前事务相关的记录状态设置为commit状态。

日志模块

在说日志模块前,先说一下什么是物理日志和逻辑日志。

物理日志 :通俗的讲,就是只有”我”自己可以使用,别人无法共享我的”物理格式,私有化。

逻辑日志: 可以给别的引擎使用,是所有引擎共享的。

redo log(重做日志)

「re」在英文中的词根含义是“重新”,redo就是「重新做」的意思,顾名思义就是MySQL根据这个日志文件重新进行操作

redo log是 InnoDB 引擎特有的日志,又被称为重写日志, 用来记录事务操作的变化,记录的是数据修改之后的值,不管事务提交是否成功,都会被记录下来。它让MySQL拥有了崩溃恢复能力。

比如MySQL实例挂了或宕机了,重启时,InnoDB存储引擎会使用redo log恢复数据,保证数据的持久性与完整性。

以常见的古代酒馆掌柜记账举例:

酒馆掌柜有一个黑板,赊账的人少时就记在黑板上如果赊账人多的话,由于黑板的空间大小有限,所以他又需要额外准备一本账本,专门记录所有赊账的账目。 如果有人要赊账的话,一般老板有两种做法:

  • 打开账本,找到赊账人的记录,进行追加赊账记录;
  • 先把赊账人的记录写到黑板上,待客流量少的时刻,再更新到赊账账目上。

如果掌柜使用第一种方法的话,每当有人要赊账的话,首先他需要打开厚厚的账本,一页一页查找该顾客的姓名,然后进行登记。你想一下,如果赊账的人不多,掌柜找赊账人的记录轻松点,如果赊账本有好几本的话,一本一本的找,掌柜看的都头疼。就存在一个查找顾客的姓名效率的问题。

在 MySQL里也有这个问题。如果每一次的更新操作都需要写进磁盘,然后磁盘也要找到对应的那条记录,然后再更新,整个过程 IO 成本、查找成本都很高。为了解决这个问题,MySQL 的设计者就用了类似酒店掌柜粉板的思路来提升更新效率。

而黑板和账本配合的整个过程,其实就是 MySQL 里经常说到的 WAL (Write Ahead Logging)技术,它的关键点就是先写日志,再写磁盘,

当有一条记录需要更新的时候,InnoDB 引擎就会先把记录写到 redo log(黑板)里面,并更新内存,这个时候更新就算完成了。同时,InnoDB 引擎会在适当的时候,将这个操作记录更新到磁盘里面,而这个更新往往是在系统比较空闲的时候做,这就像打烊以后掌柜做的事。

如果某天赊账的特别多,粉板写满了,又怎么办呢?这个时候掌柜只好放下手中的活儿,把黑板中的一部分赊账记录更新到账本中,然后把这些记录从黑板上擦掉,为记新账腾出空间。

与此类似,InnoDB 的 redo log 是固定大小的,比如可以配置为一组 4 个文件,每个文件的大小是 1GB,那么这块“黑板”总共就可以记录 4GB 的操作。从头开始写,写到末尾就又回到开头循环写, 如下图所示:

一条SQL更新语句是如何执行的?

write pos是当前记录的位置,一边写一边后移,写到4号文件末尾就回到1号文件开头。 check point 是当前要把记录写入到数据文件的位置,也是后移并且循环的。

如果和上面老板黑板场景结合起来描述的话,write pos就是老板在黑板上顺序写入赊账人记录位置,对于mysql来说, write pos后移;而check point就是老板把黑板上记录写入到赊账本上的位置,当老板写入到赊账本上后,就会把粉板上该记录擦除掉,对于mysql来说,check point后移。

write pos 和 checkpoint 之间的是“黑板”上还空着的部分,可以用来记录新的操作。如果 write pos 追上 check point,表示“黑板”满了,这时候不能再执行新的更新,得停下来先擦掉一些记录,把 check point 推进一下。

有了 redo log,InnoDB 就可以保证即使数据库发生异常重启,之前提交的记录都不会丢失,这个能力称为crash-safe。

在MySQL中,如果每一次的更新操作都需要写进磁盘,然后磁盘也要找到对应的那条记录,然后再更新,整个过程IO成本、随机查找成本(因为操作的数据散落在磁盘各处)都很高。为了解决这个问题,MySQL使用了 WAL 技术(Write Ahead Logging),也称为日志先行技术,它的关键点就是先写日志,再写磁盘。写日志虽然也是写磁盘,但是它是顺序写,相比随机写开销更小,能提升语句执行的性能。WAL技术保证了数据一致性和持久性,并且提升语句执行性能。

缓冲池内存中的数据已经更新好了,那么接下来就该开始写redo_log了,只是redo_log也不是直接写文件的,一般都是这样对吧,直接写的话性能太差了,所以就有redo_log_buffer叫做redo_log缓冲。

一条SQL更新语句是如何执行的?

在写redo_log的时候先把数据写到redo_log缓冲区,然后异步写入磁盘,很显然,极端情况下会有丢失数据的可能。控制这个刷盘策略的的参数叫做 innodb_flush_log_at_trx_commit

innodb_flush_log_at_trx_commit参数说明

参数

描述

0

提交事务时不会写入磁盘。

1

提交事务一定会进行一次刷盘,同步当然性能最差,但是也最安全。(默认且推荐策略)

2

提交事务的时候,先把日志刷入磁盘文件对应的 os cache 缓存里,隔一段时间再把数据刷入磁盘。

redo log的存在使得数据库具有crash-safe能力,即如果Mysql 进程异常重启了,系统会自动去检查redo log,将未写入到Mysql的数据从redo log恢复到Mysql中去。

数据库的crash-safe保证的是:

  1. 如果客户端收到事务成功的消息,事务就一定持久化了;
  2. 如果客户端收到事务失败(比如主键冲突、回滚等)的消息,事务就一定失败了;
  3. 如果客户端收到“执行异常”的消息或者什么也没收到,应用需要重连后通过查询当前状态来继续后续的逻辑。此时数据库只需要保证内部(数据和日志之间,主库和备库之间)一致就可以了,即事务一致性。

binlog(归档日志)

MySQL 整体来看,其实就有两块:一块是 Server层,它主要做的是 MySQL 功能层面的事情;还有一块是引擎层,负责存储相关的具体事宜。 redo log 是 InnoDB 引擎特有的日志,而 Server 层也有自己的日志,称为 binlog(归档日志)。

为什么有了redo日志还需要 binlog

  • 因为最开始MySQL里并没有InnoDB存储引擎。MySQL自带的引擎是 MyISAM ,但是 MyISAM没有崩溃恢复的能力,InnoDB后来以插件的形式被引入,顺便带来了redo日志;
  • binlog日志是用来归档的,binlog以事件的形式记录了所有的 DDL DML 语句(因为它记录的是操作而不是数据值,属于逻辑日志),但是不具备 宕机 恢复的功能,因为可能没有来得及刷新脏页,造成脏页数据的丢失,而这些操作也没有保存到binlog中从而造成数据丢失+;
  • binlog记录的是关于一个事务的具体操作内容,即该日志是逻辑日志。而redo日志记录的是关于每个页的更改的物理情况。

binlog是mysql数据库service层的,是所有存储引擎共享的日志模块,它用于记录数据库执行的写入性操作,也就是在事务commit阶段进行记录,以 二进制 的形式保存于磁盘中。

这两种日志有以下不同:

redo log

binlog

InnoDB 引擎特有的。

binlog 是 MySQL 的 Server 层实现的,所有引擎都可以使用。

redo log 是物理日志,记录的是“在某个数据页上做了什么修改。”

binlog 是逻辑日志,并且由 mysql数据库 的service层执行。记录的是这个语句的原始逻辑,比如 “给 ID=4 这一行的 score 字段加 1 ”。

redo log 是循环写的,空间固定会用完。

binlog 是可以追加写入的。可以通过 max_binlog_size 参数设置bin log文件大小,当文件大小达到某个值时,会生成新的文件来保存日志。

update 语句的执行流程图如下

一条SQL更新语句是如何执行的?

重点看下最后三步,将 redo log 的写入拆成了两个步骤:prepare 和 commit,这就是”两阶段提交”。

两阶段提交(prepare&commit)

两阶段提交不是MySQL的专利,两阶段提交是一种跨系统维持数据逻辑一致性的常见方案,尤其在 分布式事务 上,所以请读者重点体会思想

我们把redo日志的提交分成两步,两步中redo日志的状态分别是prepare和commit。步骤如下

  • InnoDB存储引擎将更改更新到内存中后,同时将这个更新操作记录到redo日志里面,此时redo日志处于prepare状态;
  • 执行器生成这个操作的binlog,并将binlog刷盘;
  • 执行器调用InnoDB的提交事务接口,InnoDB把刚刚写入的redo日志改成commit状态。至此,所有操作完成。

为什么必须有“两阶段提交”呢?由于 redo log 和 binlog 是两个独立的逻辑,两阶段提交这是为了让两份日志之间的逻辑一致。

如果不用两阶段提交,要么就是先写完 redo log 再写 binlog,或者采用反过来的顺序。我们看看这两种方式会有什么问题。

先写入redo log,后写入binlog

在写完redo log之后,数据此时具有crash-safe能力,因此系统崩溃,数据会恢复成事务开始之前的状态。但是,若在redo log写完时候,binlog写入之前,系统发生了宕机。此时binlog没有对上面的更新语句进行保存,导致当使用binlog进行数据库的备份或者恢复时,就少了上述的更新语句。从而使得id=2这一行的数据没有被更新。

先写入binlog,后写入redo log

写完binlog之后,所有的语句都被保存,所以通过binlog复制或恢复出来的数据库中id=2这一行的数据会被更新为a=1。但是如果在redo log写入之前,系统崩溃,那么redo log中记录的这个事务会无效,导致实际数据库中id=2这一行的数据并没有更新。

可以看到,如果不使用“两阶段提交”,那么数据库的状态就有可能和用它的日志恢复出来的库的状态不一致。

对于InnoDB引擎而言,在每次事务commit提交时才会记录binlog日志,此时记录仍然在内存中,那么什么时候存储到磁盘中呢?mysql通过 sync_binlog 参数控制binlog刷盘时机,取值范围:0~N:

  • 0:由系统自行判断何时写入磁盘;
  • 1:每次事务commit的时候都要将binlog写入磁盘;
  • N:每N个事务commit,才会将binlog写入磁盘;

注:该值默认为0,采用操作系统机制进行缓冲数据同步。 sync_binlog 参数建议设置为1,这样每次事务commit时就会把bin log写入磁盘中,这样也可以保证mysql异常重启之后 bin log日志不会丢失。

binlog日志的作用

主从复制

binlog是实现MySQL主从复制功能的核心组件。

master节点会将所有的写操作记录到binlog中,slave节点会有专门的I/O线程读取master节点的binlog,将写操作同步到当前所在的slave节点。

一条SQL更新语句是如何执行的?

数据恢复

通过 binlog 获取想要恢复的时间段数据

undo log(回滚日志)

undo log(撤销日志或回滚日志)记录了事务发生之前的数据状态,分为insert undo log和update undo log。

如果修改数据时出现异常,可以用 undo log来实现回滚操作(保持原子性)。可以理解为undo日志记录的是反向的操作,比如INSERT操作会记录DELETE,UPDATE会记录UPDATE之前的值,和redo日志记录在哪个物理页面做了什么操作不同,所以这是一种逻辑格式的日志。

undo日志和redo日志与事务密切相关,被统称为「事务日志」。

一条SQL更新语句是如何执行的?

小伙伴们有兴趣想了解内容和更多相关学习资料的请点赞收藏+评论转发+关注我,后面会有很多干货。我有一些面试题、架构、设计类资料可以说是程序员面试必备!所有资料都整理到网盘了,需要的话欢迎下载!私信我回复【111】即可免费获取

一条SQL更新语句是如何执行的?

作者:灵魂的沉浮
链接:

文章来源:智云一二三科技

文章标题:一条SQL更新语句是如何执行的?

文章地址:https://www.zhihuclub.com/197950.shtml

关于作者: 智云科技

热门文章

网站地图