mysql 做为一个关系型数据库,在国内使用应该是最普遍的。也许你司使用 Oracle、Pg 等等,可是大多数互联网公司,好比我司使用得最多的仍是 Mysql,重要性不言而喻。前端
事情是这样的,某天我司小胖问我执行select * from table,数据库底层到底发生了啥?从而咱们获得数据呢?如下把我给问住了,为此我查阅了大量的书籍、博客。因而就有了这篇文章。java
假设如今我有张 user 表,只有两列,一列 id 自增的,一列 name 是 varchar 类型。建表语句是这样的:mysql
CREATE TABLE IF NOT EXISTS `user`( `id` INT UNSIGNED AUTO_INCREMENT, `name` VARCHAR(100) NOT NULL, PRIMARY KEY ( `id` ) )ENGINE=InnoDB DEFAULT CHARSET=utf8;
小胖的问题就是下面这个语句的执行过程。面试
select * from user where id = 1;
要想理解这个问题就必需要知道 mysql 的内部架构。为此,我画了张 mysql 的架构图(你也能够理解为 sql 查询语句的执行过程),以下所示:算法
首先 msql 分为 server 层和存储引擎层两个部分。server 层包括四个功能模块,分别是:链接器、查询缓存、优化器、执行器。这一层负责了 mysql 的全部核心工做,好比:内置函数、存储过程、触发器以及视图等。sql
而存储引擎层则是负责数据的存取。注意,存储引擎在 mysql 是可选的,常见的还有:InnoDB、MyISAM 以及 Memory等,最经常使用的就是 InnoDB。如今默认的存储引擎也是它(从 mysql 5.5.5 版本开始),你们能够看到我上面的建表语句就是指定了 InnoDB 引擎。固然,你不指定的话默认也是它。数据库
因为存储引擎是可选的,因此 mysql 中,全部的存储引擎实际上是共用一个 server层的。回到正题,咱们就以这张图的流程来解决一下小胖的问题。编程
首先,数据库要执行 sql,确定要先链接数据库吧。这部分工做就是由链接器完成。它负责校验帐户密码、获取权限、管理链接数,最终与客户端创建链接等工做。mysql 连接数据库是这样写的:设计模式
mysql -h 127.0.0.1 -P 3306 -u root -p # 127.0.0.1 : ip 3306 : 端口 root : 用户名
运行命令以后须要输入密码,固然也能够跟在 -p 后面。不过不建议这么作,会有密码泄露的风险。缓存
输入命令后,链接器根据你的帐户名密码验证身份。这是会出现两种状况:
注意,我说的是此时查到的权限。就算你用管理员帐号修改了当前用户的权限,此时已链接上的当前用户不受影响,必需要重启 mysql 新的权限才会生效。
链接完成,若是后续没有作任何事情,这个链接就处于空闲状态。你能够用 show processlist; 命令查看 mysql 的链接信息,以下图,个人数据库链接都是 Sleep 状态的,除了执行 show processlist 操做的链接。
若是客户端太长时间没有操做,此链接将会自动断开。这个时间默认是 8 小时,由参数 wait_timeout 控制。若是断开之后继续操做就会收到 "Lost connection to MySQL server during query"的错误。这时就必须重连才能执行请求。
数据库里面有长短链接之分,长链接:链接成功后不断有请求,就会一直使用同一链接。短链接:每次执行完几回请求就断开链接,下次须要再创建。
因为创建链接是比较耗时的操做,因此建议使用长链接。但这会有个问题长链接一直连着就会致使内存占用过大,被系统强行沙雕。从而致使 MySQL 异常重启。如何解决呢?两个方法:
链接创建之后能够执行 select 语句了。这就会来到第二步:查询缓存。
查询缓存中存储的数据是 key-value 的形式,key 是查询语句,value 是查询的结果。逻辑是这样的:先看看查询缓存有没该语句对应的 value?有则直接取出返回客户端,无则继续到数据库执行语句。查出结果后会放一份到缓存中,再返回客户端。
你可能发现缓存真的香,可是并不建议使用查询缓存,由于有弊端。查询缓存的失效很是频繁,只有某个表有更新。它立刻失效了,对于常常更新的表来讲,命中缓存的几率极低。它仅仅适用于那些不常常更新的表。
而 MySQL 彷佛也考虑到这点了。提供了 query_cache_type 参数,把它设置为 DEMAND 就再也不适用韩村。而对于要使用缓存的语句则可用 SQL_CACHE 显示指定,像这样:
select SQL_CACHE * from user where id = 1;
PS:MySQL 8.0 及以上版本把查询缓存删掉了,以后再也没有这块功能了。
若是没有命中缓存就进入分析器,这里就是对 sql 进行分析。分析器会作词法分析。你输入的 sql 是啥,由啥组成,MySQL 都须要知道它们表明什么。
首先根据 "select" 识别出这是查询语句。字符串"user"识别成"表名 user"、字符串"id"识别成"列名id"。
以后进行语法分析,它会根据输入的语句分析是否是符合 MySQL 的语法。具体表现就是 select、where、from 等关键字少了个字母,明显不符合 MySQL 语法,此次就会报个语法错误的异常:它通常会提示错误行数,关注"use near"后面便可。
过了分析器,就来到了优化器。MySQL 是个聪明的仔,再执行以前会本身优化下客户端传过来的语句,看看那种执行起来不那么占内存、快一点。好比下面的 sql 语句:
select * from user u inner join role r on u.id = r.user_id where u.name = "狗哥" and r.id = 666
它能够先从 user 表拿出 name = "狗哥" 记录的 ID 值再跟 role 表内链接查询,再判断 role 表里面 id 的值是否 = 666
也能够反过来:先从 role 表拿出 id = 666 记录的 ID 值再跟 user 表内链接查询,在判断 user 表里面的 name 值是否 = "狗哥"。
两种方案的执行结果是同样的,可是效率不同、占用的资源也就不同。优化器就是在选择执行的方案。它优化的是索引应该用哪一个?多表联查应该先查哪一个表?怎么链接等等。
分析器知道了作啥、优化器知道了应该怎么作。接下来就交给执行器去执行了。
开始执行,判断是否有相应的权限。好比该帐户对 user 表没权限就返回无权限的错误,以下所示:
select * from user where id = 1; ERROR 1142 (42000): SELECT command denied to user 'nasus'@'localhost' for table 'user'
PS:若是命中缓存没走到执行器这里,那么在返回查询结果时作权限验证。
回到正题,若是有权限,继续打开表执行。执行器会根据表定义的引擎去使用对应接口。好比咱们上面的 sql 语句执行流程是这样的:
至此,整个 SQL 的执行流程完毕,小胖懂了吗?
巨人的肩膀
本文经过一条简单的 SQL 查询语句,引出 MySQL 的结构以及这条 sql 查询语句的执行流程。相信你看完会对 SQL 有更深的理解。
若是看到这里,喜欢这篇文章的话,请帮点个好看。微信搜索一个优秀的废人,关注后回复电子书送你 1000+ 本编程电子书 ,包括 C、C++、Java、Python、GO、Linux、Git、数据库、设计模式、前端、人工智能、面试相关、数据结构与算法以及计算机基础,详情看下图。回复 1024 送你一套完整的 java 视频教程。