大体来说MySQL可以分为Server层和存储引擎层两部分。 Server层包括连接器、查询缓存、分析器、优化器、执行器等,涵盖MySQL的大多数核心服务功能,以及所有的内置函数(日期、时间、数学和加密函数等),所有跨存储引擎的功能都在这一层实现,比如存储过程、触发器、视图等。 存储引擎层负责数据的存储和提取。其架构模式是插件式的,支持InnoDB、MyISAM、Memory等多个存储引擎。现在最常用的存储引擎是InnoDB,从MySQL5.5.5版本开始成为了默认存储引擎。

连接器

连接器负责跟客户端建立连接、获取权限、维持和管理连接。连接命令一般为mysql -h$ip -P$port -u$user -p。 连接建立之后进行身份验证然后在权限表查询权限。用户成功建立连接后,即使该用户权限做了修改也不会影响已经存在连接的权限。 可以使用命令show processlist查询连接状态,Command为Sleep的表示为空闲连接,长时间空闲的连接将会被连接器自动断开,时间由参数wait_timeout控制,默认为8小时。 建立连接的过程比较复杂,所以建议尽量使用长连接。但是使用长连接会导致占用内存涨的比较快,因为MySQL在执行过程中临时使用的内存是管理在连接对象里面的。这些资源在连接断开的时候才释放。

解决长连接导致占用内存涨的较快的方法有:

  1. 定期断开长连接。使用一段时间,或者程序里面判断执行过一个占用内存大的查询后断开连接,需要的时候再重连。
  2. 如果使用的是MySQL5.7或更新版本(版本查询使用select version() from dual或者status\s),可以在每次执行一个比较大的操作后,通过执行mysql_reset_connection来重新初始化连接资源。这个过程不需要重连和重新做权限验证,但是会将连接恢复到创建完时的状态。

查询缓存

MySQL拿到一个查询请求后,会先到查询缓存看是否之前执行过这条语句。之前执行过的语句及其结果会以key-value形式缓存在内存中。如果能命中缓存则直接返回给客户端。否则继续后面的执行阶段,最终结果存入查询缓存中。

但是大多数情况下建议不要使用查询缓存,因为查询缓存往往弊大于利。并且 MySQL8.0版本直接将查询缓存的整块功能删除了

只要对表更新,该表上面的所有查询缓存都会被清空。对于更新压力大的数据库来说,缓存命中率会非常低。如果业务就是一张静态表,少量更新才适合使用查询缓存。 将参数query_cache_type设置成DEMAND默认的SQL语句就不会使用查询缓存了。对于确定使用查询缓存的语句使用SQL_CACHE显示指定,类似select SQL_CACHE * from users where id = 1;

分析器

分析器先做“此法分析”然后做“语法分析”。根据词法分析的结果,语法分析根据语法规则,判断输入的SQL语句是否满足MySQL语法。

优化器

经过了分析器,MySQL知道要做什么事情了,在开始执行之前先经过优化器处理。 优化器是在表里面有多个索引的时候,决定使用哪个索引;或者在一个语句有多表关联的时候,决定各个表的连接顺序。

执行器

在开始执行语句的时候会先判断是否有查询权限,如果没有会返回权限错误。如果命中查询缓存,会在查询缓存返回结果的时候做权限验证。在分析器之后,也就是知道了语句要“干什么”之后也会先做一次权限验证,叫precheck。precheck无法对运行时涉及到的表进行权限验证,比如高使用触发器的情况,因此在执行器这里还要做一次执行时的权限验证。 权限检测通过后继续执行,执行器根据表的引擎定义,去使用对应引擎提供的接口。 执行器执行select * from users where id = 1;上面语句的流程为:

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

对于有索引的表,逻辑类似。第一次调用的是“取满足条件的第一行”然后循环取“满足条件的下一行”。 在数据库的慢查询日志中rows_examined字段表示语句执行过程中扫描的行数。这个值就是执行器每次调用引擎获取数据航的时候累加的。有些场景下执行器调用一次,引擎内部扫描了多行,因此引擎扫描行数跟rows_examined并不是完全相同。

思考

如果表T中没有字段k,当执行select * from T where k = 1语句是会在哪个阶段报错?

答案:在分析器阶段报错,分析器阶段会判断表是否存在,字段是否存在等。