暂无图片
暂无图片
暂无图片
暂无图片
暂无图片

SQL执行过程

程序猿读历史 2021-05-31
1032



本文介绍一个查询sql在数据库内部是如何执行的,比如下面一个sql语句在数据库内部的到底怎么实现的。

    mysql>select * from dept_emp limit 1;
    +--------+---------+------------+------------+
    | emp_no | dept_no | from_date | to_date |
    +--------+---------+------------+------------+
    | 10001 | d005 | 1986-06-26 | 9999-01-01 |
    +--------+---------+------------+------------+
    1 row in set (0.00 sec)


    在解释这条语句执行流程之前,我们先看看mysql的基础架构:

    上图主要有server层和引擎层组成。


    • server层

    server层包括连接器、查询缓存、分析器、优化器、执行器,以及所有的内置函数所有跨存储引擎的功能都在这一层实现,比如存储过程、触发器、视图等。


    • 引擎层

    存储引擎层负责数据的存储和读写。其架构模式是插件式的,支持InnoDB(支持事务,引进redo和undo)、MyISAM、Memory 等多个存储引擎,5.5版本之后,默认引擎是innoDB引擎。由上图发现不同的存储引擎使用相同的server层。


    1. 1、Connection pool连接池


    我们在使用数据库之前,需要连接到数据库,连接语句是:

      [root@mysql-master ~]# mysql -uroot -proot -P3306 -hlocalhost


      连接池就是处理这个过程的,主要功能是负责跟客户端建立连接、获取权限、维持和管理连接,连接池在使用的过程中如果该用户的权限改变,是不会马上生效的,因为用户权限是在连接的时候读取的,只能重新连接才可以更新权限。主要通过tcp/ip协议和socket协议连接。


      • 如果用户名或密码不对,你就会收到一个"`Access  denied for user`"的错误,然后客户端程序结束执行。

      • 如果用户名密码认证通过,连接器会到权限表里面查出你拥有的权限。之后,这个连接里面的权限判断逻辑,都将依赖于此时读到的权限判断逻辑,都将依赖于此时读到的权限。


      密码输错的抱错:

        [root@mysql-master ~]# mysql -uroot -p123
        Warning: Using a password on the command line interface can be insecure.
        ERROR 1045 (28000): Access denied for user 'root'@'localhost' (using password: YES)


        没有输入密码的报错:

          [root@mysql-master ~]# mysql -uroot -p
          Enter password:
          ERROR 1045 (28000): Access denied for user 'root'@'localhost' (using password: NO)


          连接完成后,如果没有和数据库做交互,这个链接就处于空闲状态,可以在show processlist里看到Command列状态为sleep值。mysql默认该连接是8小时,超过8小时后断开,8小时这个值由两个参数确定:

          • interactive_timeout

          • wait_timeout

            mysql>show processlist
            +----+------+-----------+------+---------+------+----------+------------------+
            | Id | User | Host | db | Command | Time | State | Info |
            +----+------+-----------+------+---------+------+----------+------------------+
            | 21 | root | localhost | NULL | Query | 0 | starting | show processlist |
            | 22 | root | localhost | NULL | Sleep | 5 | | NULL |
            +----+------+-----------+------+---------+------+----------+------------------+
            2 rows in set (0.00 sec)


            1. 2、缓存器


            经过连接以后,就连接上数据库了,这个时候可以执行语句了。


            执行语句的时候,mysql首先是去查询缓存,之前有没有执行过这样的语句,mysql会将之前执行过的语句和结果以key-value的形式存储起来(当然有一定的存储和实效时间)。如果存在缓存,则直接返回缓存的结果。


            缓存的工作流程是:


            •  服务器接收SQL,以SQL和一些其他条件为key查找缓存表

            • 如果找到了缓存,则直接返回缓存

            • 如果没有找到缓存,则执行SQL查询,包括原来的SQL解析,优化等。

            • 执行完SQL查询结果以后,将SQL查询结果缓存入缓存表


            当这个表被写时,则这个表的缓存(命中缓存,缓存写入等)将会处于失效状态,在Innodb中,如果某个事务修改了这张表,则这个表的缓存在事务提交前都会处于失效状态,在这个事务提交前,这个表的相关查询都无法被缓存。


            如果是一张静态表或者是很少变化的表就可以进行缓存,这样的效率会很高,mysql8.0开始删除了缓存查询功能,现在我们一般都会建议关闭查询缓存。


            关闭缓存设置:

              #关闭query cache
              query_cache_size = 0
              query_cache_type = 0


              1. 3、解析器


              如果没有命中缓存,就要开始执行语句了,首先mysql要对sql语句进行解析。


              MySQL解析器由两部分组成,

              • 词法分析扫描字符流,根据构词规则识别单个单词,MySQL使用Flex来生成词法扫描程序在sql/lex.h中定义了MySQL关键字和函数关键字,用两个数组存储。

              • 语法分析在词法分析的基础上将单词序列组成语法短语,最后生成语法树,提交给优化器语法分析器使用Bison,在sql/sql_yacc.yy中定义了语法规则。然后根据关系代数理论生成语法树。


              大白话总结:

              其实解析器主要是用来进行“语法分析”,然后mysql知道这个语句是用来干嘛,并且会预处理,生成语法树。



                mysql>select * frm t;
                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 'frm t' at line 1
                mysql>



                上面这个语句有三个错误:

                • 未选择库。

                • 表不存在

                • 关键字语法错误。


                由上面实验可以看出,mysql优先报语法错误。


                1. 4、优化器


                sql经过了解连接池、解析器之后,在执行之前还要经过优化器,优化器的任务是发现执行SQL查询的最佳方案。大多数查询优化器,包括MySQL的查询优化器,总或多或少地在所有可能的查询评估方案中搜索最佳方案。


                优化器主要是选择一个最佳的执行方案,执行方案是为了减少开销,提高执行效率。


                MySQL的优化器是一个非常复杂的部件,它使用了非常多的优化策略来生成一个最优的执行计划:


                • 重新定义表的关联顺序(多张表关联查询时,并不一定按照SQL中指定的顺序进行,但有一些技巧可以指定关联顺序)

                • 优化MIN()和MAX()函数(找某列的最小值,如果该列有索引,只需要查找B+Tree索引最左端,反之则可以找到最大值,具体原理见下文)

                • 提前终止查询(比如:使用Limit时,查找到满足数量的结果集后会立即终止查询)

                • 优化排序(在老版本MySQL会使用两次传输排序,即先读取行指针和需要排序的字段在内存中对其排序,然后再根据排序结果去读取数据行,而新版本采用的是单次传输排序,也就是一次读取所有的数据行,然后根据给定的列排序。对于I/O密集型应用,效率会高很多)


                优化器使用的优化策略也在不断的进化,这里仅仅介绍几个非常常用且容易理解的优化策略而已。


                大白话总结:

                解析器是知道要做什么,优化器是知道要怎么做。


                1. 5、执行器


                在解析器知道语句要干什么,优化器知道怎么做以后,就到了执行的阶段,执行是交给执行器的。


                执行器在执行的时候首先判断该用户对该表有没有执行权限,如果没有则会返回denied之类的错误提示(可能大家会有疑问,前面说连接池阶段就对权限认证,为什么到这里又是权限认证,因为有时候SQL语句要操作的表不只是SQL字面上那些。比如如果有触发器、外键,得在执行器阶段过程中才能确定)。


                如果有权限,则会打开表继续执行。打开表的时候,执行器会根据表定义的引擎,去使用该引擎的接口。最后执行语句得到数据返回给客户端。



                MySQL得到sql语句后,大概流程如下:


                    1.连接器负责和客户端进行通信

                    2.查询缓存:首先查询缓存看是否存在k-v缓存

                    3.解析器:负责解析和转发sql

                    4.预处理器:对解析后的sql树进行验证

                    5.优化器:得到一个执行计划

                    6.查询执行引擎:执行器执行语句得到数据结果集

                    7.将数据放回给调用端。



                二、 附录

                下面通过profile方法测试下整个过程。


                开启profile

                  mysql>show variables like 'profiling';
                  +---------------+-------+
                  | Variable_name | Value |
                  +---------------+-------+
                  | profiling | OFF |
                  +---------------+-------+
                  1 row in set (0.00 sec)




                  mysql>set global profiling=1;
                  Query OK, 0 rows affected, 1 warning (0.00 sec)


                  执行该语句报表不存在错误:

                   

                    mysql>select * from dept_emp1 limit 1;
                    ERROR 1146 (42S02): Table 'employees.dept_emp1' doesn't exist


                    show profile for query 1;报表不存在的错误,在Opening tables步骤时被发现错误,然后查询结束。

                    show profile for query 2;语句时正确的sql执行耗时。

                      mysql> show profiles;
                      +----------+------------+---------------------------------+
                      | Query_ID | Duration | Query |
                      +----------+------------+---------------------------------+
                      | 1 | 0.00024925 | select * from dept_emp1 limit 1 |
                      | 2 | 0.00039925 | select * from dept_emp limit 1 |
                      +----------+------------+---------------------------------+
                      9 rows in set, 1 warning (0.00 sec)




                      mysql>show profile for query 1;
                      +----------------------+----------+
                      | Status | Duration |
                      +----------------------+----------+
                      | starting | 0.000166 |
                      | checking permissions | 0.000009 |
                      | Opening tables | 0.000123 |
                      | query end | 0.000069 |
                      | closing tables | 0.000008 |
                      | freeing items | 0.000014 |
                      | cleaning up | 0.000011 |
                      +----------------------+----------+
                      7 rows in set, 1 warning (0.00 sec)




                      mysql>show profile for query 2;
                      +----------------------+----------+
                      | Status | Duration |
                      +----------------------+----------+
                      | starting | 0.000079 |
                      | checking permissions | 0.000012 |
                      | Opening tables | 0.000016 |
                      | init | 0.000020 |
                      | System lock | 0.000009 |
                      | optimizing | 0.000005 |
                      | statistics | 0.000012 |
                      | preparing | 0.000011 |
                      | executing | 0.000003 |
                      | Sending data | 0.000040 |
                      | end | 0.000005 |
                      | query end | 0.000008 |
                      | closing tables | 0.000007 |
                      | freeing items | 0.000012 |
                      | cleaning up | 0.000011 |
                      +----------------------+----------+
                      15 rows in set, 1 warning (0.00 sec)


                      文章转载自程序猿读历史,如果涉嫌侵权,请发送邮件至:contact@modb.pro进行举报,并提供相关证据,一经查实,墨天轮将立刻删除相关内容。

                      评论