MySQL连接查询解析与性能优化成本

发布于:2024-10-13 ⋅ 阅读:(51) ⋅ 点赞:(0)

一、连接查询

1.连接查询基础

MySQL中的连接查询(JOIN)是用于从两个或多个表中检索数据的一种方法。当需要组合来自不同表的信息时,通常会使用连接查询。MySQL支持多种类型的JOIN,包括INNER JOIN、LEFT JOIN、RIGHT JOIN、FULL OUTER JOIN等。

1. INNER JOIN内连接

INNER JOIN是最常用的连接类型,它返回两个表中满足连接条件的所有行。如果某一行在另一个表中没有匹配,则不会出现在结果集中。

示例:
假设我们有两个表,一个是employees(员工表),另一个是departments(部门表)。我们想要找出所有员工及其对应的部门名称。

SELECT employees.name, departments.department_name
FROM employees
INNER JOIN departments 
ON employees.department_id = departments.id;

2. LEFT JOIN (或 LEFT OUTER JOIN)左外连接

LEFT JOIN返回左表(FROM子句中提到的第一个表)中的所有记录,即使右表中没有匹配的记录。如果右表中没有匹配的记录,那么结果集中的对应列将包含NULL值。

示例:
如果我们想列出所有的员工以及他们所在的部门,即使有些员工还没有分配到任何部门,也可以使用LEFT JOIN。

SELECT employees.name, departments.department_name
FROM employees
LEFT JOIN departments 
ON employees.department_id = departments.id;

3. RIGHT JOIN (或 RIGHT OUTER JOIN)右外连接

RIGHT JOINLEFT JOIN相反,它返回右表中的所有记录,即使左表中没有匹配的记录。如果左表中没有匹配的记录,那么结果集中的对应列将包含NULL值。

示例:
如果我们想列出所有部门及其中的员工,即使有些部门目前没有员工,可以使用RIGHT JOIN。

SELECT employees.name, departments.department_name
FROM employees
RIGHT JOIN departments 
ON employees.department_id = departments.id;

4. FULL OUTER JOIN

FULL OUTER JOIN返回左表和右表中的所有记录。当某一行在其中一个表中没有匹配时,结果集中的对应列将包含NULL值。但是需要注意的是,MySQL本身并不直接支持FULL OUTER JOIN,可以通过UNION操作来模拟实现。

示例:
为了得到一个包含所有员工和部门的列表,无论是否有对应的匹配项,可以这样做:

SELECT employees.name, departments.department_name
FROM employees
LEFT JOIN departments 
ON employees.department_id = departments.id
UNION
SELECT employees.name, departments.department_name
FROM employees
RIGHT JOIN departments 
ON employees.department_id = departments.id;
  • 在使用JOIN时,确保连接条件是正确的,以避免产生过多的笛卡尔积(即每个表的每一行都与其他表的每一行配对)。
  • 当处理大型数据集时,考虑使用索引来提高性能。
  • 对于复杂的查询,可能需要使用子查询、视图或其他高级技术来优化性能和可读性。

2.连接查询的两种过滤条件

  • WHERE子句中的过滤条件:

WHERE子句中的过滤条件就是我们平时见的那种,不论是内连接还是外连接,凡是不符合WHERE子句中的过滤条件的记录都不会被加入最后的结果集。

  • ON子句中的过滤条件:

对于外连接的驱动表(即,left join中左边的表,或right join中右边的表)的记录来说,如果无法在被驱动表中找到匹配ON子句中的过滤条件的记录,那么该记录仍然会被加入到结果集中,对应的被驱动表记录的各个字段使用NULL值填充。

如果把ON子句放到内连接中,MySQL会把它和WHERE子句一样对待。

一般情况下,我们都把只涉及单表的过滤条件放到WHERE子句中,把涉及两表的过滤条件都放到ON子句中。

3.连接的原理

在执行连接查询语句时,大致会经历以下两个步骤:

  • 步骤1:选取驱动表,使用与驱动表相关的过滤条件,选取代价最低的单表访问方法来执行对驱动表的单表查询。

  • 步骤2:对上一步骤中查询驱动表得到的结果集中每一条记录,都分别到被驱动表中查找匹配的记录。

驱动表只访问一次(步骤1),但被驱动表却可能被多次访问(步骤2)。无论是步骤1还是步骤2都可以利用索引来加快查询速度。

二、性能优化成本

相信大家都经常听说数据库优化这词,所以,数据库的优化是针对什么进行优化呢?这就不得不提到MySQL的优化成本了:

  • I/O成本:

我们的表经常使用的MyISAM、InnoDB存储引擎都是将数据和索引都存储到磁盘上的,当我们想查询表中的记录时,需要先把数据或者索引加载到内存中然后再操作。这个从磁盘到内存这个加载的过程损耗的时间称之为I/O成本。

  • CPU成本:

读取以及检测记录是否满足对应的搜索条件、对结果集进行排序等这些操作损耗的时间称之为CPU成本。

对于InnoDB存储引擎来说,页是磁盘和内存之间交互的基本单位,MySQL的设计者规定读取一个页面花费的成本默认是1.0,读取以及检测一条记录是否符合搜索条件的成本默认是0.2。1.0、0.2这些数字称之为成本常数。

1.基于成本的优化

在MySQL中,性能优化都是花费尽可能少的成本来达到检索目标的,大致是这么一个过程:

  • 根据搜索条件,找出所有可能使用的索引;
  • 计算全表扫描的代价;
  • 计算使用不同索引执行查询的代价;
  • 对比各种执行方案的代价,找出成本最低的那一个。

2.调节成本常数

MySQL将成本常数存储到了mysql数据库(系统数据库)中的两个表中,一个叫engine_cost,存储的是存储引擎的成本常数;另一个叫server_cost,存储的是MySQL server端的成本常数。

在server层进行连接管理、查询缓存、语法解析、查询优化等操作,在存储引擎层执行具体的数据存取操作。也就是说一条语句在server层中执行的成本是和它操作的表使用的存储引擎是没关系的,所以关于这些操作对应的成本常数就存储在了server_cost表中,而依赖于存储引擎的一些操作对应的成本常数就存储在了engine_cost表中。

(1)mysql.server_cost表

server_cost表中在server层进行的一些操作对应的成本常数,具体内容如下:

在这里插入图片描述

  • 列的说明:

    • cost_name:表示成本常数的名称。
    • cost_value:表示成本常数对应的值。如果该列的值为NULL的话,意味着对应的成本常数会采用默认值。
    • last_update:表示最后更新记录的时间。
    • comment:注释。
  • 成本常量说明:

  • disk_temptable_create_cost:默认值是40.0,创建基于磁盘的临时表的成本,如果增大这个值的话会让优化器尽量少的创建基于磁盘的临时表。

  • disk_temptable_row_cost:默认值是1.0,向基于磁盘的临时表写入或读取一条记录的成本,如果增大这个值的话会让优化器尽量少的创建基于磁盘的临时表。

  • key_compare_cost:默认值是0.1,两条记录做比较操作的成本,多用在排序操作上,如果增大这个值的话会提升filesort的成本,让优化器可能更倾向于使用索引完成排序而不是filesort。

在MySQL中,FileSort 是一个排序算法,用于处理那些无法通过索引直接完成排序的查询。当MySQL执行一个查询并需要根据某些字段对结果进行排序时,如果这些字段上没有合适的索引,或者查询的复杂度导致MySQL决定不使用现有索引,MySQL就会使用 FileSort 方法来对结果集进行排序。
算法一——内存排序:MySQL首先尝试在内存中对数据进行排序。如果排序的数据量较小,可以完全放入内存中,MySQL会在内存中完成排序操作。MySQL使用一个称为“sort buffer”的内存区域来存储待排序的数据。
算法二——磁盘排序:如果数据量过大,超出了可用的内存大小,MySQL会将部分数据写入临时文件,并在磁盘上进行排序。这种情况下,MySQL会创建多个临时文件,每个文件中包含一部分排序后的数据。最后,MySQL会将这些临时文件合并成一个最终的排序结果。
识别FileSort:在执行 EXPLAIN 命令时,如果看到 Extra 列中有 Using filesort,这表明MySQL正在使用 FileSort 来对结果集进行排序。

  • memory_temptable_create_cost :默认2.0,创建基于内存的临时表的成本,如果增大这个值的话会让优化器尽量少的创建基于内存的临时表。
  • memory_temptable_row_cost:默认0.2,向基于内存的临时表写入或读取一条记录的成本,如果增大这个值的话会让优化器尽量少的创建基于内存的临时表。
  • row_evaluate_cost:默认0.2,检测一条记录是否符合搜索条件的成本,增大这个值可能让优化器更倾向于使用索引而不是直接全表扫描。

MySQL在执行诸如DISTINCT查询、分组查询、Union查询以及某些特殊条件下的排序查询都可能在内部先创建一个临时表,使用这个临时表来辅助完成查询(比如对于DISTINCT查询可以建一个带有UNIQUE索引的临时表,直接把需要去重的记录插入到这个临时表中,插入完成之后的记录就是结果集了)。在数据量大的情况下可能创建基于磁盘的临时表,也就是为该临时表使用MyISAM、InnoDB等存储引擎,在数据量不大时可能创建基于内存的临时表,也就是使用Memory存储引擎。

(2)mysql.engine_cost表

在这里插入图片描述

  • engine_name列:指成本常数适用的存储引擎名称。如果该值为default,意味着对应的成本常数适用于所有的存储引擎。

  • device_type列:指存储引擎使用的设备类型,这主要是为了区分常规的机械硬盘和固态硬盘,不过在MySQL 5.7.21这个版本中并没有对机械硬盘的成本和固态硬盘的成本作区分,所以该值默认是0。

  • 成本常量说明:

io_block_read_cost:默认值1.0,从磁盘上读取一个块对应的成本。对于InnoDB存储引擎来说,一个页就是一个块,不过对于MyISAM存储引擎来说,默认是以4096字节作为一个块的。增大这个值会加重I/O成本,可能让优化器更倾向于选择使用索引执行查询而不是执行全表扫描。
memory_block_read_cost:默认值1.0,与上一个参数类似,只不过衡量的是从内存中读取一个块对应的成本。

当我们想要通过调整成本常量值来优化MySQL性能时,可以根据业务需求来调整mysql.engine_cost和mysql.server_cost中的成本常量值。