一条SQL更新语句的执行过程解析

目录
  • 一、执行过程
  • 二、日志模块
    • 1、物理日志redo log
      • redo log的使用场景
      • redolog配置
    • 2、逻辑日志binlog
      • 两阶段提交
      • binlog使用场景

前言:

上一篇文章讲解了SQL查询语句执行的过程,并介绍了执行过程中涉及的处理模块。回顾一下,一条查询语句的执行过程一般是经过连接器、分析器、优化器、执行器等功能模块,最后到达存储引擎。

一、执行过程

在SQL查询语句执行的过程中,我们学习过 SQL 语句基本的执行链路,这里我再把那张图拿过来,你也可以先简单看看这个图回顾下。首先,可以确定的说,查询语句的那一套流程,更新语句也是同样会走一遍。

你执行语句前要先连接数据库,这是连接器的工作。

使用数据库的第一步就是先连接到这个数据库上,这时候作为接待的就是连接器。连接器负责跟客户端建立连接获取权限维持和管理连接。连接命令:

mysql mysql -h主机地址 -u用户名 -p

输完命令之后,你就需要在交互对话里面输入密码。虽然密码也可以直接跟在 -p 后面写在命令行中,但这样可能会导致你的密码泄露,不建议直接跟着输入。

下图都是我的电脑操作(mac+ mamp)。

如果输入账号或密码错误会提示(1045):

回归正题,我们还是从一个表的一条更新语句说起,下面是这个表的创建语句,这个表有一个主键 id 和一个字符串类型的字段 name 以及一个 decimal类型的字段score :

CREATE TABLE `s_info` (
  `id` int(11) NOT NULL AUTO_INCREMENT COMMENT '自增id',
  `name` varchar(255) NOT NULL DEFAULT '' COMMENT '名字',
  `score` decimal(5,2) NOT NULL DEFAULT '0.00' COMMENT '分数',
  PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8 COMMENT='学生分数表';

并加入几条测试数据:

INSERT INTO `test`.`s_info` ( `name`, `score`) VALUES ( '张一', 80.00), ( '赵二', 90.00),( '王三', 100.00), ( '李四', 98.00),( '马五', 87.00);

结果如下:

如果要将 id=4 这一行的值加 1,SQL 语句就会这么写:

update s_info set score=score+1 where id = 4;

执行结果如图:

在一个表上有更新的时候,跟这个表有关的查询缓存会失效,所以这条语句就会把表上所有缓存结果都清空。这也就是我们一般不建议使用查询缓存的原因。

分析器会通过词法和语法解析知道这是一条更新语句。

优化器决定要使用 ID 这个索引。

执行器负责具体执行,找到这一行,然后更新。

与查询流程不一样的是,更新流程还涉及两个重要的日志模块,redo log(重做日志,物理日志)和 binlog(归档日志,逻辑日志)。如果接触过 MySQL,这两个词肯定是绕不过的。

二、日志模块

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

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

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

1、物理日志redo log

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

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

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

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

(1)、打开账本,找到赊账人的记录,进行追加赊账记录; (2)、先把赊账人的记录写到黑板上,待客流量少的时刻,再更新到赊账账目上。 如果掌柜使用第一种方法的话,每当有人要赊账的话,首先他需要打开厚厚的账本,一页一页查找该顾客的姓名,然后进行登记。你想一下,如果赊账的人不多,掌柜找赊账人的记录轻松点,如果赊账本有好几本的话,一本一本的找,掌柜看的都头疼。

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

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

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

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

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

如下图所示:

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

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

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

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

redo log的使用场景

用于系统奔溃恢复。

redolog配置

(1)、缓存大小

innodb_log_buffer_size 默认大小 16MB。 查看相关配置sql:

SHOW GLOBAL VARIABLES LIKE '%innodb_log%';

结果如图所示:

(2)、刷盘策略

提交事物写入磁盘中,会根据这个配置的策略进行同步。

  • 0 提交事物的时候不会把redo log buffer 里的数据刷入磁盘。
  • 1 提交事物的时候,必须把日志刷入磁盘中,可以严格保证数据不丢失 (默认且推荐策略)。
  • 2 提交事物的时候,先把日志刷入磁盘文件对应的 os cache 缓存里,隔一段时间再把数据刷入磁盘。

查看相关参数SQL:

SHOW GLOBAL VARIABLES LIKE '%sync_binlog%';

2、逻辑日志binlog

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

为什么会有两份日志呢?

最开始 MySQL 里并没有 InnoDB 引擎。MySQL 自带的引擎是 MyISAM,但是 MyISAM 没有 crash-safe 的能力,binlog 日志只能用于归档。而 InnoDB 是另一个公司以插件形式引入 MySQL 的,既然只依靠 binlog 是没有 crash-safe 能力的,所以 InnoDB 使用另外一套日志系统——也就是 redo log 来实现 crash-safe 能力。

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

这两种日志有以下不同:

\ redo log binlog
1 InnoDB 引擎特有的。 binlog 是 MySQL 的 Server 层实现的,所有引擎都可以使用。
2 redo log 是物理日志,记录的是“在某个数据页上做了什么修改。” binlog 是逻辑日志,并且由mysql数据库的service层执行。记录的是这个语句的原始逻辑,比如 “给 ID=4 这一行的 score 字段加 1 ”。
3 redo log 是循环写的,空间固定会用完。 binlog 是可以追加写入的。可以通过 max_binlog_size 参数设置bin log文件大小,当文件大小达到某个值时,会生成新的文件来保存日志。

对这两个日志的有了概念性理解,我们再来看执行器和 InnoDB 引擎在执行这个简单的 update 语句时的内部流程。

  • (1)、执行器先找引擎取 ID=4 这一行。ID 是主键,引擎直接用树搜索找到这一行。如果 ID=4 这一行所在的数据页本来就在内存中,就直接返回给执行器;否则,需要先从磁盘读入内存,然后再返回,并且对这行记录加独占锁,把更新行记录的旧值写入 undo log(以便回滚)。
  • (2)、执行器拿到引擎给的行数据,把这个值加上 1,比如原来是 N,现在就是 N+1,得到新的一行数据,再调用引擎接口写入这行新数据。
  • (3)、引擎将这行新数据更新到内存中,同时将这个更新操作记录到 redo log 里面,此时 redo log 处于 prepare状态。然后告知执行器执行完成了,随时可以提交事务
  • (4)、执行器生成这个操作的 binlog,再按策略刷到 binlog文件(磁盘中)。
  • (5)、执行器调用引擎的提交事务接口,引擎把刚刚写入的 redo log 改成提交(commit)状态,更新完成。

update 语句的执行流程图如下,其中图中黄色框表示是在 InnoDB 内部执行的,绿色框表示是在执行器中执行的。

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

两阶段提交

为什么必须有“两阶段提交”呢?这是为了让两份日志之间的逻辑一致

binlog 会记录所有的逻辑操作,并且是采用“追加写”的形式。如果你的 DBA 承诺说半个月内可以恢复,那么备份系统中一定会保存最近半个月的所有 binlog,同时系统会定期做整库备份。

当需要恢复到指定的某一秒时,比如某天下午点发现上午11点有一次误删表,需要找回数据,那我们可以这么做:

首先,找到最近的一次全量备份,如果你运气好,可能就是昨天晚上的一个备份,从这个备份恢复到临时库; 然后,从备份的时间点开始,将备份的 binlog 依次取出来,重放到中午误删表之前的那个时刻。 这样你的临时库就跟误删之前的线上库一样了,然后你可以把表数据从临时库取出来,按需要恢复到线上库去。

为什么日志需要“两阶段提交”?

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

仍然用前面的 update 语句来做例子。假设当前 ID=4 的行,字段 score 的值是 98.00 ,再假设执行 update 语句过程中在写完第一个日志后,第二个日志还没有写完期间发生了 crash,会出现什么情况呢?

(1) 、先写 redo log 后写 binlog。

假设在 redo log 写完,binlog 还没有写完的时候,MySQL 进程异常重启。由于我们前面说过的,redo log 写完之后,系统即使崩溃,仍然能够把数据恢复回来,所以恢复后这一行 score 的值是 99.00。但是由于 binlog 没写完就 crash 了,这时候 binlog 里面就没有记录这个语句。因此,之后备份日志的时候,存起来的 binlog 里面就没有这条语句。如果需要用这个 binlog 来恢复临时库的话,由于这个语句的 binlog 丢失,这个临时库就会少了这一次更新,恢复出来的这一行 的值就是 98.00,与原库的值不同。

(2)、先写 binlog 后写 redo log

如果在 binlog 写完之后 crash,由于 redo log 还没写,崩溃恢复以后这个事务无效,所以这一行 score 的值是 98.00。但是 binlog 里面已经记录了“把 score 从 98.00 改成 99.00”这个日志。所以,在之后用 binlog 来恢复的时候就多了一个事务出来,恢复出来的这一行 score 的值就是 99.00,与原库的值不同。

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

疑问:这样的操作概率是不是很低,平时也没有什么动不动就需要恢复临时库的场景呀?

不只是误操作后需要用这个过程来恢复数据。当你需要扩容的时候,也就是需要再多搭建一些备库来增加系统的读能力的时候,现在常见的做法也是用全量备份加上应用 binlog 来实现的,这个“不一致”就会导致你的线上出现主从数据库不一致的情况。

简单来说,redo log 和 binlog 都可以用于表示事务的提交状态,而两阶段提交就是让这两个状态保持逻辑上的一致。

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

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

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

 SHOW GLOBAL VARIABLES LIKE '%innodb_flush%';

如图所示:

binlog使用场景

在实际场景中, bin log 的主要场景有两点,一点是主从复制,另一点是数据恢复。

  • (1)、主从复制:在master端开启 bin log ,然后将 binlog 发送给各个slaver端,slaver端读取 binlog 日志,从而使得主从数据库中数据一致。
  • (2)、数据恢复:通过 binlog 获取想要恢复的时间段数据

到此这篇关于一条SQL更新语句的执行过程解析的文章就介绍到这了,更多相关SQL更新语句内容请搜索我们以前的文章或继续浏览下面的相关文章希望大家以后多多支持我们!

(0)

相关推荐

  • 通过MySQL日志实时查看执行语句以及更新日志的教程

    查看MySQL执行的语句 想实时查看MySQL所执行的sql语句,类似mssql里的事件探查器.对my.ini文件进行设置,打开文件进行修改: basedir=E:/MySQL 4.0.12 #bind-address=192.168.15.196 datadir=E:/MySQL 4.0.12/data #language=E:/MySQL 4.0.12/share/your language directory #slow query log#=slowqueris.log #tmpdir#

  • sql更新语句中update set from用法实现

    执行一般的sql更新语句为update table_name set column_name=value where column_name1=value1;但是我们有时候需要将某个表用的字段根据两个表中相关字段更新为另一个表中某个字段的数据,即a表中主键aid在b表中有个外键bid相关,b表中字段b需要根据a.b表对应id更新为a表中字段c的数据,如果根据以上的sql一条一条的更新会非常浪费时间,所以我们需要用到update set from的语句用法. 在数据库中有一张表为student,如

  • Java反射 JavaBean对象自动生成插入,更新,删除,查询sql语句操作

    通过反射根据提供的表名.POJO类型.数据对象自动生成sql语句. 如名为 User 的JavaBean与名为 user 的数据库表对应,可以提供一个封装有数据的User对象user,根据user中含有的数据自动生成sql语句. 1.生成插入语句(插入user中包含的非空数据的语句): String insertSql = getInsertSql("user", User.class, user); 2.生成更新语句(user中id不能为空): String updateSql =

  • 记一次MySQL更新语句update的踩坑

    背景 最近在一次线上作业过程中执行了一句DML语句,本以为万无一失,结果应用反馈说没有更新,数据还是没有变,最后经过排查才发现是我语句写错了,导致update语句执行的结果与预期不符. 情景再现 为了方便演示,建立一张用户表,同时插入五条数据. create table user( id int(12) comment '用户主键id', name varchar(36) comment '用户名', age int(12) comment '年龄'); insert into user val

  • 基于更新SQL语句理解MySQL锁定详解

    前言 MySQL数据库锁是实现数据一致性,解决并发问题的重要手段.数据库是一个多用户共享的资源,当出现并发的时候,就会导致出现各种各样奇怪的问题,就像程序代码一样,出现多线程并发的时候,如果不做特殊控制的话,就会出现意外的事情,比如"脏"数据.修改丢失等问题.所以数据库并发需要使用事务来控制,事务并发问题需要数据库锁来控制,所以数据库锁是跟并发控制和事务联系在一起的. 本文主要描述基于更新SQL语句来理解MySQL锁定.下面话不多说了,来一起看看详细的介绍吧 一.构造环境 (root@

  • Mysql跨表更新 多表update sql语句总结

    假定我们有两张表,一张表为Product表存放产品信息,其中有产品价格列Price:另外一张表是ProductPrice表,我们要将ProductPrice表中的价格字段Price更新为Price表中价格字段的80%. 在Mysql中我们有几种手段可以做到这一点,一种是update table1 t1, table2 ts ...的方式: 复制代码 代码如下: UPDATE product p, productPrice pp SET pp.price = pp.price * 0.8 WHER

  • SQL UPDATE 更新语句用法(单列与多列)

    Update 语句 Update 语句用于修改表中的数据. 语法: UPDATE 表名称 SET 列名称 = 新值 WHERE 列名称 = 某值 例如: Person表: LastName FirstName Address City Gates Bill Xuanwumen 10 Beijing Wilson   Champs-Elysees   更新某一行中的一个列 我们为 lastname 是 "Wilson" 的人添加 firstname: UPDATE Person SET

  • 一条SQL更新语句的执行过程解析

    目录 一.执行过程 二.日志模块 1.物理日志redo log redo log的使用场景 redolog配置 2.逻辑日志binlog 两阶段提交 binlog使用场景 前言: 上一篇文章讲解了SQL查询语句执行的过程,并介绍了执行过程中涉及的处理模块.回顾一下,一条查询语句的执行过程一般是经过连接器.分析器.优化器.执行器等功能模块,最后到达存储引擎. 一.执行过程 在SQL查询语句执行的过程中,我们学习过 SQL 语句基本的执行链路,这里我再把那张图拿过来,你也可以先简单看看这个图回顾下.

  • 分析mysql中一条SQL查询语句是如何执行的

    目录 一.MySQL 逻辑架构概览 二.连接器(Connector) 三.查询缓存(Query Cache) 四.解析器(Parser) 五.优化器(Optimizer) 六.执行器 七.小结 一.MySQL 逻辑架构概览 MySQL 最重要.最与众不同的特性就是它的可插拔存储引擎架构(pluggable storage engine architecture),这种架构的设计将查询处理及其他系统任务和数据的存储/提取分离开来.来看官网的解释: The MySQL pluggable stora

  • MySQL中一条SQL查询语句是如何执行的

    目录 前言 1. 处理连接 1.1 客户端和服务端的通信方式 1.1.1 TCP/IP协议 1.1.2 UNIX域套接字 1.1.3 命名管道和共享内存 1.2 权限验证 1.3 查看MySQL连接 2. 解析与优化 2.1 查询缓存 2.2 解析器 & 预处理器(Parser & Preprocessor) 2.2.1 词法解析 2.2.2 语法分析 2.2.3 预处理器 2.3 查询优化器(Optimizer)与查询执行计划 2.3.1 什么是查询优化器? 2.3.2 优化器究竟做了什

  • 一篇文章弄懂MySQL查询语句的执行过程

    前言 需要从数据库检索某些符合要求的数据,我们很容易写出 Select A B C FROM T WHERE ID = XX  这样的SQL,那么当我们向数据库发送这样一个请求时,数据库到底做了什么? 我们今天以MYSQL为例,揭示一下MySQL数据库的查询过程,并让大家对数据库里的一些零件有所了解. MYSQL架构 mysql架构 MySQL 主要可以分为 Server 层和存储引擎层. Server层 包括连接器.查询缓存.分析器.优化器.执行器等,所有跨存储引擎的功能都在这一层实现,比如存

  • mysql update语句的执行过程详解

    以前有过一篇关于MySQL查询语句的执行过程,这里总结一下update语句的执行过程.由于update涉及到数据的修改,所以,很容易推断,update语句比select语句会更复杂一些. 1,准备 创建一张test表 CREATE TABLE `test` ( `id` int(11) NOT NULL AUTO_INCREMENT, `c` int(11) NOT NULL DEFAULT '0' COMMENT '数值', PRIMARY KEY (`id`) ) ENGINE=InnoDB

  • 详解MySQL 查询语句的执行过程

    首先先简单的将一个查询语句背后MySQL做了什么捋一捋: 客户端发送一条查询给服务器. 服务器先检查查询缓存,如果命中了缓存,则立刻返回存储在缓存中的结果.否则进入下一个阶段. 服务器端进行SQL解析,预处理,再由优化器生成对应的执行计划. MySQL根据优化器生成的执行计划,调用存储引擎的API来执行查询. 将结果返回给客户端. 接着我们就将这个过程中的这些步骤详细的进行展开. 1.客户端和服务器端之间的通信方式 客户端和服务器之间的通信是一种半双工的通信,即在同一时刻,只能有一方向另一方发送

  • 探讨JavaScript语句的执行过程

    废话不多说,直奔主题了.javascript的运行原理总结如下: 1.按照html文档流顺序执行javascript代码 浏览器是按照文档流从上到下逐步解析页面结构和信息的,javascript代码作为嵌入的脚本作为html文档的组成部分,所以javascript代码在加载时的执行顺序也是根据脚本标签<script>的出现顺序来确定的. 如果通过脚本标签<script>的src属性来引入外部.js文件,那么它也将按照其语句出现的顺序来执行,而且执行过程是文档加载的一部分.不会因为是

  • T-SQL 查询语句的执行顺序解析

    (5)SELECT (5-2) DISTINCT(5-3)TOP(<top_specification>)(5-1) <select_list> (1)FROM (1-J) <left_table> <join_type> JOIN <right_table> on <on_predicate> (2)WHERE <where_predicate> (3)GROUP BY <group_by_specificatio

  • sql获取存储过程返回数据过程解析

    这篇文章主要介绍了sql获取存储过程返回数据过程解析,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友可以参考下 就是在执行存储后,获取存储过程执行的数据并作为其他应的二次使用, 其实在代码中可以说是调用类似,具体操作如下: 创建一个存储过程: use [库名] go set ansi_null on GO SET QUOTED_IDENTIFIER ON GO CREATE PROCEDURE DBO.P_TEST ( @RBACK VARCHAR(20)

随机推荐