其中命令中的 mysql 是客户端⼯具,⽤来和服务端建⽴连接。在完成TCP握⼿之后,连接器开始使⽤输⼊的参数信息进⾏⾝份认证。
如果⽤户名或密码不正确,会有⼀个 Access Denied for user 的错误,客户端中断。
如果验证通过,连接器会到权限表⾥⾯查出⽤户权限并存在变量中。之后该连接⾥⾯的权限判断逻辑,都将依赖于此时读取到的权限。
此时我们应该就会理解,为什么我们给某个⽤户修改了权限之后,已经存在的该⽤户的连接不会变更为新的权限。要想新的权限⽣效,只能新建该⽤户的连接。
连接完成后,若没有后续的动作,该连接就处于空闲状态,可通过
show processlist 命令查看所有连接状态。其中 Command 列显⽰为 Sleep 表⽰系统中有⼀个空闲连接。
客户端如果长时间处于Sleep,连接器会将其⾃动断开,断开时间基于配置的 wait_timeout 参数,默认值是8⼩时。
如果连接断开之后,客户端再次发送请求的话,就会收到错误:Lost connection to MySQL server during query。此时,继续执⾏请求就需要重连。
MySQL中,长连接是指连接成功后,如果客户端持续有请求,则⼀直使⽤同⼀个连接。短连接则是指每次执⾏完很⼩的⼏次查询就断开连接,下次查询重建新连接。
建⽴连接的过程⼀般是⽐较复杂的,所以我们应该尽量使⽤长连接。
但是并不是说长连接就没有问题,因为MySQL执⾏过程中临时使⽤的内存是管理在连接对象中的,所以每次执⾏完请求,都会增加连接的内存占⽤,这些资源⼀直不释放会导致内存爆满,被系统强⾏kill掉,从现象上看就是MySQL异常重启了。
那么解决这个问题的⽅案就需要考虑以下两点:
1、定期断开长连接,或在程序中判断某个连接内存占⽤过⼤,就将其关闭,之后使⽤再连接即可。
2、MySQL 5.7之后的版本,有⼀个 mysql_reset_connection 的函数可以重新初始化连接资源,也就是释放该连接占⽤的内存。这个过程不需要重连和重新做权限验证就会将连接恢复到刚刚创建完时的状态。
查询缓存
连接建⽴完成之后,我们就可以执⾏ select 语句了,此时就会来到执⾏逻辑的第⼆步:查询缓存。
select语句查询日期
这⼀步不做过多说明,因为MySQL 8.0版本直接将查询缓存的整块功能删掉了。
查询缓存我们往往不需要使⽤,因为其命中率实在是低下。对⼀个表就⾏更新操作就会使表上的所有查询缓存清空。
分析器
分析器的执⾏逻辑就是,内建解析树,对其进⾏词法语法检查,⽣成新的解析树,语义检查(字段,表是否存在)等。
⾸先进⾏词法分析:主要是根据MySQL的关键字进⾏验证和解析以及识别表名和列名。
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
之后进⾏语法分析:在词法分析的基础上,判断sql语句是否合法。
若语句不正确(如字段不存在),就会收到报错信息:
如果我们执⾏这个语句:其中表T中字段k不存在,则会在分析器阶段就报错
select * from T where k=1
Unknown column ‘k’ in ‘
之后会进⾏⼀个权限检查,这也就是如果⽤户没有T表的权限,且sql中字段k页不存在,却报错为⽆权限的原因。
优化器
经过分析器之后,MySQL已经知道我们要做什么了,在开始执⾏sql之前,还要经过优化器的处理。
简单来说,优化器就是优化sql执⾏计划。
优化器是在表⾥⾯有多个索引的时候,决定使⽤哪个索引;或者在⼀个语句有多表关联的时候(join)的时候,决定各个表的连接顺序。
简单来说:优化器就是将前⾯⽣成的解析树转换成执⾏计划,并进⾏最优选择。
如执⾏以下语句:
mysql> 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。
以上两种执⾏⽅法的逻辑结果是⼀样的,但是执⾏效率可能存在不同,优化器的作⽤就是决定选择使⽤哪⼀种⽅案。
优化器执⾏完之后,就会进⼊执⾏器阶段。
预埋问题:优化器怎么选择索引?会不会选错?
执⾏器
MySQL通过分析器知道了我们要做什么,通过优化器知道了该怎么去做,到了执⾏器阶段,就开始执⾏逻辑了。
简单来说,执⾏器就是检查权限,打开表,处理数据。
执⾏sql时,会判断sql中的表以及操作涉及到的其他表(如更新⼀张表触发更新另⼀个表的触发器操作)
在当前连接上有没有相关权限,若没有,则会返回权限错误;若有则继续处理数据
注:并⾮在执⾏器才执⾏权限检查,事实上,在命中查询缓存时,会在缓存返回结果时,做权限验证。且在优化器之前也会调⽤ precheck 验证权限。
mysql> select * from T where ID=10;
ERROR 1142 (42000): SELECT command denied to user 'b'@'localhost' for table 'T'
如 select * from T where ID=10; 这条语句,表T中的ID字段没有索引,那么执⾏的流程就是:
1、调⽤ InnoDB 引擎接⼝取表的第⼀⾏,判断ID值是否为10,不是则跳过,是则将该⾏信息存在结果集中。
2、调引擎接⼝继续获取下⼀⾏,重复第1步判断,直⾄最后⼀⾏数据。
3、执⾏器将结果集返回给客户端。
对于有索引的表,第⼀次调⽤的是引擎中取满⾜条件的第⼀⾏的接⼝,之后循环取满⾜条件的下⼀⾏。这些接⼝都是引擎中已经定义好的。
之后我们可以在数据库的慢查询⽇志中看到⼀个 rows_examined 的字段,表⽰这个语句执⾏过程中扫描了多少⾏。该值就是在执⾏器每次调⽤引擎获取数据⾏的时候累加的。
在某些场景下,执⾏器调⽤⼀次,在引擎内部则会扫描多⾏,因此引擎扫描⾏数和 rows_examined 并不是完全相同的。
在开启查询缓存时,此时的结果集会更新到查询缓存中。
问题
1、在执⾏器阶段为什么要判断对表是否具有执⾏查询的权限,⽽不是在分析器去判断?根据 MySQL 中的 information_schema 库的存储信息,理论上可以在分析器阶段判断是否有权限。
有时sql语句要操作的表不只是sql字⾯上的那些,⽐如有个触发器,得在执⾏器阶段才能确定,所以优化器前判断权限有时候不完整。
2、创建⼀个没有 select 权限的⽤户,执⾏ select * from T where k=1;(表T中⽆k字段),报错信息为:select command denied ⽽不是:unknown column 是否可以说明在打开表之后才判断读取的列不存在?
并不能说明,为了安全起见,返回⽆权限信息肯定是最好的
总结
客户端通过连接器连接到服务端,获取到权限等信息,然后在连接的有效时长内(interactive_timeout 和 wait_timeout 参数控制,5.7版本会断开⾃动重连)对客户端请求进⾏处理。
判断sql是select/update/delete/insert 中的哪⼀种,若是 select 判断查询缓存是否开启。(此时判断的select是简单判断,其他解析并没有做,所以分析器会再次判断并解析)
若查询缓存开启,则
若命中,则在数据返回时会判断查询权限,权限通过则返回数据。
若未命中,则进⼊分析器。
若未开启直接进⼊分析器。
分析器进⾏词法分析,语法分析,检查sql的语法顺序等⽣成解析树,然后预处理器对解析树进⼀步分析,验证对表是否具有相应权限以及数据表,字段是否存在,验证通过后更新解析树,交给优化器处理。
优化器对sql的执⾏计划进⾏最优选择,将最优执⾏计划交给执⾏器。
执⾏器再次验证相应权限,验证通过后,调⽤引擎接⼝获取数据并将结果返回给客户端,若查询缓存开启,则更新缓存。若是
update/delete/insert请求,则删除查询缓存。
⼀些容易漏掉的知识:
1、连接器是从权限表⾥⾯查询⽤户权限并保存在⼀个变量⾥⾯以供查询缓存、分析器、执⾏器在检查权限的时候使⽤。
2、sql在执⾏的过程中,可能会有触发器这种在运⾏时才能确定是否有相应权限的操作,分析器⼯作结束后的 precheck 操作是不能对这种运⾏时涉及到的表进⾏权限校验的,所以需要在执⾏器阶段进⾏权限检查。另外也是因为有precheck这个步骤,才会在报错时显⽰为⽤户⽆权,⽽不是字段k不存在,此举是为了不向⽤户暴露表结构。
3、词法分析阶段是从information_schema⾥⾯获得表的结构信息的。
4、可使⽤连接池的⽅式,将短连接变为长连接
5、mysql_reset_connection 是MySQL为各个语⾔提供的api,⾮sql语句。
6、wait_timeout 是⾮交互式连接的空闲超时,interactive_timeout 是交互式连接的空闲超时。执⾏时间不计⼊空闲时间。通过客户端连接的为交互式,通过程序连接的是⾮交互式
版权声明:本站内容均来自互联网,仅供演示用,请勿用于商业和其他非法用途。如果侵犯了您的权益请与我们联系QQ:729038198,我们将在24小时内删除。
发表评论