首先要介绍MySQL的组成部分。MySQL由server层和存储引擎层两部分组成。
server层包括:连接器、查询缓存、分析器、优化器、执行器。大多数功能都在server层实现,例如存储过程、触发器、试图、函数等。
存储引擎层负责数据的存储和提取。常见的存储引擎有InnoDB、MyISAM、Memory等。从MySQL5.5开始使用InnoDB作为默认存储引擎,也就是说,如果在创建表时不指定存储引擎,MySQL将默认InnoDB为此表的存储引擎。在create table时加入engine=memory即可指定此表的存储引擎为memory。
连接器
客户端使用下面的代码进行数据库连接
mysql -h$ip -P$port -u$user -p
在完成经典的TCP握手之后,连接器就开始认证身份,会在交互界面要求输入密码。
如果账号或密码错误,客户端会受到一个错误 "Access denied for user"。
如果账号密码正确,连接器会到权限表中查出此账户拥有的权限。所以一个账户的权限是在连接时缓存下来的,若权限修改了,需要重新连接才可生效。
客户端如果长时间没有动静,连接器就会主动断开连接。这个时间由配置文件中的 wait_timeout 设置,默认8小时。
建立连接的过场通常比较复杂,所以我们在开发中尽量减少建立连接的动作。
但是再长时间使用长连接后,可能会出现内存占用特别大的情况。这是因为MySQL在执行过程中临时使用的内存是管理在连接对象里面的,这些内存资源是在连接断开时才会释放。如果长连接占用的内存特别大,会被系统强行杀掉(OOM,内存溢出),从现象看就是MySQL异常重启。解决办法可以参考以下两种方案:
- 定期断开长连接。
- 在执行一个比较大的操作后,通过 mysql_reset_connection 重新初始化连接资源。
查询缓存
完成连接之后,就可以执行sql语句了。
MySQL在拿到一条sql请求后,会先到查询缓存看看是否执行过这条语句。查询缓存通过 hash table 存储缓存,sql语句为key,结果为value,如果查询到有结果,直接将value返回给客户端。
如果没有结果,将会继续后面的执行阶段:分析器。在所有阶段执行完后,执行结果将会写入到查询缓存的 hash table 中。
可以看到,查询缓存如果查询到数据,会省略后面的所有步骤直接返回结果,效率非常高。但是大多数情况下建议不要使用查询缓存。
查询缓存的失效非常频繁,只要对表进行更新,这张表的所有查询缓存都会被清空。所以对于更新频繁的表来说,命中率会非常低,每次执行sql的查询缓存都会占用一部分资源。如果是一张静态表,不需要频繁的更新数据,那这张表的查询才适合使用查询缓存。
可以通过修改配置文件中 query_cache_type 参数为 DEMAND ,这样默认的sql语句都不会使用查询缓存。对于确定要使用查询缓存的语句,可以使用 SQL_CACHE 关键字标示。
select SQL_CACHE * from T where ID = 10;
需要注意的是,MySQL8完全的去掉了查询缓存。
分析器
如果在查询缓存中没有命中,sql的执行阶段就到了分析器。
分析器会先做“词法分析”,即从sql语句中分析各个关键字是什么,代表什么。
比如,MySQL上面提到的查询语句中讲 select 识别出来,代表这条语句是一条查询语句,将 T 识别出来是表名。
做完了“词法分析”后,就要做“语法分析”了。根据“词法分析”的结果和语法规定,判断这条sql语句是否满足MySQL语法。
如果sql语句不正确,客户端将会受到“You have an error in your SQL synatx”的错误提示。
优化器
经过了分析器,MySQL就知道你要做的事情了,在真正的执行sql语句前,还要先经过优化器的优化处理。
优化器是在表里面有多个索引的时候,决定使用哪个索引;或者在一个语句中有多表关联的时候,决定各个表的连接顺序,如下面这个语句:
select * from t1 join t2 useing(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。
这两种执行方法的逻辑都是一样的,但是执行效率不同,优化器的作用就是决定使用哪一种方案。
执行器
从分析器知道你要做什么,从优化器该怎么做,最后就进入了执行器阶段,开始执行sql语句。
在开始执行的时候,要先判断一下当前账户对表是否有相应的权限。如上面提到的查询语句,需要先判断是否有表 T 的查询权限。如果没有,客户端会收到没有权限的错误。
SELECT command denied to user 'b'@'localhost' for table 'T'
如果有权限,就打开表继续执行。打开表的时候,执行器会根据表的引擎定义,去使用这个引擎提供的结构。
比如上面的查询例子表 T 中,ID 字段没有索引,那么执行器的执行流程就是这样的:
- 调用 InnoDB 引擎接口取这个表的第一行,判断 ID 值是不是 10,如果不是则跳过,如果是则将这行存在结果集中;
- 调用引擎接口取下一行,重复相同的判断逻辑,直到取到这个表的最后一行。
- 执行器将上述遍历过程中所有满足条件的行组成记录集作为结果集返回给客户端。
至此,这个语句就执行完了。
对于有索引的表,执行逻辑也是差不多的。第一次调用的是“取满足条件的第一行”这个接口,之后循环执行“满足条件的下一行”接口。
可以在数据库的慢查询日志中看到 rows_examined 这个字段,表示这个语句执行过程中扫描了多少行。