我们经常说,看一个事儿千万不要直接陷入细节中,你应该先鸟瞰其全貌,这样能帮助你从高纬度理解问题。同样,对于mysql的学习也是这样。平时我们使用的数据库,看到通常是一个整体。比如,你有个最简单的表,表里有个id字段,在执行下面语句时候:
select * from T where ID = 10;
我们看到的知识输入的一条语句,返回一个结果,却不知道这条语句在mysql中内部的执行过程。
所以今天我想和你一起吧mysql拆解一下,看看里面有哪些零件,希望借由这个拆解过程,对mysql有更深入的理解。这样当我们碰到mysql的一些异常或者问题时,能直戳本质,更为快速的定位并解决问题。
下面我们给出mysql基本框架示意图,从中卡伊看到sql语句在mysql各个功能模块的执行过程。
大体来说,mysql可分为server层和存储引擎层两个部分。
server层包括连接器,查询缓存,分析器,优化器,执行器等,涵盖mysql的大多数核心服务功能,以及所有的内置函数(日期,时间,数学和加密函数等) ,所有跨存储 引擎的功能都在这层实现,比如存储过程,触发器,视图等。
存储引擎层负载数据的存储和提取。其架构模式是插件式的,支持innodb,myisam,memory等多个存储引擎,现在最常用的存储引擎是innodb,他从mysql5.5.5版本之后成为 了默认的存储引擎。
也就是说,你执行create table 建表的时候,如果不指定存储引擎的话,默认使用的事innodb,不过,你也可以通过制定存储引擎来选择,比如使用create table engine=memory 来制定内存引擎创建表。
从图中看出,不同的存储引擎公用一个server层,也就是从连接器到执行器的部分。你可以先对每个组件的名字都有个音响,
第一步,你会先连接到这个数据库上,这时候接待你的事连接器,连接器负责跟客户端建立连接
根据权限,维持和管理连接,连接命令一般这么写:
mysql -h ip -p port -u user -p
输入完成 ,弹出输入密码
连接命中的mysql是客户端工具,用来跟服务端建立连接。在完成经典的tcp握手之后,连接器就要开始认证你的身份,这个时候用你的输入用户名和密码
如果密码不对,则会收到“access denied for user” 的错误,然后客户端结束执行。
如果用户名密码认证通过,连接器则会到权限表中查看你的权限。之后,这个链接里面的权限判断逻辑,都将以来此时读到的权限。
就意味着,一个用户成功建立连接后, 即使管理员对这个用户权限作了修改,也不会影响已经存在的连接的权限, 修改完成后,只有在新建的连接 才会有新的权限设置。
连接完成后, 如果你没有后续操作,这个链接处于空闲状态,你可以zai show processlist 命令中看到他,文本中图就是show processlist的结果,其中 command例显示的为sleep、
这个一行,表示系统里面有一个空闲的连接。
客户端如果太长时间没有动静,连接器就会自动断开,这个时间参数wait_timeout控制 ,默认值8小时。
如果在连接器断开之后,客户端再次发送请求的话,会提醒一句“lost connection to mysql server during query” 这个时候如果要继续 需要从连, 然后再执行请求。
数据库里面长链接之连接成功后,如果客户端持续请求,则一直使用同一个连接,短连接是每次执行完很少的几次查询就断开,再次查询则重新建立一个。
建立连接过程通常是比较复杂的, 所以建议在使用中尽量减少建立连接的动作,也就是尽量使用长链接。
但是在使用长链接后,可能会发现,有些时候mysql占用内存长得特别快,这是因为mysql在执行过程中临时使用内存是管理在连接对象里面的。这些资源在连接断开之后才会释放。所以, 如果长链接累计下来,可能会导致内存占用太大,被系统强行傻掉(oom),从现象看就是mysql异常重启了,
怎么解决呢,通常两种方案
1 定期断开长链接,使用一段时间,或者程序里面判断执行一个占用内存大的查询后,断开连接,
之后再重新连接
2 如果你用mysql5.7 或者更新的版本,可以在执行一个比较大的操作后,通过执行mysql_reset_connection 来重新初始化连接资源。 这个过程不需要重连和重新权限验证,但是会连接恢复到刚刚创建完成时的状态。
建立链接以后,你就可以执行select语句了。执行逻辑就会到第二步, 查询缓存。
mysql拿到一个查询请求后,会先到查询缓存看看,之前是不是执行过这条语句。之前执行的语句以及结果可能会以key-value对的形式,被直接缓存在内存中,key是查询语句,value是查询的结果。 如果你的查询能够砸这个缓存中找到key,那么这个value则被直接返回给客户端。
如果语句不在查询缓存中,就会继续执行后面的阶段。执行完成后,执行结果将被放入到缓存中。可以看到,如果查询命中缓存,mysql不需要执行后面的复杂操作,就可以直接返回结果,这个效率会很高。
但是大多数情况下建议你不要使用查询缓存。因为查询缓存弊大于利。
首先,查询缓存失效特别频繁,只要对一个表的更新,这个这个表上的所有缓存都会被清空,因此,可能你费劲的把结果存起来,还没开始使用,就已经被清空了。对于更新压力大的数据库来说,查询缓存命中率特别低。除非你的业务只有一张静态表,很长时间才会更新一次,比如系统配置表,那这张表的查询才适合查询缓存。
好在mysql也提供了按需使用 的方式。你可以将参数 query_cache_type设置成DEMAND.这样对于默认的sql语句都不使用查询缓存,而对于你确定使用的查询缓存的语句,可以使用 sql_cache 显示指定,像下面sql:
mysql> select sql_cache * from table where id =1;
需要注意的是, mysql8.0 版本直接将查询缓存整块删除了,所以8.0之后的版本不在支持这个功能了。
如果没有命中缓存,就要开始真正执行语句了。首先,mysql需要知道你要做什么,因此对sql语句做解析。
分析器会做“词法分析”。 你输入的由多个字符串和空格组成的sql。 mysql 需要识别出里面的字符串分别是什么的, 代表了什么。
mysql 从你输入的 select 这个关键字识别开始, 这是一个查询语句,他要把字符串 table 识别成表table, 把字符串id识别成列id。
如果你收到“”“you have an error in your sql syntax; check the manual yhate.....”
就是分析器返回你的。
经过了分析器, myql知道要做什么了, 在开始执行之前, 还是要先经过优化器的处理,、
优化器是在表里面多个索引的时候 ,决定使用哪个索引, 或者一个语句多表关联的时候, 决定各个表的连接顺序,如:、
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通过分析器知道了你要做什么,通过优化器知道该怎么做, 于是到了执行阶段,开始执行语句
开始执行的时候,首先判断一下你对这个表t有没有查询权限, 如果没有,就直接返回执行错误。
如果有权限直接执行.....