推广 热搜: csgo  vue  angelababy  2023  gps  新车  htc  落地  app  p2p 

一条SQL查询语句是如何执行的?

   2023-06-23 网络整理佚名940
核心提示:现在有一张500W行且没有添加索引的数据表,我执行以下命令两次,第二次会不会变得很快?从你输入的这个关键字识别出来,这是一个查询语句。如果我们写了一条语法和词法都没有问题的SQL,但是字段名和表名却不存在,这个错误是在哪一个阶段爆出的呢?查询执行计划展示了接下来执行查询的具体方式,比如多张表关联查询,先查询哪张表,在执行查询的时候有多个索引可以使用,实际上该使用哪些索引。

MySQL是典型的C/S架构(客户端/服务器架构)。 客户端进程向服务器进程发送文本(MySQL指令),服务器进程进行语句处理并返回执行结果。

问题来了。 服务器进程到底对客户端发送的请求做了什么? 本文以查询请求为例,讲解MySQL服务器进程的处理流程。

如下图所示,服务器进程处理客户端请求时,一般需要执行三个步骤:

接下来我们仔细看看这3步都做了什么。

1. 处理连接

客户端向服务器发送请求,最终收到响应,本质上是一个进程间通信的过程。

MySQL 有一个专门处理连接的模块——连接器。

1.1 客户端与服务器端的通信方式 1.1.1 TCP/IP 协议

TCP/IP 协议是 MySQL 客户端和服务器之间最常用的通信方法。

我们通常说MySQL服务器的默认监听端口是3306。这句话的前提是客户端进程和服务器进程使用TCP/IP协议进行通信。

当我们使用mysql命令启动客户端程序时,只要-h参数后跟IP地址作为服务器进程所在的主机地址,那么通信方式就是TCP/IP协议。

如果客户端进程和服务器进程位于同一台主机,并且要使用TCP/IP协议进行通信,则需要将IP地址指定为127.0.0.1,而不是使用

1.1.2 UNIX 域套接字

如果客户端进程和服务器进程都位于类UNIX操作系统(MacOS、、等)的主机上,并且在启动客户端程序时没有指定主机名,或者指定了指定的主机名,或者--=启动参数,那么客户端进程和服务器进程将使用UNIX域套接字进行进程间通信。

MySQL服务器进程默认监听的UNIX域套接字文件是/temp/mysql.sock,客户端进程启动时也会默认连接到这个UNIX域套接字文件。

即使你不明白什么是UNIX域套接字也没关系,只要你知道它是一种进程间的通信方式。 这里提到的主要目的是希望读者知道MySQL客户端和进程的通信方式并不局限于TCP/IP协议

1.1.3 命名管道和共享内存

如果您的 MySQL 安装在主机上,则客户端和服务器进程可以使用命名管道和共享内存进行通信。

但使用这些通信方式需要在服务器和客户端启动时添加一些启动参数。

即使你不明白什么是命名管道和共享内存也没关系,只要你知道这是一种进程间的通信方式。 这里提到的主要目的是希望读者知道MySQL客户端和进程的通信方式并不局限于TCP/IP协议

1.2 权威验证

确认通信方式并成功建立连接后,连接器将开始验证您的身份,使用的信息是您的用户名和密码。

1.3 查看MySQL连接

每当客户端连接到服务器时,服务器进程都会创建一个单独的线程来处理当前的客户端交互。

那么如何查看MySQL当前所有连接呢?

mysql> show global status like 'Thread%';

+-------------------+-------+
| Variable_name     | Value |
+-------------------+-------+
| Threads_cached    | 0     |
| Threads_connected | 1     |
| Threads_created   | 1     |
| Threads_running   | 1     |
+-------------------+-------+

各字段含义如下

字段含义

缓存中的线程连接数

当前打开的连接数

为处理连接而创建的线程数

非睡眠状态连接数,通常指并发连接数

连接建立后,除非客户端主动断开连接,否则服务器会等待客户端发送请求。 但线程的创建和维护需要消耗服务器资源,因此服务器会断开长时间不活动的客户端连接。

有2个参数来控制这种自动断开连接行为,每个参数默认为28800秒,即8小时。

-- 非交互式超时时间,如JDBC连接
mysql> show global variables like 'wait_timeout';
+---------------+-------+
| Variable_name | Value |
+---------------+-------+
| wait_timeout  | 28800 |
+---------------+-------+

-- 交互式超时时间,如数据库查看工具Navicat等
mysql> show global variables like 'interactive_timeout';
+---------------------+-------+
| Variable_name       | Value |
+---------------------+-------+
| interactive_timeout | 28800 |
+---------------------+-------+

由于连接会消耗资源,那么MySQL的最大连接数有默认限制吗? 这是正确的! 默认最大连接数为 151。

mysql> show variables like 'max_connections';
+-----------------+-------+
| Variable_name   | Value |
+-----------------+-------+
| max_connections | 151   |
+-----------------+-------+

题外话:细心的读者可能会发现,有些MySQL查询语句中含有关键字。 这个关键词是什么意思?

MySQL系统变量有两个作用域(不区分大小写),分别是

但并非每个参数都有两个作用域。 例如,允许同时连接到服务器的客户端数量仅是全局级别的。

当没有scope关键字时,默认为level,包括查询和修改操作。

例如修改某个参数后,在当前窗口生效,但在其他窗口不生效。

show VARIABLES like 'autocommit';
set autocommit = on;

因此,如果只是临时修改,请使用level,如果需要当前设置在其他会话中生效,则需要使用关键字。

至此,服务器进程已经与客户端进程建立了连接,接下来就是处理客户端的请求了。

2. 分析与优化

服务器收到客户端的请求后,需要经过查询缓存、词法语法分析和预处理、查询优化。

2.1 查询缓存

如果我们执行同一个查询两次,第二次响应时间会比第一次响应时间短吗?

以前使用过Redis缓存工具的读者应该有这个自然的想法。 MySQL收到查询请求后,首先应该检查缓存,看看之前是否执行过该命令。 如果缓存命中,则直接返回结果; 否则,重新查询该查询,然后添加到缓存中。

MySQL 内部确实带有一个缓存模块。

现在有一个数据表,有500W行,没有添加索引。 如果我执行以下命令两次,第二次会变得更快吗?

SELECT * FROM t_user WHERE user_name = '蝉沐风'

决不! 说明缓存没有生效,为什么呢? MySQL默认关闭自身的缓存功能,检查变量设置。

mysql> show variables like 'query_cache_type';
+------------------------------+---------+
| Variable_name                | Value   |
+------------------------------+---------+
| query_cache_type             | OFF     |
+------------------------------+---------+

默认关闭意味着不推荐。 为什么MySQL不建议用户使用自己的缓存功能?

MySQL内置缓存系统的应用场景非常有限。 它要求SQL语句必须完全相同。 多一个空格和一次大小写更改被视为两条不同的 SQL 语句。

缓存失效的情况非常频繁。 只要一张表的数据有任何修改,该表的所有缓存都会失效。 对于频繁更新的数据表,缓存命中率很低!

因此,缓存功能交给专业的ORM框架(比如默认开启一级缓存)或者独立的缓存服务Redis比较合适。

.0已经完全去掉了缓存功能

2.2 解析器和预处理器 ( & )

现在跳过缓存步骤,接下来需要做什么?

如果我在客户端随机输入一个字符串,服务器返回1064错误

mysql> chanmufeng;
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 'chanmufeng' at line 1

服务器如何判断我的输入是错误的? 这就是MySQL解析器的作用,它主要包括两个步骤,即词法分析和语法分析。

2.2.1 词法分析

以下面的SQL语句为例

SELECT * FROM t_user WHERE user_name = '蝉沐风' AND age > 3;

分析器首先会进行“词法分析”,即将一条完整的SQL语句分解成单个单词。 例如,一个简单的 SQL 语句将被分解为 8 个符号。 每个符号的类型是什么?它从哪里开始? 哪里结束了。

MySQL 从您的输入(即查询语句)中识别出该关键字。 它还将字符串识别为“表名”,将字符串识别为“列”。

2.2.2 语法分析

词法分析完成后,下一步就是进行语法分析。

根据词法分析的结果,语法分析器会根据语法规则判断你输入的SQL语句是否符合MySQL语法,比如单引号是否闭合、关键字拼写是否正确等等。在。

解析器会根据SQL语句生成一个数据结构,我们称之为解析树。

我故意拼错了关键字,MySQL报语法错误,这就是语法分析的步骤。

mysql> ELECT * FROM t_user WHERE user_name = '蝉沐风' AND age > 3;
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_user WHERE user_name = '蝉沐风'' at line 1

词法语法分析是一个非常基本的功能。 Java编译器和百度搜索引擎如果想要识别句子,还必须具备词法语法分析功能。

对于任何数据库中间件来说,要解析SQL完成路由功能,还必须具备词法和语法分析功能,比如Mycat、-JDBC(使用Druid)等。市面上也有很多开源的词法分析工具,如LEX、Yacc等

2.2.3 预处理器

如果我们写了一条语法和词法结构都没有问题的SQL,但是字段名和表名不存在,那么错误是在什么阶段爆发的呢?

词法分析和语法分析无法知道数据库中有哪些表和字段。 了解这些信息需要解析阶段的另一个工具——预处理器。

它检查生成的解析树,解决解析器无法解析的语义。 例如,它检查表名和列名是否存在,并检查名称和别名以确保不存在歧义。 经过预处理后得到一棵新的解析树。

解析和预处理本质上是一个编译过程,涉及词法分析、语法分析和语义分析。 我们不会探讨更多细节。 有兴趣的读者可以阅读有关编译原理的书籍。

2.3 查询优化器()和查询执行计划

至此,MySQL终于知道了我们要查询的表和列以及对应的搜索条件,我们可以直接查询吗?

还没有。 MySQL的作者担心我们写的SQL太垃圾,所以他设计了一个叫做查询优化器的东西来帮助我们提高查询效率。

2.3.1 什么是查询优化器?

执行SQL语句是否只有一种方法? 或者说数据库最终执行的SQL和我们发送的SQL是一样的吗?

不。 一条SQL语句可以有多种执行方式,最后返回的结果都是一样的,它们是等价的。

举一个非常简单的例子,假设您执行以下语句:

SELECT * FROM t1, t2 WHERE t1.id = 10 AND t2.id = 20

这两种执行方式的逻辑结果是一样的,但是执行效率会有所不同。 如果执行方法有这么多,那么这些执行方法是如何获得的呢? 最后选择哪一个呢? 根据什么标准来选择?

这是MySQL查询优化器模块()的工作。

查询优化器的目的是根据解析树生成不同的执行计划(Plan),然后选择一个最优的执行计划。 MySQL 使用基于成本的优化器。 无论哪个执行计划的开销最少,就使用哪个执行计划。

2.3.2 优化器到底做什么的?

举两个简单的例子:

当我们对多个表进行关联查询时,以哪个表的数据作为参考表。

当有多个索引可用时,选择哪个索引。

事实上,对于每一种数据库来说,优化器模块都是必不可少的,它们通过复杂的算法尽可能的优化查询效率。

具体来说,查询优化器主要优化以下几个方面:

本文不会解释优化的细节。 大家了解MySQL的整体架构就足够了。 具体细节将另行介绍。

但优化器也不是万能的。 如果SQL语句太垃圾,再好的优化器也救不了你。 因此,大家在编写SQL语句时应该有意识地进行优化。

2.3.3 执行计划

优化后,你会得到什么? 优化器最终会将解析树转变为查询执行计划。

查询执行计划展示了接下来执行查询的具体方式,比如多表关联查询、先查询哪张表、执行查询时可以使用的多个索引以及实际应该使用哪些索引等。

MySQL提供了查看执行计划的工具。 我们可以通过在SQL语句前面添加执行计划信息来查看。

mysql> EXPLAIN SELECT * FROM t_user WHERE user_name = '';
+----+-------------+--------+------------+------+---------------+------+---------+------+------+----------+-------------+
| id | select_type | table  | partitions | type | possible_keys | key  | key_len | ref  | rows | filtered | Extra       |
+----+-------------+--------+------------+------+---------------+------+---------+------+------+----------+-------------+
|  1 | SIMPLE      | t_user | NULL       | ALL  | NULL          | NULL | NULL    | NULL |    1 |   100.00 | Using where |
+----+-------------+--------+------------+------+---------------+------+---------+------+------+----------+-------------+

如果想获取更详细的信息,还可以使用=JSON,或者开启trace。

mysql> EXPLAIN FORMAT=JSON SELECT * FROM t_user WHERE user_name = '';

文中不会让你详细了解执行计划的各个参数。 内容非常复杂。 可以先了解一下MySQL的整体架构。 具体细节将另行介绍

3. 存储引擎

历经千辛万苦,MySQL终于算出了最终的执行计划,然后就可以直接执行了吗?

好的。 。 。 还是不行。

我们知道表是由一行一行的记录组成的,但这只是一个逻辑概念,或者只是看起来像这样。

3.1 什么是存储引擎

数据应该存储在哪里,内存中还是磁盘上? 如何从表中读取数据以及如何将数据写入特定的表中是存储引擎的职责。

好吧,也许看到这里你还不知道什么是存储引擎。 毕竟存储引擎的名字听起来太神秘了。 它的前身叫桌面处理器,是不是比较接地气?

3.2 为什么需要存储引擎

因为存储要求不同。

想象一下:

大家应该明白为什么支持这么多的存储引擎,因为一种存储引擎不可能提供所有的功能。

存储引擎是计算机抽象的典型代表。 它的作用是接受上层的指令,然后读写表中的数据,而这些操作对上层是完全屏蔽的。 你甚至可以参考MySQL文档来定义自己的存储引擎,只要你外部实现相同的接口即可。

存储引擎只是MySQL读写数据的一个插件,可以根据不同的用途随意更换(插拔)

3.3 如何使用存储引擎 3.3.1 创建表时指定存储引擎

创建表时,可以指定当前表的存储引擎。 如果没有指定,则默认存储引擎。 如果你想明确指定存储引擎,你可以这样做

CREATE TABLE `t_user_innodb` (
  `id` int(11NOT NULL AUTO_INCREMENT,
  PRIMARY KEY (`id`)
ENGINE=innodb DEFAULT CHARSET=utf8mb4;

3.3.2 修改表的存储引擎

ALTER TABLE 表名 ENGINE = 存储引擎名称;

3.4 存储引擎底层差异

接下来我们创建三个不同存储引擎的表,,,

我们来看看不同存储引擎底层存储的差异,首先找到MySQL数据存储目录

mysql> show variables like 'datadir';
+---------------+-----------------+
| Variable_name | Value           |
+---------------+-----------------+
| datadir       | /var/lib/mysql/ |
+---------------+-----------------+

进入目标目录后,找到当前数据库对应的目录(MySQL会为数据库创建同名目录)。 数据库中表的存储结构如下

不同的存储引擎存储数据的方式不同,生成的文件数量和格式也不同。 有2个文件、1个文件、3个文件。

3.5 常见存储引擎对比

首先我们看一下目前MySQL服务器支持的存储引擎。

mysql> SHOW ENGINES;
+--------------------+---------+--------------+------+------------+
| Engine             | Support | Transactions | XA   | Savepoints |
+--------------------+---------+--------------+------+------------+
| InnoDB             | DEFAULT | YES          | YES  | YES        |
| MRG_MYISAM         | YES     | NO           | NO   | NO         |
| MEMORY             | YES     | NO           | NO   | NO         |
| BLACKHOLE          | YES     | NO           | NO   | NO         |
| MyISAM             | YES     | NO           | NO   | NO         |
| CSV                | YES     | NO           | NO   | NO         |
| ARCHIVE            | YES     | NO           | NO   | NO         |
| PERFORMANCE_SCHEMA | YES     | NO           | NO   | NO         |
| FEDERATED          | NO      | NULL         | NULL | NULL       |
+--------------------+---------+--------------+------+------------+

在,

3.5.1

应用范围比较小,表级锁限制了读/写的性能。 因此,在Web和数据仓库配置中,通常用于只读或基于读取的工作。

特征:

如何快速向数据库插入100万条数据?

可以先使用插入数据,然后修改存储引擎为 。

3.5.2

MySQL 5.7 及更高版本中的默认存储引擎。 是一个事务安全(符合 ACID)的 MySQL 存储引擎,具有提交、回滚和崩溃恢复功能来保护用户数据。 行级锁(无需升级为粗粒度锁)和样式一致的非锁定读取可提高多用户并发性。 将用户数据存储在聚集索引中,以减少基于主键的常见查询的I/O。 为了保持数据完整性,还支持外键引用完整性约束。

特征:

补充:最初由该公司开发,与MySQL AB合作开源代码。 但没想到MySQL的竞争对手也收购了它。 后来在2008年,Sun公司(Sun,开发了Java语言)收购了MySQL AB,2009年,Sun公司又被收购,所以MySQL和MySQL又是一个家族了。 有人觉得MySQL越来越相似,其实就是这个原因。

3.5.3

将所有数据存储在 RAM 中以便快速访问。 该引擎以前称为堆引擎。

特征:

3.5.4 CSV

它的表实际上是带有逗号分隔值的文本文件。 csv 表允许导入或转储 CSV 格式的数据,以便与读写相同格式的脚本和应用程序交换数据。 由于 CSV 表没有索引,因此通常在正常操作期间将数据保留在表中,并且仅在导入或导出阶段使用 CSV 表。

特征:

3.5.5

用于存储和检索大量很少引用的信息的专用存档压缩空间。

特征:

3.6 如何选择存储引擎

如果所有存储引擎都不能满足您的需求,并且技术能力足够,您可以根据官网内部手册用C语言开发存储引擎:

 
反对 0举报 0 收藏 0 打赏 0评论 0
 
更多>同类资讯
推荐图文
推荐资讯
点击排行
网站首页  |  关于我们  |  联系方式  |  使用协议  |  版权隐私  |  网站地图  |  排名推广  |  广告服务  |  积分换礼  |  网站留言  |  RSS订阅  |  违规举报
Powered By DESTOON