MySQL 的一条语句是怎么执行的
该文为《 MySQL 实战 45 讲》的学习笔记,感谢查看,如有错误,欢迎指正
一、MySQL 的基础架构
以下就是 MySQL 的基础架构图。

在 Linux 中安装 MySQL 时,最少需要安装 mysql-server 以及 mysql-client,而服务端中又包含了 Server 层和存储引擎。
Server 层包含了连接器,查询缓存,分析器,优化器,执行器,以及内置函数(日期,时间,数学和加密函数等),所有跨存储引擎的功能都在这一层实现。比如存储过程,触发器,视图等。
存储引擎层是独立的,可以理解为插件形式,Server 层接入了好几种存储引擎,比如MyISAM、InnoDB、Memory等,MySQL 5.5.5 之后默认的存储引擎是InnoDB。不管你的 MySQL 使用了多少种存储引擎,它们都是共享一个 Server 层。
如果在建表时(create table),想指定使用其它引擎,可以加上engine=MyISAM实现。
二、查询语句的执行过程
语句示例:
mysql> select * from T where id=10
2.1 连接器
连接器负责接收处理客户端发送过来的连接请求,获取权限,维持和管理连接。
客户端可以使用 mysql-client,通过命令行mysql -uroot -p进行建立连接。也可以使用第三方工具如Navicat建立连接。连接过程也是走的TCP/IP协议,有经典的3次握手过程。
连接器先判断用户名密码是否正确,不正确会返回Access denied for user错误;正确的话,会到权限表中查询出该用户的权限,只要该连接未断开,将会一直使用该权限。
这也就是为什么有时候我们即使修改了用户的权限,也不会立刻生效,必须断开重连,才能生效。当然如果该连接长时间处于空闲状态(连接上以后没有动作),默认 8 小时以后就会自动断开该连接。这个 8 小时是由wait_timeout来控制的。
通过show processlist可以查看哪些连接处于空闲状态,Command为Sleep的就是空闲连接,可以通过kill Id来手动断开连接,一般在死锁或者事务阻塞的时候会用到。
mysql> show processlist;
+----+---------+-----------------+------+---------+------+-------+------------------+
| Id | User | Host | db | Command | Time | State | Info |
+----+---------+-----------------+------+---------+------+-------+------------------+
| 3 | root | localhost:64511 | NULL | Query | 0 | init | show processlist |
| 4 | ruhrbim | localhost:64519 | NULL | Sleep | 3 | | NULL |
+----+---------+-----------------+------+---------+------+-------+------------------+
2 rows in set (0.00 sec)
mysql>
MySQL 5.7及更新版本,可以使用mysql_reset_connection来初始化连接资源,这个操作不需要重新做权限验证,直接恢复到刚创建完连接时的状态。
2.2 查询缓存
查询缓存中存储的是之前的查询语句及结果,以key-value的形式存储,key是查询语句,value是查询结果。在执行select语句之前,会先去查询缓存看一下,如果有结果,直接从查询缓存返回,不经过后面的分析器、优化器、执行器等。如果没有结果,就会往后依次执行一遍,最后把语句及结果存入查询缓存,以便下次使用。
查询缓存主要针对的是变动不频繁的表,只要表发生了变更,那么这个表上的查询缓存都会被清空。MySQL也提供了"按需使用"的方法,将query_cache_type设置为DEMAND,这样默认 SQL 语句都不使用查询缓存,要使用的时候,可以通过SQL CACHE显式指定。
mysql> select SQL_CACHE * from T where ID=10;
Tips:MySQL 8.0 将查询缓存功能直接去掉了
2.3 分析器
没有命中查询缓存,就会到分析器这一步。分析器是对 SQL 语句做解析的。
首先进行「词法分析」,根据select判断是一个查询语句,还要把字符串T识别为表名 T,字符串ID识别为列 ID。
然后进行「语法分析」,分析这一行 SQL 语句是否满足 MySQL 语法。
mysql> select * fron huanzi;
ERROR 1064 (42000): You have an error in your SQL syntax; check the manual that corresponds to your MySQL server version for the right syntax to use near 'fron huanzi' at line 1
mysql>
我们把from写成了fron,分析器做语法分析时识别出这里有问题,并将问题点定位出来了,在use near后面就是。
2.4 优化器
现在 MySQL 已经知道要做什么了,但在开始执行 SQL 语句之前还要经过优化器的处理。
优化器能够选择使用哪个索引,或者在多表关联的时候,选择连接的顺序。
当然有时候优化器也会选择错索引,我们可以使用force index(有索引的列名)来强制指定使用某一个索引。
mysql> select * from t force index(a) where a between 10000 and 20000;
2.5 执行器
SQL 语句经过了以上步骤,最终到达执行器,执行器的作用就是执行 SQL 语句。
开始执行的时候,要先判断一下是否有执行该语句的权限,没有权限会返回错误。
mysql> select * from T where ID=10;
ERROR 1142 (42000): SELECT command denied to user 'b'@'localhost' for table 'T
mysql>
如果命中了查询缓存,不走执行器,也会在查询缓存返回结果的时候做权限验证。
如果有权限,就继续执行,以上述查询语句为例,执行流程如下:
- 调用 InnoDB 引擎接口取这个表的第一行,判断 ID 值是不是 10,如果不是则跳过,如果是则将这行存在结果集中;
- 调用引擎接口取“下一行”,重复相同的判断逻辑,直到取到这个表的最后一行。
- 执行器将上述遍历过程中所有满足条件的行组成的记录集作为结果集返回给客户端。
三、更新语句的执行过程
给出一个表的建表语句:
mysql> create table T(ID int primary key, c int);
更新语句也有查询语句的那些流程,以update T set c=c+1 where ID=2;为例,首先连接到连接器,然后将表T上的缓存全部清空,分析器分析以后知道这是一个更新语句,优化器决定使用 ID 这个索引。执行器负责执行语句。
除了以上步骤,更新语句还多了 2 个日志模块,分别是redo log(重做日志)和binlog(归档日志)。
3.1 redo log
更新的时候,如果每次都要去磁盘找到那条记录,并且直接更新至磁盘,会产生很大的 IO 成本,在 MySQL 中有 1 个 WAL 技术就是为了解决这个问题,全称叫做 Write-Ahead Logging,关键点在于先写日志,再写磁盘。
具体来说,在更新数据库的时候,InnoDB 引擎会先把记录写到 redo log 里面,并更新内存,这时候更新就算已经完成了,InnoDB 引擎会在适当的时候,将记录更新到磁盘,一般是在服务器负载较低的时候。
InnoDB 里面的 redo log 是固定大小的,可以在/etc/my.cnf中进行配置,一般是 1 组 4 个文件,文件名是ib-logfile-0、ib-logfile-1、ib-logfile-2、ib-logfile-3,会从 0 到 3 开始循环写,在 3 写满之后又会向 0 里面写,因此要永远保证 redo log 中有剩余空间可以记录信息,如果已经写满了,就会停下来先刷一部分数据到磁盘,空间腾出来以后,继续记录。

write pos 是当前记录的位置,checkpoint 是当前要擦除的位置。write pos 和 checkpoint 之间的是“粉板”上还空着的部分,可以用来记录新的操作。
有了 redo log,InnoDB 就可以保证即使数据库发生异常重启,之前提交的记录都不会丢失,这个能力称为 crash-safe。
3.2 binlog
redo log 是 InnoDB 独有的功能,在 MySQL 还没有引入 InnoDB 的时候,也有一个日志,就是 binlog 日志,主要功能是归档,以及主从复制使用。crash-safe 和 WAL 也都是 InnoDB 特有的。
- binlog 是 Server 层的日志,并不是引擎层,因此所以的引擎都可以使用 binlog 日志。
- redo log 是物理日志,记录的是“在某个数据页上做了什么修改”;binlog 是逻辑日志,记录的是这个语句的原始逻辑
- redo log 是循环写的,空间固定会用完;binlog 是可以追加写入的,不会覆盖旧的文件。

回到更新语句中,InnoDB 将数据写入 redo log 后还没结束,此时 redo log 处于 prepare 状态;
然后 InnoDB 告知执行器执行完成了,随时可以提交事务,执行器生成这个操作的 binlog,并把 binlog 写入磁盘。
执行器调用引擎的提交事务接口,引擎把刚刚写入的 redo log 改成提交(commit)状态,更新完成。
这个就是 MySQL 中的两阶段提交。

感谢阅读,有兴趣的小伙伴可以关注我的微信公众号DevOps探索之旅,大家一起学习进步

MySQL 的一条语句是怎么执行的的更多相关文章
- MySQL:一条SQL是如何执行的
目录 MySQL基本架构 Server层 连接器 查询缓存 分析器 优化器 执行器 存储引擎层 InnoDB MyISAM Memory SQL执行流程 MySQL基本架构 在讲SQL语句是如何执行之 ...
- 转载《mysql 一》:mysql的select查询语句内在逻辑执行顺序
原文:http://www.jellythink.com/archives/924 我的抱怨 我一个搞应用开发的,非要会数据库,这不是专门的数据库开发人员干的事么?话说,小公司也没有数 据库开发人员这 ...
- 转 【MySQL】常用拼接语句 shell 下执行mysql 命令
[MySQL]常用拼接语句 前言:在MySQL中 CONCAT ()函数用于将多个字符串连接成一个字符串,利用此函数我们可以将原来一步无法得到的sql拼接出来,在工作中也许会方便很多,下面主要介绍下几 ...
- python中a, b = a, a + b这条语句是如何执行的?
a,b=b,a+b,这条语句在"理解"上还是与C语言有些差别的.在Python中,可以做下面的方式理解:首先,把等号右边的算式分别算完再说,然后按照一一对应的关系把值赋给等号左边的 ...
- 【417】一条语句编译并执行C语言
参考:shell学习笔记(1)Linux下在一行执行多条命令 要实现在一行执行多条Linux命令,分三种情况: 1.&& 举例: lpr /tmp/t2 && rm / ...
- mysql实战45讲读书笔记(一) 一条SQL查询语句是如何执行的
我们经常说,看一个事儿千万不要直接陷入细节里,你应该先鸟瞰其全貌,这样能够帮助你从高维度理解问题.同样,对于MySQL的学习也是这样.平时我们使用数据库,看到的通常都是一个整体.比如,你有个最简单的表 ...
- 当程序执行一条查询语句时,MySQL内部到底发生了什么? (说一下 MySQL 执行一条查询语句的内部执行过程?
先来个最基本的总结阐述,希望各位小伙伴认真的读一下,哈哈: 1)客户端(运行程序)先通过连接器连接到MySql服务器. 2)连接器通过数据库权限身份验证后,会先查询数据库缓存是否存在(之前执行过相同条 ...
- MySQL:一条更新语句是如何执行的
目录 引言 更新流程图 更新流程说明 第一步:更新数据 数据页内存 Change Buffer 第二步:缓存日志内容 redo log buffer binlog cache 第三步:日志写入磁盘 两 ...
- MySQL数据库详解(一)执行SQL查询语句时,其底层到底经历了什么?
一条SQL查询语句是如何执行的? 前言 大家好,我是WZY,今天我们学习下MySQL的基础框架,看一件事千万不要直接陷入细节里,你应该先鸟瞰其全貌,这样能够帮助你从高维度理解问题.同样,对于MyS ...
随机推荐
- 看看AQS阻塞队列和条件队列
上一篇简单介绍了AQS,我们大概知道AQS就是一个框架,把很多功能都给实现了(比如入队规则,唤醒节点中的线程等),我们如果要使用的话只需要实现其中的一些方法(比如tryAcquire等)就行了!这次主 ...
- flutter 与 android 混合开发
现有的混合开发方式,都是存flutter项目在android系统或者iOS上面跑. 但是,实际情况是,我们需要在一个成熟的native项目上面,跑几个flutter页面,逐步的进行flutter的融合 ...
- Shoot the Bullet(有源汇带上下界最大流)
有源汇带上下界最大流 在原图基础上连一条汇点到源点流量为inf的边,将有源汇网络流转化为无源汇网络流用相同方法判断是否满流,如果满流再跑一边源点到汇点的最大流就是答案 例题:Shoot the Bul ...
- 每天一道Java题[8]
以下题目及解答属于个人见解,欢迎大家也分享和补充一下解答的内容,互相促进,共同进步! 题目 RESTful WebService与SOAP WebService有什么异同? 解答 SOAP是一个协议, ...
- markdown常用语法使用笔记+使用技巧(持续更新......)
参考引用内容: 简书教程 一 基本语法 1. 标题 语法: 在想要设置为标题的文字前面加#来表示,一个#是一级标题,二个#是二级标题,以此类推.支持六级标题. 注:标准语法一般在#后跟个空格再写文字 ...
- hashmap 的边遍历边存储
PS: Hashmap 的一边遍历边存储,可解决例如两数之和. 无重复最长子串问题等,代码为cpp格式. 以无重复最长子串为例. class Solution { public: int length ...
- 页面置换算法之Clock算法
1.前言 缓冲池是数据库最终的概念,数据库可以将一部分数据页放在内存中形成缓冲池,当需要一个数据页时,首先检查内存中的缓冲池是否有这个页面,如果有则直接命中返回,没有则从磁盘中读取这一页,然后缓存到内 ...
- scrapy-redis分布式爬虫实战
Scrapy-Redis代码实战 Scrapy 是一个通用的爬虫框架,但是不支持分布式,Scrapy-redis是为了更方便地实现Scrapy分布式爬取,而提供了一些以redis为基础的组件(仅有组件 ...
- python环境安装及配置
一.下载python,可选择python2.x或python 3.0 下载地址:[官网],选择系统 ---选择对应版本 注意自己电脑是32位(X86)还是64位(x86-64) 下载文件包,点击点击安 ...
- React+Echarts简单的封装套路
今天我们来介绍一下React中,对Echarts的一个简单的封装. 首先在我们的React项目中,想使用Echart包,首先需要先安装它,安装代码如下,任选一个就可以 cnpm install ech ...