MySQL 是典型的 C/S 架构,即 Client/Server 架构,服务器步调利用的是 mysqld。
岂论客户端进程和服务器进程是采用哪种方式进行通讯,末了实现的效果都是:客户端进程向服务器进程发送一段文本(SQL)语句,服务器进程处置惩罚后再向客户端进程发送一段文本(处置惩罚结果)。
那服务器进程对客户端进程发送的请求做了什么处置惩罚,才能产生末了的处置惩罚结果呢?这里以查询请求为例展示:
具体流程(针对于 MySQL-5.7):
在 MySQL-8.0 版本中 缓存 被移除了 5.1.2 客户端连接器
客户端连接器(Connectors)指的是不同语言中与 SQL 的交互,MySQL 起首是一个网络步调,在 TCP 之上界说了本身的应用层协议,所以要利用 MySQL,可以编写代码跟 MySQL 创建 TCP 连接,之后按照其界说好的协议进行交互或者比力方便的办法是调用 SDK,比如 Native C API、JDBC、PHP 等各个语言 MySQL Connector,或者通过 ODBC,但通过 SDK 来访问 MySQL 本质上照旧在 TCP 连接上通过 MySQL 协议跟 MySQL 进行交互。 5.1.3 MySQL Server 的三层架构
简化为三层布局:
① 连接层:客户端和服务器端创建连接,客户端发送 SQL 至服务器端
② SQL 层(服务层):对 SQL 语句进行查询处置惩罚;与数据库文件的存储方式无关
③ 存储引擎层:与数据库文件打交道,负责数据的存储和读取
5.1.3.1 第一层:连接层
系统(客户端)访问 MySQL 服务器前做的第一件事就是创建 TCP 连接
经过三次握手创建连接乐成后,MySQL 服务器对 TCP 传输过来的账号暗码做身份认证、权限获取
用户名或暗码不对,会收到一个 Access denied for user 错误,客户端步调执行结束
它利用 选取-投影-连接 策略进行查询,例如:SELECT id,name FROM student WHERE gender = '女';,这个 SELECT 查询会根据 WHERE 语句进行 选取,而不是将表全部查询出来以后再进行 gender 过滤,根据 id 和 name 进行属性 投影,而不是将属性全部取出以后再进行过滤,将这两个查询条件 连接 起来生成终极查询布局
Cache & Buffers:查询缓存组件
MySQL 内部维持着一些 Cache 和 Buffer,比如 Query Cache 用来缓存一条 SELECT 语句的执行结果,如果可以或许在其中找到对应的查询结果,那么就不必再进行查询剖析、优化和执行的整个过程了,直接将结果反馈给客户端
这个缓存机制是由一系列小缓存构成的,比如表缓存、记录缓存、key 缓存、权限缓存等
这个查询缓存可以在 不同客户端之间共享
从 MySQL-5.7.20 开始,不推荐利用查询缓存,并在 MySQL-8.0 中删除
5.1.3.3 第三层:存储引擎层
和其它数据库相比,MySQL 有点与众不同,它的架构可以在多种不同场景中应用并发挥良好作用,紧张体如今存储引擎的架构上,插件式的存储引擎 架构将查询处置惩罚和其它的系统使命以及数据的存储提取相分离,这种架构可以根据业务的需求和现实必要选择合适的存储引擎。同时开源的 MySQL 还允许 开发职员设置本身的存储引擎。
这种高效的模块化架构为那些希望专门针对特定应用步调需求(例如数据仓库、变乱处置惩罚或可用性情况)的人提供了巨大的好处,同时享受利用一组独立于任何接口和服务的优势存储引擎。
插件式存储引擎层(Storage Engines)真正的负责了 MySQL 中数据的存储和提取,对物理服务器级别维护的底层数据执行操作,服务器通过 API 与存储引擎进行通讯。不同的存储引擎具有的功能不同,这样可以根据本身的现实必要进行选取。
可以用以下命令检察 MySQL 所支持的存储引擎
SHOW ENGINES;
复制代码
MySQL 8.0 默认支持的存储引擎如下:
mysql> SHOW ENGINES;
+--------------------+---------+----------------------------------------------------------------+--------------+------+------------+| Engine | Support | Comment | Transactions | XA | Savepoints |+--------------------+---------+----------------------------------------------------------------+--------------+------+------------+| FEDERATED | NO | Federated MySQL storage engine | NULL | NULL | NULL || MEMORY | YES | Hash based, stored in memory, useful for temporary tables | NO | NO | NO || InnoDB | DEFAULT | Supports transactions, row-level locking, and foreign keys | YES | YES | YES || PERFORMANCE_SCHEMA | YES | Performance Schema | NO | NO | NO || MyISAM | YES | MyISAM storage engine | NO | NO | NO || MRG_MYISAM | YES | Collection of identical MyISAM tables | NO | NO | NO || BLACKHOLE | YES | /dev/null storage engine (anything you write to it disappears) | NO | NO | NO || CSV | YES | CSV storage engine | NO | NO | NO || ARCHIVE | YES | Archive storage engine | NO | NO | NO |+--------------------+---------+----------------------------------------------------------------+--------------+------+------------+9 rows in set (0.00 sec)
1. 查询缓存:Server如果在查询缓存中发现了这条 SQL 语句,就会直接将结果返回给客户端;如果没有,就进入到剖析器阶段。必要分析的是,由于查询缓存往往服从不高,所以在 MySQL 8.0 之后就扬弃了这个功能。
MySQL 拿到一个查询请求后,会先到查询缓存看看,之前是不是执行过这条语句,之前执行过的语句及其结果可能会以 key-value 键值对的情势被直接缓存在内存中,key 是查询语句,value 是查询的结果。如果你的查询可以或许直接在这个缓存中找到 key,那么这个 value 就会被直接返回给客户端。如果语句不在查询缓存中,就会继续后面的执行阶段。执行完成后,执行结果会被存入查询缓存中,所以,如果查询命中缓存,MySQL 不必要执行后面的复杂操作,就可以直接返回结果,这个服从会很高。
但是在大多数情况下查询缓存就是个鸡肋,由于查询缓存是提前把查询结果缓存起来,这样下次不必要执行就可以直接拿到结果,必要分析的是,在 MySQL 中的查询缓存,不说缓存查询计划,而是查询对应的结果。这就意味着查询匹配的 鲁棒性大大低落,只有 相同的查询操作才会命中查询缓存。两个查询请求在任何字符上的不同(例如:空格、注释、大小写等),都会导致缓存不会命中,因此 MySQL 查询缓存命中率不高。
比如:
如果没有命中查询缓存,就要开始真正执行语句了,起首,MySQL 必要知道你要做什么,因此必要对 SQL 语句进行剖析,SQL 语句的分析分为 词法分析 和 语法分析。
分析器先做 词法分析,你输入的是由多个字符串和空格构成的一条 SQL 语句,MySQL 必要识别出里面的字符串分别是什么,代表什么。
MySQL 从你输入的 select 这个关键字就能判断这是一个查询语句,并把所必要查询的表、列都识别出来
接着要做 语法分析,根据词法分析的结果,语法分析器(比如:Bison)会根据语法规则,判断你输入的这个 SQL 语句是否 满足 MySQL 语法。
如果你的语句不对,就会提示 You have an error in your SQL syntax 的错误提示,比如下面这个语句将 from 写成 fro
mysql> select * fro t_user where id = 1;
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 'fro t_user where id = 1' at line 1
再重启 MySQL 服务器
这里我 mike_inner 库下有一个 t_access_log 的表,我对于该表进行两次查询操作
示例:
mysql> use mike_inner;Reading table information for completion of table and column namesYou can turn off this feature to get a quicker startup with -ADatabase changedmysql> SET profiling = 1;
Query OK, 0 rows affected, 1 warning (0.00 sec)
mysql> select * from t_access_log;+----+------------+-------------+-----------------+---------------------+-------+| id | login_name | access_path | access_ip | create_time | state |+----+------------+-------------+-----------------+---------------------+-------+| 1 | duojiala | /auth/login | 192.168.110.193 | 2024-04-18 09:15:28 | 0 || 2 | caodali | /auth/login | 192.168.110.193 | 2024-04-18 09:16:27 | 1 |+----+------------+-------------+-----------------+---------------------+-------+2 rows in set (0.00 sec)mysql> select * from t_access_log;+----+------------+-------------+-----------------+---------------------+-------+| id | login_name | access_path | access_ip | create_time | state |+----+------------+-------------+-----------------+---------------------+-------+| 1 | duojiala | /auth/login | 192.168.110.193 | 2024-04-18 09:15:28 | 0 || 2 | caodali | /auth/login | 192.168.110.193 | 2024-04-18 09:16:27 | 1 |+----+------------+-------------+-----------------+---------------------+-------+2 rows in set (0.00 sec)mysql> show profiles;
+----------+------------+----------------------------+| Query_ID | Duration | Query |+----------+------------+----------------------------+| 1 | 0.00030500 | select * from t_access_log || 2 | 0.00027300 | select * from t_access_log |+----------+------------+----------------------------+2 rows in set, 1 warning (0.00 sec)mysql> show profile for query 1;+--------------------------------+----------+| Status | Duration |+--------------------------------+----------+| starting | 0.000064 || Waiting for query cache lock | 0.000006 || starting | 0.000008 || Checking query cahce for query | 0.000051 || checking permissions | 0.000006 || Opening tables | 0.000031 || init | 0.000005 || System lock | 0.000010 || Waiting for query cache lock | 0.000006 || System lock | 0.000010 || optimizing | 0.000004 || statistics | 0.000014 || preparing | 0.000015 || executing | 0.000070 || Sending data | 0.000070 || end | 0.000016 || query end | 0.000004 || closing tables | 0.000004 || freeing items | 0.000004 || Waiting for query cache lock | 0.000006 || freeing items | 0.000004 || Waiting for query cache lock | 0.000006 || freeing items | 0.000004 || storing result in query cache | 0.000020 || cleaning up | 0.000020 |+--------------------------------+----------+25 rows in set, 1 warning (0.00 sec)mysql> show profile for query 2;+--------------------------------+----------+| Status | Duration |+--------------------------------+----------+| starting | 0.000066 || Waiting for query cache lock | 0.000006 || starting | 0.000008 || Checking query cahce for query | 0.000051 || Checking privileges on cached | 0.000051 || checking permissions | 0.000006 || sending cached result to clien | 0.000006 || cleaning up | 0.000010 |+--------------------------------+----------+17 rows in set, 1 warning (0.00 sec)
mysql> use mike_inner;Reading table information for completion of table and column namesYou can turn off this feature to get a quicker startup with -ADatabase changedmysql> SET profiling = 1;
Query OK, 0 rows affected, 1 warning (0.00 sec)
mysql> select * from t_access_log;+----+------------+-------------+-----------------+---------------------+-------+| id | login_name | access_path | access_ip | create_time | state |+----+------------+-------------+-----------------+---------------------+-------+| 1 | duojiala | /auth/login | 192.168.110.193 | 2024-04-18 09:15:28 | 0 || 2 | caodali | /auth/login | 192.168.110.193 | 2024-04-18 09:16:27 | 1 |+----+------------+-------------+-----------------+---------------------+-------+2 rows in set (0.00 sec)mysql> select * from t_access_log;+----+------------+-------------+-----------------+---------------------+-------+| id | login_name | access_path | access_ip | create_time | state |+----+------------+-------------+-----------------+---------------------+-------+| 1 | duojiala | /auth/login | 192.168.110.193 | 2024-04-18 09:15:28 | 0 || 2 | caodali | /auth/login | 192.168.110.193 | 2024-04-18 09:16:27 | 1 |+----+------------+-------------+-----------------+---------------------+-------+2 rows in set (0.00 sec)mysql> show profiles;
+----------+------------+----------------------------+| Query_ID | Duration | Query |+----------+------------+----------------------------+| 1 | 0.00030500 | select * from t_access_log || 2 | 0.00027300 | select * from t_access_log |+----------+------------+----------------------------+2 rows in set, 1 warning (0.00 sec)mysql> show profile for query 1;
+--------------------------------+----------+
| Status | Duration |
+--------------------------------+----------+
| starting | 0.000064 |
| Executing hook on transaction | 0.000006 |
| starting | 0.000008 |
| checking permissions | 0.000006 |
| Opening tables | 0.000031 |
| init | 0.000005 |
| System lock | 0.000010 |
| optimizing | 0.000004 |
| statistics | 0.000014 |
| preparing | 0.000015 |
| executing | 0.000070 |
| end | 0.000016 |
| query end | 0.000004 |
| waiting for handler commit | 0.000008 |
| closing tables | 0.000007 |
| freeing items | 0.000020 |
| cleaning up | 0.000020 |
+--------------------------------+----------+
17 rows in set, 1 warning (0.00 sec)
mysql> show profile for query 2;+--------------------------------+----------+| Status | Duration |+--------------------------------+----------+| starting | 0.000066 || Executing hook on transaction | 0.000004 || starting | 0.000008 || checking permissions | 0.000006 || Opening tables | 0.000032 || init | 0.000006 || System lock | 0.000009 || optimizing | 0.000005 || statistics | 0.000013 || preparing | 0.000015 || executing | 0.000068 || end | 0.000003 || query end | 0.000003 || waiting for handler commit | 0.000007 || closing tables | 0.000007 || freeing items | 0.000012 || cleaning up | 0.000010 |+--------------------------------+----------+17 rows in set, 1 warning (0.00 sec)
如果你利用的是 MySQL MyISAM 存储引擎,它只缓存索引,不缓存数据,对应的健缓存参数为 key_buffer_size,可以用它进行检察。
如果你利用的是 InnoDB 存储引擎,可以通过检察 innodb_buffer_pool_size 变量来检察缓冲池的大小,命令如下:
show variables like 'innodb_buffer_pool_size';
复制代码
示例:
mysql> show variables like 'innodb_buffer_pool_size';
+-------------------------+-----------+| Variable_name | Value |+-------------------------+-----------+| innodb_buffer_pool_size | 134217728 |+-------------------------+-----------+1 row in set (0.00 sec)
Buffer Pool 本质上 InnoDB 向操作系统申请的一块 连续的内存空间,在多线程情况下,访问 Buffer Pool 中的数据都必要 加锁 处置惩罚。在 Buffer Pool 特别大而且多线程并发访问特别高的情况下,单一的 Buffer Pool 可能会影响请求的处置惩罚速率。所以在 Buffer Pool 特别大的时间,我们可以把它们 拆分成多少个小的 Buffer Pool,每个 Buffer Pool 都称为一个 实例,它们都是独立的,独立的去申请内存空间,独立的管理各种链表。所以在多线程并发访问时并不会相互影响,从而进步并发处置惩罚能力。
我们可以在服务器启动的时间通过设置 innodb_buffer_pool_instances 的值来修改 Buffer Pool 实例的个数,比如这样:
[mysqld]
innodb_buffer_pool_instances = 2
复制代码
这样就表明我们要创建 2 个 Buffer Pool 实例
我们看下怎样检察缓冲池的个数,利用命令:
show variables like 'innodb_buffer_pool_instances';
复制代码
示例:
mysql> show variables like 'innodb_buffer_pool_instances';
+------------------------------+-------+| Variable_name | Value |+------------------------------+-------+| innodb_buffer_pool_instances | 1 |+------------------------------+-------+1 row in set (0.01 sec)
也就是总共的大小除以实例的个数,结果就是每个 Buffer Pool 实例占用的大小。
不外也不是说 Buffer Pool 实例创建得越多越好,分别 管理各个 Buffer Pool 也是必要性能开销 的,InnoDB 规定:当 innodb_buffer_pool_size 的值小于 IG 的时间设置多个实例是无效的,InnoDB 会默认把 innodb_buffer_pool_instances 的值修改为 1,而我们鼓励在 Buffer Pool 大于或等于 IG 的时间设置多个 Buffer Pool 实例。 5.3.5 引申问题
Buffer Pool 是 MySQL 内存布局中是否核心的一个构成,你可以把它想象成一个黑盒子。 黑河下的更新数据流程
当我们查询数据的时间,会先去 Buffer Pool 中查询。如果 Buffer Pool 中不存在,存储引擎会先将数据从磁盘加载到 Buffer Pool 中,然后将数据返回给客户端;同理,当我们更新某个数据的时间,如果这个数据不存在于 Buffer Pool,同样会先数据加载进来,然后修改修改内存的数据。被修改过的数据会在之后统一刷入磁盘。
这个过程看似没有啥问题,实则是有问题的。假设我们修改 Buffer Pool 中的数据乐成,但是还没来得及将数据刷入磁盘 MySQL 就挂了怎么办?按照上图的逻辑,此时更新之后的数据只存在于 Buffer Pool 中,如果此时 MySQL 宕机了,这部门数据将会永世地丢失。
再者,我更新到一半突然发生错误了,想要回滚到更新之前的版本,该怎么办?连数据恒久化的包管、变乱回滚都做不到还谈什么崩溃规复? 上篇:第四章、配置文件与系统变量
下篇:第六章、存储引擎