MySQL 基础架构

MySQL 基础架构 #

一条 SQL 查询语句在 MySQL 内的执行过程,是怎样的?

上图是 MySQL 的基本架构示意图。可以看出,MySQL 大致可以分为两部分:Server 层存储引擎层

Server 层包括连接器、查询缓存、分析器、优化器、执行器等,涵盖 MySQL 的大多数核心服务功能,以及所有的内置函数(如日期、时间、数学和加密函数等),所 有跨存储引擎的功能都在这一层实现,比如存储过程、触发器、视图等。

存储引擎层负责数据的存储和提取。支持 InnoDB、MyISAM、Memory 等多个存储引擎。不同的存储引擎共用一个 Server 层。存储引擎向 Server 层提供统一 的调用接口(存储引擎 API),包含了几十个底层函数,像"读取索引第一条内容”、“读取索引下一条内容”、“插入记录"等等。

从 MySQL 5.5.5 版本开始, InnoDB 成为了默认存储引擎。

接下来看一条 SQL 查询语句的执行过程,如 select * from T where ID=10;

连接管理 #

第一步就是与服务端建立连接。连接器负责跟客户端建立连接、获取权限、维持和管理连接。

客户端可以采用 TCP/IP、命名管道或共享内存、Unix 域套接字这几种方式之一来与服务端建立连接。

连接命令:

mysql -h$ip -P$port -u$user -p

注意,使用 -p 后面尽量不要跟着密码。-p 和密码值之间不能有空白字符(其他参数名之间可以有空白字符)。 如果服务端和客户端安装在同一台机器上,-h 参数可以省略。

建立 TCP 连接后,连接器会进行身份认证,并获取权限。之后,这个连接里面的权限判断逻辑,都将依赖于此时读到的权限。意味着,一个用户成功建立 连接后,即使用管理员账号对这个用户的权限做了修改,也不会影响已经存在连接的权限。只有再新建的连接才会使用新的权限设置。

每当有一个客户端进程连接到服务器进程时,服务器进程都会创建一个线程来专门处理与这个客户端的交互,当该客户端退出时会与服务器断开连接,服务器并不会 立即把与该客户端交互的线程销毁掉,而是把它缓存起来,在另一个新的客户端再进行连接时,把这个缓存的线程分配给该新客户端。这样就起到了不频繁创建和销 毁线程的效果。

MySQL 服务器会为每一个连接进来的客户端分配一个线程,但是线程分配的太多了会严重影响系统性能,所以也需要限制一下可以同时连接到服务器的客户端数量。

连接完成后,如果没有后续的动作,这个连接就处于空闲状态,可以使用 show processlist 命令查看。Command 列显示为 “Sleep” 的,就表示是有一 个空闲连接。

客户端如果太长时间没动静,连接器就会自动将它断开。这个时间是由参数 wait_timeout 控制的,默认值是 8 小时。

如果在连接被断开之后,客户端再次发送请求的话,就会收到一个错误提醒:“Lost connection to MySQL server during query”。这时候如果要继续,就 需要重连,然后再执行请求了。

数据库里面,长连接是指连接成功后,如果客户端持续有请求,则一直使用同一个连接。短连接则是指每次执行完很少的几次查询就断开连接,下次查询再重新建立一个。

建立连接的过程通常是比较复杂的,所以建议在使用中要尽量减少建立连接的动作,也就是尽量使用长连接。

但是全部使用长连接后,可能会发现,有些时候 MySQL 占用内存涨得特别快,这是因为 MySQL 在执行过程中临时使用的内存是管理在连接对象里面的。这些资源 会在连接断开的时候才释放。所以如果长连接累积下来,可能导致内存占用太大,被系统强行杀掉(OOM),从现象看就是 MySQL 异常重启了。

怎么解决这个问题?可以考虑以下两种方案。

  • 定期断开长连接。使用一段时间,或者程序里面判断执行过一个占用内存的大查询后,断开连接,之后要查询再重连。
  • 如果是 MySQL 5.7 或更新版本,可以在每次执行一个比较大的操作后,通过执行 mysql_reset_connection 来重新初始化连接资源。这个过程不 需要重连和重新做权限验证,但是会将连接恢复到刚刚创建完时的状态。

查询缓存 #

MySQL 处理查询请求时,会把刚刚处理过的查询请求和结果以 key-value 的形式,缓存在内存中。如果下一次有一模一样的请求过来,优先从缓存中查找结果。 如果命中缓存,就不需要再执行后面的复杂操作,直接返回结果。

查询缓存可以在不同客户端之间共享。

查询缓存的弊端 #

如果两个查询请求在任何字符上的不同(例如:空格、注释、大小写),都会导致缓存不会命中。

如果查询请求中包含某些系统函数、用户自定义变量和函数、一些系统表,如 mysql 、information_schema、 performance_schema 数据库中的表,那这 个请求就不会被缓存。

查询缓存的失效非常频繁。MySQL 的缓存系统会监测涉及到的每张表,只要该表的结构或者数据被修改,如对该表使用了 INSERT、 UPDATE、DELETE、 TRUNCATE TABLE、ALTER TABLE、DROP TABLE 或 DROP DATABASE 语句,那这个表上所有的查询缓存都会被清空。对于更新压力大的数据库来说,查 询缓存的命中率会非常低。除非你的业务表,很长时间才会更新一次。

按需使用查询缓存 #

MySQL 提供了 query_cache_type 参数,当设置成 DEMAND 时,默认 SQL 语句不会使用查询缓存。而对于确定要使用查询缓存的语句,可以 用 SQL_CACHE 显式指定:

select SQL_CACHE * from T where ID=10;

MySQL 在 8.0 中删除了查询缓存的功能。

语法解析 #

如果查询缓存没有命中,接下来就需要真正执行查询语句了。因为客户端程序发送过来的请求只是一段文本而已,MySQL 首先要对这段文本做分析。

分析器先会做词法分析。MySQL 需要识别出文本里面的字符串分别是什么,代表什么。比如从 “select” 这个关键字,可以判断出是一个查询语句。 把字符串 “T” 识别成 “表名 T”,把字符串 “ID” 识别成 “列 ID”。

接下来要做语法分析,根据词法分析的结果,分析器根据语法规则,判断这个 SQL 语句是否满足 MySQL 语法。如果语句不对,就会收 到 “You have an error in your SQL syntax” 的错误提醒。

查询优化 #

语法解析之后,MySQL 知道了要查询的列是哪些,表是哪个,搜索条件是什么等等。在开始执行之前,还要先经过优化器的处理。因为我们写的 SQL 语句执行起来 效率可能并不是很高,优化器会对语句做一些优化,如外连接转换为内连接、表达式简化、子查询转为连接、选择索引等等。

优化器会生成一个执行计划,这个执行计划表明了应该使用哪些索引进行查询,表之间的连接顺序是啥样的。可以使用 EXPLAIN 语句来查看某个语句的执行计划。

执行器 #

MySQL 通过分析器知道了你要做什么,通过优化器知道了该怎么做,接下来就进入了执行器阶段,开始执行语句。

开始执行的时候,要先判断一下你对这个表 T 有没有执行查询的权限,如果没有,就会返回没有权限的错误。

打开表的时候,执行器就会根据表的引擎定义,去使用这个引擎提供的接口。

比如例子中的表 T,ID 字段没有索引,执行器的执行流程:

  1. 调用 InnoDB 引擎接口取这个表的第一行,判断 ID 值是不是 10,如果不是则跳过,如果是则将这行存在结果集中;
  2. 调用引擎接口取“下一行”,重复相同的判断逻辑,直到取到这个表的最后一行。
  3. 执行器将结果集返回给客户端。

至此,这个语句就执行完成了。对于有索引的表,执行的逻辑也差不多。

数据库的慢查询日志中有一个 rows_examined 的字段,表示这个语句执行过程中扫描了多少行。这个值就是在执行器每次调用引擎获取数据行的时候累加的。

在有些场景下,执行器调用一次,在引擎内部则扫描了多行,因此引擎扫描行数跟 rows_examined 并不是完全相同的

存储引擎 #

数据库表是由一行一行的记录组成的,但这只是一个逻辑上的概念,物理上如何表示记录,怎么从表中读取数据,怎么把数据写入具体的物理存储器上,这都是存 储引擎负责的事情。为了实现不同的功能,MySQL 提供了各式各样的存储引擎,不同存储引擎管理的表具体的存储结构可能不同,采用的存取算法也可能不同。

常用存储引擎 #

存储引擎 描述
InnoDB 具备外键支持功能的事务存储引擎
MEMORY 置于内存的表
MyISAM 主要的非事务处理存储引擎

最常用的就是 InnoDB

查看当前服务器程序支持的存储引擎:

SHOW ENGINES;

mysql> SHOW ENGINES;
+--------------------+---------+----------------------------------------------------------------+--------------+------+------------+
| Engine             | Support | Comment                                                        | Transactions | XA   | Savepoints |
+--------------------+---------+----------------------------------------------------------------+--------------+------+------------+
| InnoDB             | DEFAULT | Supports transactions, row-level locking, and foreign keys     | YES          | YES  | YES        |
| MRG_MYISAM         | YES     | Collection of identical MyISAM tables                          | NO           | NO   | NO         |
| MEMORY             | YES     | Hash based, stored in memory, useful for temporary tables      | NO           | NO   | NO         |
| BLACKHOLE          | YES     | /dev/null storage engine (anything you write to it disappears) | NO           | NO   | NO         |
| MyISAM             | YES     | MyISAM storage engine                                          | NO           | NO   | NO         |
| CSV                | YES     | CSV storage engine                                             | NO           | NO   | NO         |
| ARCHIVE            | YES     | Archive storage engine                                         | NO           | NO   | NO         |
| PERFORMANCE_SCHEMA | YES     | Performance Schema                                             | NO           | NO   | NO         |
| FEDERATED          | NO      | Federated MySQL storage engine                                 | NULL         | NULL | NULL       |
+--------------------+---------+----------------------------------------------------------------+--------------+------+------------+
9 rows in set (0.00 sec)

mysql>
  • Support 表示该存储引擎是否可用,如果值为 DEFAULT 则表示是默认的存储引擎。
  • Transactions 表示该存储引擎是否支持事务处理。
  • XA 表示着该存储引擎是否支持分布式事务。
  • Savepoints 表示着该列是否支持部分事务回滚。