写在开始

本系列源自极客时间 MySQL 专栏,整理而成

在执行下面这个查询语句时的执行的流程是怎么样的?

mysql

看过相关资料的同学都可能知道执行流程大概是这样的:

其执行过程为:连接、查询缓存、词法分析,语法分析,语义分析,构造执行树,生成执行计划、执行器执行计划。

大体来说,MySQL 可以分为 Server 层和存储引擎层两部分。

MySQL 的逻辑架构图

Server 层包括连接器、查询缓存、分析器、优化器、执行器等,涵盖 MySQL 的大多数核心服务功能,以及所有的内置函数(如日期、时间、数学和加密函数等),所有跨存储引擎的功能(存储过程、触发器、视图等)都在这一层实现。

存储引擎层则负责数据的存储和查询。其架构模式是插件式的,支持 InnoDB、MyISAM、Memory 等多个存储引擎。现在最常用的存储引擎是 InnoDB,它从 MySQL 5.5.5 版本开始成为了默认存储引擎。

连接器

第一步,你会先连接到这个数据库上,这时候接待你的就是连接器。连接器负责跟客户端建立连接、获取权限、维持和管理连接。

在完成经典的 TCP 握手后,连接器就要开始认证你的身份,这个时候用的就是你输入的用户名和密码。

  1. 如果用户名或密码不对,你就会收到一个 "Access denied for user" 的错误,然后客户端程序结束执行。
  2. 如果用户名密码认证通过,连接器会到权限表里面查出你拥有的权限。之后,这个连接里面的权限判断逻辑,都将依赖于此时读到的权限。

一个用户成功建立连接后,即使你用管理员账号对这个用户的权限做了修改,也不会影响已经存在连接的权限。修改完成后,只有再新建的连接才会使用新的权限设置。

客户端连接之后,连接线程在整个查询周期有不同的状态,有下面这几种状态:

sleep: 线程等待客户端发送查询请求。
query: 线程正在执行查询过正在向客户端返回查询结果。
block: 线程正在等待表锁,这个是在 service 层实现的,在存储引擎级别实现的锁,例如 InoDB 引擎的行锁,不会体现在线程状态中,而对于 MyISAM 来说,这是一个典型的状态。
analyzing and statistis: 线程正在收集存储引擎的统计信息,并生成查询的的执行计划。
copying to tmp table: 线程正在执行查询,并将结果复制到一个临时表中,这个状态一般是执行 group by 操作 、文件排序操作、union 操作。
sorting Result: 线程正在对查询结果进行排序操作。
sending data: 线程可能处于多个状态间传送数据,比如生成结果集或者在向客户端返回数据。

客户端如果太长时间没动静,连接器就会自动将它断开。这个时间是由参数 wait_timeout 控制的,默认值是 8 小时。

查询缓存

连接建立完成后,你就可以执行 select 语句了。执行逻辑就会来到第二步:查询缓存。

MySQL 拿到一个查询请求后,会先到查询缓存看看,之前是不是执行过这条语句。这个过程是根据对大小写敏感的哈希查找实现的,之前执行过的语句及其结果可能会以 键值对的形式直接缓存在内存中。key 是查询的语句,value 则是查询的结果。

如果你的查询能够直接在这个缓存中找到 key,那么这个 value 只需要进行一次权限检查后就会被直接返回给客户端。 如果语句不在查询缓存中,就会继续后面的执行阶段。执行完成后,执行结果会被存入查询缓存中。

你可以看到,如果查询命中缓存,MySQL 不需要执行后面的复杂操作,就可以直接返回结果,这个效率会很高。但是大多数情况下建议不要使用查询缓存,为什么呢?

因为查询缓存往往弊大于利

因为查询缓存往往弊大于利

因为查询缓存往往弊大于利

查询缓存的失效非常频繁,只要有对一个表的更新,这个表上所有的查询缓存都会被清空。因此很可能你费劲地把结果存起来,还没使用呢,就被一个更新全清空了。对于更新压力大的数据库来说,查询缓存的命中率会非常低。除非你的业务就是有一张静态表,很长时间才会更新一次。例如,一个系统配置表,那这张表上的查询才适合使用查询缓存。

分析器

如果没有命中查询缓存,就要开始真正执行语句了。首先,MySQL 需要知道你要做什么,因此需要对 SQL 语句做解析。

分析器先会做 “词法分析”。你输入的是由多个字符串和空格组成的一条 SQL 语句,MySQL 需要识别出里面的字符串分别是什么,代表什么。

MySQL 从你输入的 "select" 这个关键字识别出来,这是一个查询语句。它也要把字符串 “table” 识别成 “表名 “table”,把字符串 “id” 识别成 “列 “id”。

因为在 sql/lex.h 中定义了 MySQL 关键字和函数关键字,用两个数组存储。
关键字 static SYMBOL symbols []
函数 static SYMBOL sql_functions []

做完了这些识别以后,MySQL 会生成一颗” 解析树”。

之后通过预处理对生成的” 解析树” 做 “语法分析”。根据词法分析的结果,语法分析器会根据语法规则,判断你输入的这个 SQL 语句是否满足 MySQL 语法。MySQL 的语法分析是使用 yacc。
详情参考这篇文章:MySQL 语法解析

如果你的语句不对,就会收到 “You have an error in your SQL syntax” 的错误提醒。

优化器

经过了分析器,MySQL 就知道你要做什么了。在开始执行之前,还要先经过优化器的处理。

优化器是在表里面有多个索引的时候,决定使用哪个索引;或者在一个语句有多表关联(join)的时候,决定各个表的连接顺序。

执行器

MySQL 通过分析器知道了你要做什么,通过优化器知道了该怎么做,于是就进入了执行器阶段,开始执行语句。

开始执行的时候,要先判断一下你对这个表有没有执行查询的权限,如果没有,就会返回没有权限的错误,如下所示。

mysql> select * from table where id = 1 ;
ERROR 1142 (42000): SELECT command denied to user 'b'@'localhost' for table 'table'

如果有权限,就打开表继续执行。打开表的时候,执行器就会根据表的引擎定义,去使用这个引擎提供的接口。比如我们这个例子中的表 table 中,id 字段没有索引,那么执行器的执行流程是这样的:

  1. 调用 InnoDB 引擎接口取这个表的第一行,判断 id 值是不是 1,如果不是则跳过,如果是则将这行存在结果集中;
  2. 调用引擎接口取 “下一行”,重复相同的判断逻辑,直到取到这个表的最后一行。
  3. 执行器将上述遍历过程中所有满足条件的行组成的记录集作为结果集返回给客户端。

至此,这个查询语句就执行完成了


一条 SQL 查询语句的执行过程

query 和 update 执行流程不一样的在于 update 涉及了日志模块,binlog (归档日志) 和 redo log (重做日志);

学习 MySQL 这两个词肯定是绕不过的,那就先说说这两个日志模块。

日志模块: redo log

在 MySQL 中也是一样,如果每一次更新都需要更新到磁盘,那么去磁盘中查询到这条记录,然后更新数据,整个更新过程的 IO 成本和查找成本都会很高。

为了解决这个问题,MySQL 的设计者就用了类似先记录后更新的思路来提升效率。 就是 MySQL 中常说的 WAL 技术,也就是 write-ahead-logging ,他的关键就是先写日志再写磁盘。

具体点说,更新一条记录的时候,InnoDB 会先写 redo log 并更新到内存中,这时候就算完成了,InnoDB 的引擎会在适当的时候将这条记录更新到磁盘里面,而这个更新往往是系统比较空闲的时候去做。

redo log 是有固定大小的,并且是循环写入的,有了 redo log, InnoDB 就可以保证即使数据库发生异常之后重启,之前提交的记录也不会丢失,这个能力称之为 creash-safe。

日志模块 binlog

上面说过 MySQL 整体来看分成 Service 层和存储引擎层,redo log 是属于存储引擎层的,而 binlog 属于 Service 层的日志。 可能有些人会问,为什么需要两个日志呢?这是因为一开始 MySQL 还没有 InnoDB 存储引擎,只有 MyISAM,这个引擎没有 creash-safe 的能力,binlog 只用于归档,而 InnoDB 是通过插件形式引入 MySQL 中的,所以 InnoDB 为了实现 creash-safe 的能力,使用了另外一套日志系统,那就是 redo log 。

那这两种日志有什么不同?

  • redo log 是有固定大小的,如果空间满了,就会擦除部分内容才能继续写入,binlog 是追加写的,是指 binlog 文件达到一定大小之后新建文件继续的写入,不会覆盖之前的内容。
  • redo log 是 InnoDB 特有的,binlog 是属于 Service 层的,所有的存储引擎都可以使用。
  • redo log 是属于「物理日志」,记录的是 “在某个数据页做了什么修改”; binlog 是属于「逻辑日志」,记录的是原始逻辑,比如 “将 id = 2 的这行数据的 c 字段修改为 3”。

上面说了两种日志的概念和区别,那他们是怎样记录的呢,拿下面的更新语句说起,id 是 table 表的一个主键。

mysql

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

将上面的执行过程绘制了一张流程图,其中浅色的是 InnoDB 内部操作,深色是执行器中的操作;

图片来源极客时间 MySQL 专栏

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

两阶段提交

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

由于 redo log 和 binlog 是两个独立的逻辑,如果不用两阶段提交,要么就是先写完 redo log 再写 binlog,或者采用反过来的顺序。我们看看这两种方式会有什么问题。 仍然用前面的 update 语句来做例子。假设当前 ID=2 的行,字段 c 的值是 0,再假设执行 update 语句过程中在写完第一个日志后,第二个日志还没有写完期间发生了 crash,会出现什么情况呢?

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

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

如何数据恢复

前面我们说过了,binlog 会记录所有的逻辑操作,并且是采用 “追加写” 的形式。如果你的 DBA 承诺说半个月内可以恢复,那么备份系统中一定会保存最近半个月的所有 binlog,同时系统会定期做整库备份。这里的 “定期” 取决于系统的重要性,可以是一天一备,也可以是一周一备。

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

数据库一天一备和一周一备的区别在哪里?

区别在故障恢复的时间上,一周一备使用 binlog 重放的语句较长!需要根据业务故障恢复需要的时长定义备份重要性

mysql 拼接sql批量执行_Mysql 学习笔记之 SQL 执行过程相关推荐

  1. mysql select语句详解_mysql学习笔记之完整的select语句用法实例详解

    本文实例讲述了mysql学习笔记之完整的select语句用法.分享给大家供大家参考,具体如下: 本文内容: 完整语法 去重选项 字段别名 数据源 where group by having order ...

  2. mysql数据库select语句用法_mysql学习笔记之完整的select语句用法实例详解

    本文实例讲述了mysql学习笔记之完整的select语句用法.分享给大家供大家参考,具体如下: 本文内容: 完整语法 去重选项 字段别名 数据源 where group by having order ...

  3. mysql模糊查询索引失效_MySql学习笔记(九):索引失效

    数据准备:CREATE TABLE `t_blog` ( `id` int(11) NOT NULL auto_increment, `title` varchar(50) default NULL, ...

  4. mysql添加字符串日期时间_mysql学习笔记--- 字符串函数、日期时间函数

    一.常见字符串函数:1.CHAR_LENGTH  获取长度(字符为单位) 2.FORMAT  格式化 3.INSERT  替换的方式插入 4.INSTR  获取位置 5.LEFT/RIGHT  取左. ...

  5. MySQL按字符串hash分区_MySQL学习笔记(14):分区

    本文更新于2019-06-30,使用MySQL 5.7,操作系统为Deepin 15.4. 分区类型 可以使用SHOW PLUGINS查看是否安装了分区插件. MySQL创建分区表支持使用大部分存储引 ...

  6. mysql 存储引擎的选择_MySQL学习笔记(四):存储引擎的选择

    一:几种常用存储引擎汇总表 二:如何选择 一句话:除非需要InnoDB 不具备的特性,并且没有其他办法替代,否则都应该优先考虑InnoDB:或者,不需要InnoDB的特性,并且其他的引擎更加合适当前情 ...

  7. mysql对所有id求积_MySQL学习笔记(二)—查询

    一.多表连接查询 新建两张表t_user.t_order.       1.内连接 返回满足条件的所有记录. (1)显式内连接 使用inner join关键字,在on子句中设定连接条件. SELECT ...

  8. mysql入门很简单系列视频-学习笔记

    mysql入门很简单系列视频-学习笔记 视频链接:mysql入门很简单系列视频 https://www.bilibili.com/video/av14920200/ 以前主要就了解DDL.DML.DC ...

  9. [python教程入门学习]python学习笔记(CMD执行文件并传入参数)

    本文章向大家介绍python学习笔记(CMD执行文件并传入参数),主要包括python学习笔记(CMD执行文件并传入参数)使用实例.应用技巧.基本知识点总结和需要注意事项,具有一定的参考价值,需要的朋 ...

最新文章

  1. 手机扫一扫就能“隔空移物”?AR炫酷新玩法,快来解锁新技能吧!
  2. Java学习——使用Static修饰符
  3. SQL Server 2008安装配置说明书+简单使用 亲测可用
  4. 微信小程序 - 骨架屏
  5. php如何使用正则表达式,php如何使用正则表达式实现替换
  6. 兰州大学2016年初试成绩基本要求
  7. 记录一次手机联系人整理(XML文件格式处理)
  8. 云计算之IasS、PasS、SaaS
  9. 你们刚开始是怎么看英文文献的?
  10. php TCPDF 生成pdf文件
  11. 蜀山前传之二---------------第八回
  12. VUE手写横向轮播图
  13. CALCULATE函数的运算顺序-第一弹
  14. 【电脑】你了解电脑吗?
  15. 二维高斯核函数(python)
  16. 作为IT行业过来人,我有一些话不得不说
  17. 【题目】pyCharm 专业版 和 社区版的区别以及如何查看其版本
  18. 原生JS实现贪吃蛇——项目总结
  19. Linux进程调用execve,实验:从整理上理解进程创建、可执行文件的加载和进程执行进程切换,重点理解分析fork、execve和...
  20. ubuntu 下 uml 工具

热门文章

  1. spyder清除控制台命令
  2. 中文ocr识别数据集地址
  3. uoj#268. 【清华集训2016】数据交互(动态dp+堆)
  4. 探秘区块链 - 头条新闻
  5. 从“共享马扎”的营销,看共享经济的刷屏玩法
  6. SecureCRT如何与Linux虚拟机进行关联
  7. LC31 Next Permutation
  8. Spring事务传播性与隔离级别
  9. 吃透Java集合中的Set集合必备文章,快快收藏
  10. kaggle训练模型