SQL语句在MySQL中如何执行
MySQL的基础架构
首先就是客户端,其次Server服务层,大多数MySQL的核心服务都在这一层,包括连接、分析、优化、缓存以及所有的内置函数(时间、日期、加密函数),所有跨存储引擎功能都在这一层实现:存储过程、触发器、视图等;最后存储引擎层,负责MySQL中数据的存储和提取,Server层通过API与存储引擎进行通信,这些接口屏蔽了不同存储引擎之间差异,使得这些差异对上层(Server)的查询过程比较透明(清晰,没有阻碍,差异不存在一样)。
SQL语句在MySQL中如何执行
-
客户端发送SQL查询语句到MySQL的服务器
-
MySQL服务器的连接器开始处理这个请求,跟客户端建立连接,获取权限,管理连接
-
(Mysql8.0之前还有查询缓存,不过后面数据不一会就变更需要更新缓存,就显得鸡肋了点,就删除了)
-
使用解析器(分析器)去对SQL语句进行解析,检查语法规则,确保引用的数据库、表和列都存在,并处理SQL语句中的名称解析和权限验证。(首先词法分析,然后语法分析和分析机不断循环遍历关键字添加到语法树中,生成语法树)
-
使用优化器负责确定SQL语句的执行计划,这包括选择使用哪些索引,以及决定表之间的连接顺序,会尝试找出最高效的方式来执行查询。
-
执行器会调用存储引擎的API来进行数据的读写(使用锁)
-
在引擎层中写一个undolog版本链用于MVCC回滚
写redolog,写下所有命令,用于故障恢复
若有开启binlog,这时会写binlog用于主从同步
提交事务,刷redolog进磁盘,刷binlog进磁盘,二阶段提交保证数据一致性。
-
MySQL的存储引擎是插件式的,不同的存储引擎在细节上面有很大不同,如InnoDB支持事务,MyISAM不支持。将执行结果返回给客户端
-
客户端接收到查询结果,完成这次查询请求。
详细讲解
-
客户端发送 SQL 查询语句到 MySQL 服务器及连接器处理
- 连接建立:
- 客户端通过网络发送 SQL 查询语句到 MySQL 服务器指定的端口(通常是 3306)。服务器的连接器首先会处理这个连接请求。它会验证客户端提供的连接参数,包括主机地址、端口、用户名和密码。例如,当使用 MySQL 命令行客户端连接时,用户输入
mysql -h [服务器地址] -u [用户名] -p
,然后输入密码,服务器会根据配置文件(如user
表中的用户记录)来检查用户名和密码是否匹配。
- 客户端通过网络发送 SQL 查询语句到 MySQL 服务器指定的端口(通常是 3306)。服务器的连接器首先会处理这个连接请求。它会验证客户端提供的连接参数,包括主机地址、端口、用户名和密码。例如,当使用 MySQL 命令行客户端连接时,用户输入
- 权限获取:
- 一旦连接通过验证,连接器会根据用户账户的权限设置来确定该用户对数据库的操作权限。这些权限包括对特定数据库、表、列的读取、写入、修改等权限。例如,一个用户可能被授予对某个数据库中某些表的
SELECT
和INSERT
权限,但没有DELETE
权限。权限信息存储在 MySQL 的系统数据库(如mysql
数据库中的相关权限表)中,连接器会查询这些表来获取用户的权限范围。
- 一旦连接通过验证,连接器会根据用户账户的权限设置来确定该用户对数据库的操作权限。这些权限包括对特定数据库、表、列的读取、写入、修改等权限。例如,一个用户可能被授予对某个数据库中某些表的
- 连接管理:
- 连接器会维护连接的状态,包括跟踪连接是否处于活动状态、是否超时等。它还会管理连接池(如果配置了连接池),在有多个客户端连接时,合理地分配和复用连接资源。例如,当一个客户端长时间没有发送任何请求时,连接器可能会根据服务器的配置(如
wait_timeout
参数)来判断是否关闭该连接,以释放资源。
- 连接器会维护连接的状态,包括跟踪连接是否处于活动状态、是否超时等。它还会管理连接池(如果配置了连接池),在有多个客户端连接时,合理地分配和复用连接资源。例如,当一个客户端长时间没有发送任何请求时,连接器可能会根据服务器的配置(如
- 连接建立:
-
SQL 语句解析(解析器处理)
- 词法分析:
- 解析器首先进行词法分析,它会将 SQL 语句分解为一个个的单词(也称为词法单元)。例如,对于语句
SELECT * FROM users WHERE age > 30
,解析器会将其分解为SELECT
、*
、FROM
、users
、WHERE
、age
、>
、30
等词法单元。这些词法单元是 SQL 语法的基本组成部分,解析器会根据预定义的词法规则(如关键字、标识符、常量、操作符等的规则)来识别它们。
- 解析器首先进行词法分析,它会将 SQL 语句分解为一个个的单词(也称为词法单元)。例如,对于语句
- 语法分析和语法树生成:
- 在完成词法分析后,解析器会进行语法分析。它会根据 SQL 的语法规则来检查这些词法单元的组合是否合法。解析器会使用一种类似于状态机的机制,不断循环遍历这些关键字和符号,按照语法规则构建一个语法树。例如,在上述语句中,解析器会识别出
SELECT
是查询操作的关键字,*
表示选择所有列,FROM
指定了要查询的表是users
,WHERE
引导了筛选条件。它会将这些信息构建成一个层次结构的语法树,其中SELECT
节点是根节点,它的子节点包括*
和一个表示FROM
子句的节点,FROM
子句节点的子节点是users
,还会有一个表示WHERE
子句的分支,其下包含age
、>
和30
等节点。在这个过程中,解析器还会检查引用的数据库、表和列是否存在。例如,它会查询数据库的元数据(存储在系统表中)来验证users
表是否存在,以及age
列是否是users
表中的列。同时,也会进行名称解析和权限验证。如果用户没有对users
表的SELECT
权限,解析器会返回权限错误。
- 在完成词法分析后,解析器会进行语法分析。它会根据 SQL 的语法规则来检查这些词法单元的组合是否合法。解析器会使用一种类似于状态机的机制,不断循环遍历这些关键字和符号,按照语法规则构建一个语法树。例如,在上述语句中,解析器会识别出
- 词法分析:
-
优化器确定执行计划
- 索引评估:
- 优化器会首先查看 SQL 语句中涉及的表和列是否有可用的索引。例如,对于查询
SELECT * FROM users WHERE username = 'john'
,如果username
列有索引,优化器会考虑使用该索引来加速查询。它会评估索引的类型(如 B - Tree 索引、哈希索引等)、索引的选择性(即通过索引能够过滤掉多少数据)等因素。例如,一个索引的选择性高意味着通过该索引能够快速定位到少量满足条件的数据行,优化器会更倾向于使用这样的索引。
- 优化器会首先查看 SQL 语句中涉及的表和列是否有可用的索引。例如,对于查询
- 表连接顺序确定:
- 当 SQL 语句涉及多个表的连接时,优化器会决定表之间的连接顺序。例如,对于连接查询
SELECT * FROM table1 JOIN table2 ON table1.id = table2.id JOIN table3 ON table2.id = table3.id
,优化器会根据表的大小(通过统计信息,如每个表的行数)、索引情况等来判断先连接哪两个表更高效。如果table1
是一个小表,并且id
列有索引,而table2
和table3
相对较大,优化器可能会先将table1
和table2
进行连接,然后再连接table3
,以减少中间结果集的大小,提高查询效率。
- 当 SQL 语句涉及多个表的连接时,优化器会决定表之间的连接顺序。例如,对于连接查询
- 执行计划生成和评估:
- 优化器会生成多种可能的执行计划,并根据成本模型来评估每个执行计划的成本。成本模型会考虑多种因素,如磁盘 I/O 操作次数、CPU 计算量、内存使用等。例如,一个需要进行大量磁盘扫描的执行计划成本可能较高,而一个能够充分利用索引、减少磁盘 I/O 的执行计划成本较低。优化器会选择成本最低的执行计划作为最终的执行计划,这个计划将指导执行器如何进行数据的读写操作。
- 索引评估:
-
执行器调用存储引擎 API 进行数据读写(使用锁)
- 读写操作启动:
- 执行器根据优化器确定的执行计划,开始调用存储引擎的 API 进行数据的读写操作。例如,对于查询操作,执行器会按照计划从存储引擎中读取数据。如果执行计划是进行全表扫描,执行器会通过存储引擎的接口逐行读取表中的数据;如果是利用索引进行查询,执行器会通过索引接口快速定位到满足条件的数据行。
- 锁的使用:
- 在进行数据读写时,执行器会根据 SQL 语句的性质(如是否是事务中的操作、是否涉及并发访问等)和存储引擎的锁机制来使用锁。例如,在 InnoDB 存储引擎中,如果执行一个
SELECT... FOR UPDATE
语句,执行器会对查询结果集对应的行加上排他锁(X 锁),以防止其他事务同时修改这些行。对于并发的事务,锁可以保证数据的一致性和完整性。不同的存储引擎有不同的锁机制,执行器会根据存储引擎的规则来正确地获取和释放锁。
- 在进行数据读写时,执行器会根据 SQL 语句的性质(如是否是事务中的操作、是否涉及并发访问等)和存储引擎的锁机制来使用锁。例如,在 InnoDB 存储引擎中,如果执行一个
- 读写操作启动:
-
引擎层 MVCC 回滚相关操作(undolog 版本链)
- 版本链创建:
- 在 InnoDB 存储引擎中,为了支持多版本并发控制(MVCC),会为每一行数据创建一个版本链。当对一行数据进行修改时,存储引擎不会直接覆盖原来的数据,而是会将修改前的数据作为一个旧版本,通过一个链表结构(版本链)将旧版本和新版本连接起来。例如,最初有一行数据
(id = 1, value = 'A')
,当将value
修改为'B'
时,会在存储引擎中保留旧版本(id = 1, value = 'A')
,并创建一个新版本(id = 1, value = 'B')
,这两个版本通过版本链连接。
- 在 InnoDB 存储引擎中,为了支持多版本并发控制(MVCC),会为每一行数据创建一个版本链。当对一行数据进行修改时,存储引擎不会直接覆盖原来的数据,而是会将修改前的数据作为一个旧版本,通过一个链表结构(版本链)将旧版本和新版本连接起来。例如,最初有一行数据
- MVCC 和回滚操作:
- MVCC 允许不同事务在不同时间点看到同一行数据的不同版本。在事务执行过程中,如果需要回滚操作,存储引擎可以根据 undolog 版本链找到事务修改之前的数据版本,将数据恢复到事务开始之前的状态。例如,一个事务读取了
(id = 1, value = 'A')
,然后另一个事务将value
修改为'B'
,如果第一个事务设置了隔离级别为可重复读(REPEATABLE READ),它仍然可以看到(id = 1, value = 'A')
这个版本的数据。如果第二个事务需要回滚,存储引擎可以通过 undolog 版本链将数据恢复为(id = 1, value = 'A')
。
- MVCC 允许不同事务在不同时间点看到同一行数据的不同版本。在事务执行过程中,如果需要回滚操作,存储引擎可以根据 undolog 版本链找到事务修改之前的数据版本,将数据恢复到事务开始之前的状态。例如,一个事务读取了
- 版本链创建:
-
日志相关操作(redolog 和 binlog)
- redolog 记录:
- redolog 用于记录数据库的物理修改操作,它是一种基于磁盘的日志。在执行对数据的修改操作(如插入、更新、删除)时,存储引擎会先将修改操作记录到 redolog 中。例如,当执行
UPDATE users SET age = 31 WHERE id = 1
时,存储引擎会将这个修改操作的相关信息(如修改的表、列、新值和旧值等)记录到 redolog 中。redolog 采用了预写式日志(WAL)的机制,即先写日志,后修改数据,这样可以保证在数据库发生故障(如突然断电、系统崩溃等)时,通过 redolog 来恢复尚未完成的事务,保证数据的持久性。
- redolog 用于记录数据库的物理修改操作,它是一种基于磁盘的日志。在执行对数据的修改操作(如插入、更新、删除)时,存储引擎会先将修改操作记录到 redolog 中。例如,当执行
- binlog 记录(如果开启):
- binlog 主要用于数据库的主从复制和数据恢复等用途。如果开启了 binlog(通过配置参数),在执行 SQL 语句时,存储引擎会将 SQL 语句(以事件的形式)记录到 binlog 中。例如,在主从复制环境中,主数据库上的每一个修改操作都会被记录到 binlog 中,然后从数据库会通过读取主数据库的 binlog 来同步数据。binlog 的记录格式有多种(如 STATEMENT、ROW、MIXED),不同的格式记录的内容和方式略有不同。例如,ROW 格式会记录每一行数据的详细修改情况,而 STATEMENT 格式会记录执行的 SQL 语句。
- 二阶段提交保证数据一致性:
- 在事务提交时,MySQL 会使用二阶段提交(2PC)来保证 redolog 和 binlog 的一致性。首先,存储引擎会准备好提交事务,将事务的状态设置为可以提交,这个过程会涉及到将 redolog 从内存刷到磁盘(部分情况下)等操作。然后,在确保 redolog 已经准备好提交后,才会将 binlog 也刷到磁盘。只有当 redolog 和 binlog 都成功写入磁盘后,事务才真正提交成功。这样可以保证在数据库恢复或者主从复制过程中,数据的一致性和完整性。
- redolog 记录:
-
提交事务及返回结果给客户端
- 事务提交:
- 当所有的数据读写操作完成,日志也按照要求记录后,执行器会提交事务。在提交事务过程中,会根据前面提到的二阶段提交机制,确保数据的一致性。如果在提交过程中出现问题(如磁盘满、网络故障等),事务可能会根据日志进行回滚,以保证数据的完整性。
- 结果返回:
- 对于查询操作,存储引擎将查询到的数据结果集返回给执行器,执行器再将结果返回给服务器的连接器,最后由连接器将结果发送给客户端。客户端接收到查询结果后,可以根据自己的需求进行处理,例如在命令行中显示结果、在图形化客户端中以表格形式展示结果等。对于非查询操作(如插入、更新、删除),如果操作成功,会返回相应的成功信息(如受影响的行数)给客户端,完成这次查询请求。
- 事务提交: