前言:看了极客时间的Mysql实战45讲,觉得写的很好,做一下笔记。
问题:平时我们再使用数据库的时候,比如说执行下面这个查询语句:
mysql> select * from T where ID=10;
我们只知道,输入一条语句之后,返回得到了一个结果,却不知道这条语句在Mysql内部的执行。
MySQL架构介绍
大体上来说,Mysql可以分为Server层和存储引擎层两部分。
Server层包括连接器、查询缓存、分析器、优化器、执行器等,涵盖了Mysql大多数的核心服务功能,以及所有的内置函数,所有跨存储引擎的功能都在这一层实现,比如存储过程、触发器、视图等。
而存储引擎层负责数据的存储和提取。其架构模式是插件式的,支持InnoDB、MyISAM、Memory等多个存储引擎。现在最常用的存储引擎是InnoDB,它从MySQL5.5.5版本之后成为了mysql默认的存储引擎。
也就是说,当我们执行create table建表的时候,如果不指定存储引擎,默认使用的就是InnoDB。如果你想使用别的存储引擎,可以在建表语句中使用engine=XXX来指定存储引擎。不同的存储引擎的表存取方式不同,支持的功能也不同。
由图可知,不同的存储引擎共用一个Server层,也就是从连接器到执行器的部分。
连接器
第一步,我们使用navicat或者其他工具连接数据库的时候,就需要和连接器打交道。连接器负责客户端连接、获取权限、维护和管理连接。连接命令一般如下:
mysql -h$ip -P$port -u$user -p
连接命令中的mysql是客户端工具,用来跟服务端建立连接。在完成经典的TCP握手之后,连接器就开始认证身份,这个时候就需要你输入用户名和密码。
- 如果用户名或者密码不对,会出现"Access denied for user"的错误,然后客户端程序结束运行。
- 如果用户名和密码认证通过,连接器会到权限表里面查出你拥有的权限。之后,此连接里面的权限判断逻辑,都依赖于此时读到的权限。
这意味着,一个用户成功建立连接后,即使你用管理员的权限对这个用户的权限做了修改,也不会影响已经存在连接的权限。修改完成后,只有再新建的连接才会使用新的权限设置。
连接完成后,如果你没有后续的动作,这个连接就处于空闲状态,你可以使用show processlist命令来查看。其中Command列显示为"sleep"的这一行,就表示现在系统里面有一个空闲的连接。
客户端如果太长时间没有动静,连接器就会将此连接自动断开。这个时间由参数wait_timeout控制,默认值是8小时。
如果在连接被断开之后,客户端再次发送请求的话,就会收到一个错误的提醒:Lost connection to MySQL server during query。这个时候如果想继续操作,就需要重连,然后再执行请求。
在数据库里面,长连接是指连接成功后,如果客户端持续有请求,则一直使用同一个连接。短连接则是指每次执行完很少的几次查询就断开连接,下次查询再重新建立一个。
建立连接的过程通常比价复杂,所以要尽量减少建立连接的动作,也就是尽量使用长连接。
但是全部使用长连接之后,我们会发现,有时候mysql占用内存会涨的特别快,这是因为mysql在执行过程中临时使用的内存是管理在连接对象里面的。这些资源会在断开连接的时候才释放。所以长连接积累下来,就可能导致内存占用太大,被系统强行干掉。表现形式就是Mysql异常重启。
那么如何解决长连接占用内存太大的问题呢?
1、定期断开长连接。使用一段时间,或者程序里面判断执行过一个占用内存的大查询后,断开连接,之后要查询再重连。
2、如果使用的MySQL5.7以后的版本,可以在每次执行一个比较大的操作后,通过执行mysql_reset_connection来重新初始化连接资源。这个过程不需要重连和重新做权限验证,但是会将连接恢复到刚刚创建完时的状态。
查询缓存
连接建立完成后,就可以执行select语句了,执行逻辑就会来到第二步:查询缓存
Mysql拿到一个查询请求后,会先到查询缓存看看,之前是不是执行过这条语句。之前查询过的语句以及其结果可能会以key-value对的形式被直接缓存到内存中。key是查询的语句,value是查询的结果。如果查询语句能够直接在缓存中找到key,那么这个value就会被直接返回给客户端。
如果语句不在查询缓存中,就会继续后面的执行阶段。执行完成后,执行结果会被存入查询缓存中。如果查询命中缓存,Mysql不需要执行后面的复杂操作,就可以直接返回结果,这个效率会很高。
但是大多数情况下不推荐使用查询缓存?
查询缓存的失效非常频繁,只要有对一个表的更新,那么这个表上所有的查询缓存都会被清空。因此可能我们很费劲把结果存起来,还未使用就被一个更新清空了。对于更新压力大的数据库来说,查询缓存的命中率非常低。
并且mysql8.0之后将查询缓存这个模块功能删掉了。
分析器
如果没有命中查询缓存,就要开始真正执行语句了。首先Mysql需要知道你要做什么,因此需要对SQL语句进行解析。
分析器先要做“词法分析”。Mysql需要识别输入的字符串是什么,代表什么。Mysql从语句中将"select"这个关键字识别出来,这是一个查询语句。它也要把"T"识别成"表名T",把字符串"ID"识别为"列ID"。
做完词法分析后,就需要做语法分析,语法分析器会根据语法规则,判断输入的SQL语句是否Mysql语法。如果SQL语句不符合规范,就会出现You have an error in your SQL syntax 的错误提醒
优化器
经过分析器之后,Mysql就知道你要做什么了。在开始执行之前,还要经过优化器的处理。
优化器是在表里面有多个索引的时候,决定使用哪个索引;或者在一个语句中有多个表关联的时候,决定各个表的连接顺序。比如执行如下语句的时候
mysql> select * from t1 join t2 using(ID) where t1.c=10 and t2.d=20;
- 既可以先从表t1里面取出c=10的记录的ID值,再根据ID值关联到表t2,再判断t2里面d的值是否等于20
- 也可以先从表t2里面取出d=20的记录的ID值,再根据ID值关联到表t1,再判断t1里面c的值是否等于10
优化器阶段完成后,这个语句的执行方案就确定下来了,然后进入执行器阶段。
执行器
Mysql通过分析器知道了要做什么,通过优化器知道了该怎么做,于是进入执行器阶段,开始执行语句。
开始执行的时候,会先判断登录的用户对表T有没有查询的权限,如果没有,就会返回没有权限的错误。
mysql> select * from T where ID=10;
ERROR 1142 (42000): SELECT command denied to user 'b'@'localhost' for table 'T'
如果有权限,就打开表继续执行。打开表的时候,执行器就会根据表的引擎定义,去使用这个引擎提供的接口。
比如上述查询语句,ID字段没有索引,那么执行器的执行流程如下:
1、调用InnoDB引擎接口取这个表的第一行,判断ID值是不是10,如果不是则跳过,如果是则将这行存在结果集中
2、调用引擎接口取“下一行”,重复相同的判断逻辑,直到取到这个表的最后一行。
3、执行器将上述遍历过程中所有满足条件的行组成的记录集作为结果集返回客户端。
至此,这个语句就执行完成了。
对于有索引的表,执行逻辑类似。
小结
提问:如果表T中没有字段k,而你执行了这个语句 select * from T where k=1, 那肯定是会报“不存在这个列”的错误: “Unknown column ‘k’ in ‘where clause’”。那么这个错误是在哪个阶段报出来的呢?
答:分析器阶段。