首先我们来了解 MySQL 的逻辑架构,对 MySQL 有一个整体的认识。
例如在执行下面的一条语句时:
mysql> select * from T where ID=10;
我们看到的结果是返回一条记录,那么 MySQL 是如何执行这条 SQL 查询语句呢?
首先我们来看一下 MySQL 的逻辑架构图:
大体来说,MySQL 分为 Server 层和存储引擎层两部分。
Server 层包含连接器、查询缓存、分析器、优化器、执行器等 MySQL 的核心服务功能。
存储引擎层负责数据的存储和提取。其架构是插件式的,支持 InnoDB、MyISAM、Memory 等多种存储引擎。
从图中不难看出,不同的存储引擎共用一个 Server 层。下面我们通过上面那条 SQL 查询语句来看一下整体流程和每个组件的作用。
连接器
连接器是客户端与 MySQL 进行连接的组件。客户端通过以下命令进行连接:
mysql -h$ip -P$port -u$user -p
输完这条命令后,再输入用户名和密码。虽然也能够在上面的命令里同时输入密码,但容易造成密码泄露,尤其是在生产环境中,不建议把密码显式输入。
- 如果用户名或密码不正确,返回 "Access denied for user" 的错误,连接中断。
- 如果用户名密码认证通过,连接器会到权限表中查出你的权限,之后这个连接的权限判断都依赖于读取出来的权限。
这意味着,一个用户成功建立连接后,即便修改了他的权限,当前连接也不会生效,除非断开重连。
查询缓存
上一步与 MySQL 建立了连接,然后我们从客户端输入 SQL 查询语句:
mysql> select * from T where ID=10;
MySQL 会先到查询缓存里找,看是否存在这条记录。如果存在,则直接返回结果。
但是大多数情况下我会建议你不要使用查询缓存,为什么呢?因为查询缓存往往弊大于利。
查询缓存里的缓存数据很容易失效,只要表中任意数据的更新都会清空这个表的缓存,尤其是对于写比较频繁的应用,缓存基本不起效果。
我们可以通过把参数 query_cache_type
设置成 DEMAND
来禁用查询缓存,也可以显式设置使用查询缓存:
mysql> select SQL_CACHE * from T where ID=10;
注意:MySQL 8.0 版本直接将查询缓存的整块功能删掉了,也就是说 8.0 开始彻底没有这个功能了。
分析器
MySQL 是如何 “认得出” 这条 SQL 语句的呢?
通过分析器,MySQL 首先对 SQL 语句进行词法分析,提取为若干个字符串。
然后再进行语法分析,判断 SQL 语句是否符合 MySQL 的语法。
如果你的语句不对,就会收到 “You have an error in your SQL syntax” 的错误提醒,比如下面这个语句 select 少打了开头的字母 “s” 。
mysql> elect * from t where ID=1;
ERROR 1064 (42000): You have an error in your SQL syntax; check the manual that corresponds to your MySQL server version for the right syntax to use near 'elect * from t where ID=1' at line 1
一般语法错误会提示第一个出现错误的位置,所以你要关注的是紧接 “use near” 的内容。
优化器
经过分析器,MySQL 知道了要做什么,在开始执行前,还需要经过优化器的处理。
优化器是在表里有多个索引时,决定使用哪个索引。比如下面的查询语句:
mysql> select * from t1 join t2 using(ID) where t1.c=10 and t2.d=20;
有两种执行方式:
- 先从表 t1 里读取
t1.c=10
的行,然后再从表 t2 里判断t1.ID=t2.ID and t2.d=20
。 - 先从表 t2 里读取
t2.d=20
的行,然后再从表 t1 里判断t1.ID=t2.ID and t1.c=10
。
优化器就是判断要选择哪种执行方式,大概的判断依据是 “成本估算”,估算哪种执行方式的 “成本” 更低,就选择哪种。
执行器
MySQL 经过分析器知道要做什么,经过优化器知道怎么做,最后就是执行了。首先 MySQL 会判断是否具有查询权限,如果没有,就返回没有权限的错误,如下:
mysql> select * from T where ID=10;
ERROR 1142 (42000): SELECT command denied to user 'b'@'localhost' for table 'T'
如果有权限,就打开表继续执行。打开表的时候,执行器会根据表中定义的存储引擎,去使用这个存储引擎提供的接口。
比如我们的例子中,表 T 中,ID 字段没有索引,那么执行器的流程是:
- 调用 InnoDB 存储引擎接口取一行,判断
ID=10
。如果不是,则跳过;如是,则保存到结果集中。 - 然后再取下一行,重复直到表结束为止。
- 最后组成结果集返回给客户端。