
- Server 层,包括[[#连接器]]、[[#查询缓存]]、[[#分析器]]、[[#优化器]]、[[#执行器]]等 涵盖 MySQL 的大多数核心服务功能,以及所有的内置函数(如日期、时间、数学和加密函数等), 所有跨存储引擎的功能都在这一层实现,比如存储过程、触发器、视图等。
连接器
连接器负责用来跟客户端,建立连接、获取权限、维持和管理连接。
创建连接
使用以下命令,通过 MySQL 客户端,连接 MySQL 服务端。
bashmysql -h$ip -P$port -u$user -p
[!attention] 为了保护密码安全,不要在
-p参数后直接拼接服务密码,尤其是在生产环境中。
MySQL 连接过程
- 客户端 与 服务端,完成 TCP 握手
连接器进行身份验证- 根据用户表,判断 用户名 和 密码
- 根据权限表,判断 用户权限
[!attention] 当前已连接用户的权限,依赖于连接登录时查询到的权限信息。 进行用户权限修改时,不会影响已经存在连接的权限。
连接完成后,可以使用 [[MySQL 命令#查看连接状态]],查看当前连接的状态。
空闲连接等待超时
- 通过使用
wait_timeout参数,可以控制 客户端无操作时最大等待时间,超过这个时间后,连接将自动断开。 - 当长连接被断开后,此时客户端再次发送请求的话,就会收到一个错误提醒:
Lost connection to MySQL server during query。此时需要重连后再执行请求。
- 通过使用
MySQL 长连接
- 由于 建立连接过程 较为复杂,所以应尽可能使用长连接。
[!faq] Q: 在使用长连接时,有些时候 MySQL 占用内存涨得特别快 A: MySQL 在执行过程中临时使用的内存,是管理在连接对象里面的。这些资源会在连接断开的时候才释放,可能导致内存占用太大。
- 解决方案:
- 定期断开长连接。
- 使用
mysql_reset_connection重新初始化连接资源(>=5.7)。 这个过程不需要重连和重新做权限验证,但是会将连接恢复到刚刚创建完时的状态。
查询缓存
- MySQL 在进行
SELECT操作时,会先尝试查询缓存。 - 之前执行过的 SQL 语句 与 查询结果,会在内存中,使用 Key-Value 结构,进行缓存。
- 其中,
Key是查询的语句;Value是查询的结果。 - 如果语句可以在查询缓存中找到时,将直接返回结果。否则,将执行后续流程,并在执行结束后,将执行结果存储查询缓存。
[!attention] 但是在大多数情况下,不建议使用查询缓存。查询缓存往往弊大于利。 查询缓存的失效非常频繁。当表进行更新时,表上所有的查询缓存都会被清空。 对于更新压力大的数据库来说,查询缓存的命中率会非常低。
MySQL 支持通过设置参数
query_cache_type为DEMAND,将默认的 SQL 语句都不使用查询缓存。对于确定需要使用 查询缓存 的语句,可以使用
SQL_CACHE进行显式指定:sqlmysql> select SQL_CACHE * from T where ID=10;
[!attention] 在 MySQL 8.0 版本中,完全移除了 查询缓存 功能。
分析器
- 通过 分析器,进行
词法分析,识别每个字符串分别代表什么。 - 分析器负责识别 SQL 语法与字段是否合法。
《高性能 mysql》里提到解析器和预处理器。 解析器处理语法和解析查询, 生成一课对应的解析树。 预处理器进一步检查解析树的合法。比如: 数据表和数据列是否存在, 别名是否有歧义等。 如果通过则生成新的解析树,再提交给优化器。
优化器
优化器的主要作用:
- 当表中存在多个 [[索引]] 时,优化器决定使用哪个索引。
- 一个语句有多表关联(join)的时候,决定各个表的连接顺序。
比如进行以下查询:
sqlmysql> 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。
- 这两种执行方法的逻辑结果是一样的,但是执行的效率会有不同,而优化器的作用就是决定选择使用哪一个方案。
执行器
- 优化器阶段完成后,这个语句的执行方案就确定下来了,然后进入执行器阶段。
flowchart LR
BGN --> QUERY_PERMISSION{是否存在对表执行查询的权限}
QUERY_PERMISSION --NO--> END
QUERY_PERMISSION --YES--> DETERMINE_QUERY_CACHE{判断是否命中缓存}
DETERMINE_QUERY_CACHE --YES--> END
DETERMINE_QUERY_CACHE --NO--> QUERY[执行结果查询]
QUERY --> CACHE[缓存查询结果]
CACHE --> END在执行的时候,需要先判断 连接的用户,是否存在对表执行查询的权限。
如果没有,则会返回没有权限的错误:
sqlERROR 1142 (42000): SELECT command denied to user 'b'@'localhost' for table 'T'如果命中查询缓存,会在查询缓存返回结果的时候,做权限验证。查询也会在优化器之前调用 precheck 验证权限。
如果有权限,就打开表继续执行。打开表的时候,执行器就会根据表的引擎定义,去使用这个引擎提供的接口。
比如在进行以下查询时
sqlmysql> select * from T where ID=10;如果 ID 字段没有索引:
- 调用 InnerDB 引擎接口取这个表的第一行,判断 ID 值是不是 10,如果不是则跳过,如果是则将这行存在结果集中;
- 调用引擎接口取“下一行”,重复相同的判断逻辑,直到取到这个表的最后一行;
- 执行器将上述遍历过程中所有满足条件的行组成的记录集作为结果集返回给客户端。
如果 ID 字段存在索引:
- 与上述无索引流程类似,将调用接口改为 取满足条件的第一行。
慢查询日志中的
rows_examined字段,就表示了在语句执行过程中,扫描了多少行。这个值就是在执行器每次调用引擎获取数据行的时候累加的。在有些场景下,执行器调用一次,在引擎内部则扫描了多行,因此引擎扫描行数跟 rows_examined 并不是完全相同的。