基础架构
MySQL的基础架构如下图:
大体来说,MySQL 可以分为 Server 层和存储引擎层两部分。
大多数的MySQL服务功能都在Server层,包括查询解析、缓存、分析、优化以及所有内置函数,所有跨存储引擎的功能都在这一层实现,比如存储过程、触发器、视图等。
而存储引擎则是负责存储数据,提供读写接口。有多种存储引擎,例如InnoDB、MyISAM等,各有各的优势和劣势,针对不同的应用场景。最常用的是 InnoDB,在5.5.5版本以后, InnoDB是默认引擎,也可以在创建表时使用“engine=存储引擎”来指定特定的引擎。
存储引擎不会解析SQL,但是InnoDB除外,它会解析外键定义,因为Server层没有实现该功能。
连接器
我们在查询数据之前,需要先连接 MySQL。我们可以通过以下命令连接数据库:
mysql -h ip -P port -u user -p password
其中mysql 是客户端工具,用来跟服务端建立连接。在完成TCP握手后,连接器开始认证你的身份,认证基于用户名、密码和原始主机信息。如果使用SSL连接,还可以使用X.509证书认证。如果用户名或密码出错,则收到"Access denied for user"的错误,认证通过则连接器到权限表里面查出你拥有的权限,之后进行的查询都将依赖于此权限。也就是说,一个用户成功建立连接后,即使你用管理员账号这个用户的权限做了修改,也不会影响已经存在的连接的权限。
如果客户端太久没有动作,则连接器会自动将它断开,这个时间是由参数 wait_timeout 控制的,默认值是8小时,5.7 版本 断开后可以自动重连。
由于建立连接是相对复杂的操作,所以我们一般会使用长连接,但是 mysql 在执行的过程中临时使用的内存是管理在连接对象中的,这些资源会等到连接断开才释放。所以如果连接使用的时间过长,会导致内存占用过高。在 MySQL 5.7 版本之后,可以通过mysql_reset_connection
重新初始化连接资源,该命令会释放资源,但是不需要重新连接和重新做权限验证。
但是现在开发者并不需要注意在程序中是使用短连接或是长连接了,而是将连接交由连接池管理。
查询缓存
MySQL 拿到一个查询请求后,如果查询缓存是打开的会先到查询缓存看看,之前是不是执行过这条语句。之前执行过的语句及其结果MySQL将其放在一个引用表中,类似 key-value 对的形式。key 是查询的语句、当前要查询的数据库、客户端协议版本等等,value 是查询的结果。如果你的查询能够直接在这个缓存中找到 key,那么这个 value 就会被直接返回给客户端(返回之前会验证权限)。
如果语句不在查询缓存中,就会继续后面的执行阶段。执行完成后,执行结果会被存入查询缓存中。
但是只要对表进行更新,那么该表相关的所有查询缓存就会被清空。因此,如果更新频繁的表,不建议进行查询缓存,除非是配置表这类的,会比较适合使用查询缓存。
在使用查询缓存时,需要注意的是 MySQL 中判断缓存是否命中时,并不会解析、或者格式化、参数化查询语句(实际上,在检查查询缓存之前, MySQL 只做了一件事情,就是通过一个大小写不敏感的检查看看 SQL 语句是否是以 SEL 开头的,以此来判断是否是查询语句),而是直接使用 SQL 语句和客户端发送过来的其他原始信息与缓存 key 直接匹配,所以 SQL 语句上的任何不同都会导致缓存的不命中。不止如此,但查询语句出现不确定的数据(任何用户定义的函数、存储函数、用户变量、临时表、mysql 库中的系统表、任何包含列级别权限的表)时,不会被缓存,例如:
DATA_SUB(CURRENT_DATE, INTERVAL 1 DAY) -- 不会被缓存
DATA_SUB('2020-3-22', INTERVAL 1 DAY) -- 会被缓存
将参数 query_cache_type 设置成 DEMAND,这样对于默认的 SQL 语句都不使用查询缓存。而对于你确定要使用查询缓存的语句,可以用 SQL_CACHE 显式指定,像下面这个语句一样:
select SQL_CACHE * from user;
查询缓存默认是开启的,可以关闭查询缓存。
- 临时的直接命令行执行
set global query_cache_size=0
set global query_cache_type=0
- 永久的修改配置文件my.cnf ,添加下面的配置query_cache_type=0
query_cache_size=0
需要注意的是在 MySQL 8.0 版本,查询缓存的整块功能已经被删除了。
解析器
在执行 SQL 前,需要先对 SQL 语句进行解析。
解析器先对 SQL 语句做词法分析,先识别出哪些是关键字,代表什么(将输入转化为一个个 Token,然后分析哪些 Token 是关键字,哪些不是)。
然后再做语法分析,通过语法规则来验证和解析查询,比如 SQL 中是否使用了错误的关键字或者关键字的顺序是否正确、及符号是否正确,并生成语法树。
预处理器则会根据 MySQL 规则进一步检查语法树是否合法。比如检查要查询的数据表和数据列是否存在,解析名字和别名是否有歧义等等。
接下来预处理器还会验证权限(precheck)。
如果语句不对,就会收到“You have an error in yourSQL syntax”的错误提醒。一般语法错误会提示第一个出现错误的位置,所以你要关注的是紧接“use near”的内容。
优化器
通过解析器的解析后,优化器就要对SQL语句进行优化。优化器并不关心表使用的存储引擎是什么,但是存储引擎对于查询优化是有影响的,因为优化器会请求存储引擎提供容量或是某个具体操作的开销信息以及表数据的统计信息,这些都会影响优化器对查询语句的优化。
优化器对查询语句的优化有,优化多表关联的时候,决定各个表的连接的顺序;有多个索引的时候,决定使用哪个索引;还有重写查询等等。
可以使用explain来查看执行计划,然后调整SQL语句、修改配置等等。
执行器
优化完 SQL 语句后便开始执行,先判断一下是否有该表的查询权限,没有则返回没有权限的错误。(因为可能还有触发器这种需要在执行器阶段才能确定的情况,预处理器的 precheck 是不能对这种运行时才涉及到的表进行权限校验的,所以需要在执行时再进行权限检查。)
如果有权限,打开表,然后调用存储引擎的接口获取数据返回。
本文介绍了MySQL的基础架构,这也是一条SQL查询语句执行的流程。