首先数据库分为两个部分:存储引擎和服务器层。

服务器层

服务器层的主要功能包括:连接管理、安全认证、权限判断、查询解析、分析优化、缓存、内置函数、存储过程、触发器等。

连接管理

服务器与Mysql的连接是通过TCP/IP协议进行的。Mysql的默认端口是3306。
已经连接的客户端会在SHOW PROCESSLIST中显示。
并且已经连接的客户端不会因为被修改了权限而断开连接。
当Mysql的连接数达到最大值时,新的连接会被拒绝。是由参数max_connections控制的。默认的最大连接数是151。最大可以设置为100000。
当连接处于闲置状态时,Mysql会自动关闭连接。是由参数wait_timeout控制的。默认的等待时间是8小时。最大可以设置为31536000秒。
为什么是31536000秒呢?因为这个时间是一年的秒数。(手动滑稽🤪)
Mysql的连接数是由操作系统的文件描述符限制的。所以在linux系统中,可以通过ulimit -n
查看文件描述符的限制。可以通过ulimit -n 100000设置文件描述符的最大值。
mysql的连接也存在长链接和短链接的问题。长链接会占用更多的资源,但是短链接会频繁的建立和断开连接,会消耗更多的资源。所以需要根据实际情况来选择。

解析、分析和优化

在执行sql语句之前,Mysql会先解析sql语句,然后进行分析和优化。

词法分析

比如说:

1
SELECT * FROM user WHERE id = 1;

Mysql会将这条sql语句分解成:SELECT*FROMuserWHEREid=1
其中关键字是不区分大小写的。
这一条执行语句中的关键字是:SELECTFROMWHERE

语法分析

在词法分析阶段,将sql语句分解成一个个的token。然后在语法分析阶段,将token组合成一个个的语法树。
如果sql语句不符合Mysql的语法规则,那么Mysql会报错。
报错语句如下

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 'from user where id = 1' at line 1

执行计划

经过语法分析之后,Mysql会生成一个执行计划。执行计划是Mysql用来执行sql语句的一个计划。
分为3个部分:预处理器、优化器和执行器。

预处理器

在预处理阶段,Mysql会对sql语句进行一些预处理。比如:将*转换成具体的字段名。
比如:SELECT * FROM user WHERE id = 1;会被转换成SELECT id, name, age FROM user WHERE id = 1;
并且会判断表是否存在,字段是否存在等。
报错语句如下

1
ERROR 1146 (42S02): Table 'test.user' doesn't exist

优化器

优化器的主要工作是对执行计划进行优化。比如:选择合适的索引、选择合适的连接方式等。
比如:SELECT * FROM user WHERE id = 1;选择的索引就比较简单了,使用的是id字段的索引。(即主键索引)
如果想知道Mysql是如何选择索引的,可以使用explain命令。
比如说

1
explain SELECT * FROM user WHERE id = 1;

会返回如下结果

1
2
3
4
5
+----+-------------+-------+------------+-------+---------------+---------+---------+------+------+----------+-------------+
| id | select_type | table | partitions | type | possible_keys | key | key_len | ref | rows | filtered | Extra |
+----+-------------+-------+------------+-------+---------------+---------+---------+------+------+----------+-------------+
| 1 | SIMPLE | user | NULL | const | PRIMARY | PRIMARY | 4 | const| 1 | 100.00 | Using index |
+----+-------------+-------+------------+-------+---------------+---------+---------+------+------+----------+-------------+

如果稍微复杂一些,比如:SELECT * FROM user WHERE id > 50 and name like '%ling%';
优化器就会根据执行效率的不同,来选择最优的索引。

执行器

预处理、优化都完成了之后,就轮到执行器了。执行器的主要工作是执行sql语句。

  • 主键索引查询
1
SELECT * FROM user WHERE id = 1;

这条sql语句会使用主键索引查询。因为id是主键,并且是唯一的。不会有相同的记录存在。
所以优化器会使用 const (恒量)类型的查询。并且会使用 PRIMARY 索引。
执行器第一次查询,会调用read_first_record函数。
把条件 id = 1 交给存储引擎,去定位到符合条件的第一条记录。
如果找不到记录,则查询结束。
如果记录存在,则会把记录返回给执行器,并且判断是否符合查询条件。如果不符合就跳过该记录。
执行器的查询过程是一个 while 循环,所以还会查询一次,但是调用的函数不是read_first_record
调用的是read_record,因为优化器选择的访问类型是 const (恒量)。函数指针会指向 -1 然后执行器退出循环,结束查询。

  • 全表扫描
1
SELECT * FROM user WHERE name = 'Tom';

因为 name 没有用到索引,所以优化器会使用 ALL 来进行访问类型。
因为使用的 ALL 所以存储引擎会从表的第一条记录开始读取。
执行器会判断读取到的记录是否匹配查询条件,如果不匹配,则跳过,如果匹配则返回给客户端。
存储引擎每获取到一条匹配的记录都会返回给服务器层并发送给客户端,之所以我们看到的是一个列表。
是因为客户端是等待查询语句全部完成之后才显示。

  • 索引下推
    索引下推的目的是为了减少二级索引在查询时的回表操作,提高查询效率,因为它将服务器层部分负责的事儿,交给了存储引擎去处理了。

    回表操作:
    使用聚集索引(主键或第一个唯一索引)就不会回表,普通索引就会回表。
    原因是通过 k 这个普通索引查询方式,则需要先搜索 k 索引树,然后得到主键 ID 的值为 1,再到 ID 索引树搜索一次。这个过程虽然用了索引,但实际上底层进行了两次索引查询,这个过程就称为回表。
    tips: 减少 SELECT * 的使用,因为会导致回表操作。

举例说明

1
SELECT * FROM user WHERE age > 50 and name like '%ling%';

这条sql语句会使用 age 索引和 name 索引。
我们将 agename 建立联合索引。
但是当使用到范围查询时,name 字段就不会使用索引了。
如果没有用到索引下推,那当匹配到第一条age > 50的记录时,会回表查询。
然后将完整的记录返回给服务器层,然后服务器层再判断name是否符合条件。
可以发现,如果没有索引下推,那么会有很多的回表操作。
但是使用了索引下推,当定位到记录之后,会先不执行回表操作,而是先判断name是否符合条件。
如果条件不成立,那么就不会回表,直接跳过该记录。如果成立,那么再回表。
可以看出,索引下推可以减少回表操作,提高查询效率。
当你发现执行计划中的 Extra 字段中有 Using index condition 时,就说明使用了索引下推。

存储引擎

存储引擎负责数据的存储和提取。常见的存储引擎有:InnoDB、MyISAM、Memory、CSV、Archive等。
Mysql的存储引擎是插件式的,可以根据需求选择不同的存储引擎。
比如:InnoDB支持事务,MyISAM不支持事务。
然后Mysql默认的存储引擎是InnoDB。从版本5.5开始。
InnoDB的索引是聚集索引,MyISAM的索引是非聚集索引。
InnoDB支持索引类型是B+树。