MySQL实战技巧-1:Join的使用技巧和优化

本文涉及的产品
云数据库 RDS MySQL Serverless,0.5-2RCU 50GB
简介: join用于多表中字段之间的联系,在数据库的DML (数据操作语言,即各种增删改查操作)中有着重要的作用。合理使用Join语句优化SQL有利于:增加数据库的处理效率,减少响应时间;减少数据库服务器负载,增加服务器稳定性;减少服务器通讯的网络流量;1.

join用于多表中字段之间的联系,在数据库的DML (数据操作语言,即各种增删改查操作)中有着重要的作用。

合理使用Join语句优化SQL有利于:

  1. 增加数据库的处理效率,减少响应时间;
  2. 减少数据库服务器负载,增加服务器稳定性;
  3. 减少服务器通讯的网络流量;

1. Join的分类:

  • 内连接 Inner Join
  • 全外连接 FULL Outer Join
  • 左外连接 Left Outer Join
  • 右外连接 Right Outer Join
  • 交叉连接 Cross Join
连接的分类

每种连接的区别作为基础内容,这里就不再展开说明,请读者自己参看其他文章了解,比如Mysql Join语法以及性能优化

需要说明的是,目前MySQL不支持全连接,需要使用UNION关键字进行联合。

Union:对两个结果集进行并集操作,不包括重复行,同时进行默认规则的排序;
Union All:对两个结果集进行并集操作,包括重复行,不进行排序;

3. Join使用的注意事项

下面进行本文重点,Join的使用注意事项和技巧,首先给出要使用的表结构:

-- auto-generated definition
CREATE TABLE customer
(
  id        INT AUTO_INCREMENT
    PRIMARY KEY,
  cust_name VARCHAR(50)  NOT NULL CHARSET utf8,
  over      VARCHAR(100) NULL CHARSET utf8,
  CONSTRAINT customer_id_uindex
  UNIQUE (id)
)
  ENGINE = InnoDB;
  
-- auto-generated definition
CREATE TABLE faculty
(
  id        INT AUTO_INCREMENT
    PRIMARY KEY,
  user_name VARCHAR(50)  NOT NULL CHARSET utf8,
  over      VARCHAR(200) NULL CHARSET utf8,
  CONSTRAINT faculty_id_uindex
  UNIQUE (id)
)
  ENGINE = InnoDB;
customer表中数据,代表客户的信息

faculty表中的数据,代表职工的信息

2.1 显式连接 VS 隐式连接

所谓显式连接,即如上显示使用inner Join关键字连接两个表,

select * from
table a inner join table b
on a.id = b.id;

而隐式连接即不显示使用inner Join关键字,如:

select a.*, b.*
from table a, table b
where a.id = b.id;

二者在功能上没有差别,实现的性能上也几乎一样。只不过隐式连接是SQL92中的标准内容,而在SQL99中显式连接为标准,虽然很多人还在用隐私连接,但是它已经从标准中被移除。从使用的角度来说,还是推荐使用显示连接,这样可以更清楚的显示出多个表之间的连接关系和连接依赖的属性。

2.2 On VS Where

ON 条件(“A LEFT JOIN B ON 条件表达式”中的ON)用来决定如何从 B 表中检索数据行。如果 B 表中没有任何一行数据匹配 ON 的条件,将会额外生成一行所有列为 NULL 的数据,在匹配阶段 WHERE 子句的条件都不会被使用。仅在匹配阶段完成以后,WHERE 子句条件才会被使用。ON将从匹配阶段产生的数据中检索过滤。

所以我们要注意:在使用Left (right) join的时候,一定要在先给出尽可能多的匹配满足条件,减少Where的执行。尽可能满足ON的条件,而少用Where的条件,从执行性能来看也更加高效。

3 Join的技巧

3.1 如何更新使用过虑条件中包括自身的表

假设现在要将是职工中的消费者的“over”属性设置为"优惠",直接如下更新会报错:


1516605305289.png

这是由于Mysql不支持这种查询后更新(这其实是标准SQL中一项要求,Oracle、SQL Server中都是可以的)。

为了解决这种更新的过虑条件中包含要更新的表的情况,可以把带过滤条件的查询结果当做一个新表,在新表上,执行更新操作。

UPDATE (faculty f INNER JOIN customer c
    on user_name=cust_name)
set c.over = "优惠";
更新成功

3.2 Join优化子查询

嵌套的子查询是比较低效地,因为每一条记录都要进行匹配,如果记录长度比较大的话,那么我们的查询就有可能非常的耗时。我们应该尽量避免使用子查询,而用表连接。如下面的这个子查询就可以转化为等价的连接查询

SELECT user_name, over ,(SELECT over FROM customer c where user_name=cust_name) as over2
from faculty f;
SELECT user_name, f.over , c.over as over2
from faculty f
  LEFT JOIN customer c ON cust_name=user_name;

3.3 使用Join优化聚合查询

为了说明这个问题 ,我们在添加一个工作量的表,记录每个职工每天的工作量

-- auto-generated definition
CREATE TABLE tasks
(
  id        SMALLINT(5) UNSIGNED AUTO_INCREMENT
    PRIMARY KEY,
  facult_id SMALLINT(5) UNSIGNED                NULL,
  timestr   TIMESTAMP DEFAULT CURRENT_TIMESTAMP NOT NULL,
  workload  SMALLINT(5) UNSIGNED                NULL
)
  ENGINE = InnoDB
  CHARSET = utf8;
tasks记录职工的工作量

比如我们想查询每个员工工作量最多是哪一天,通过子查询可以这样实现:

select a.user_name ,b.timestr,b.workload
from faculty a
  join tasks b
    on a.id = b.facult_id
where b.workload = (
  select max(c.workload)
  from tasks c
  where c.facult_id = b.facult_id)
查询结果

使用表连接优化之后:

SELECT user_name, t.timestr, t.workload
FROM faculty f
  JOIN tasks t ON f.id = t.facult_id
  JOIN tasks t2 ON t2.facult_id = t.facult_id
GROUP BY user_name,t.timestr,t.workload
HAVING t.workload = max(t2.workload);

这里额外的再连接了一个task表中内容,在这个“额外表”中通过聚合计算出工作量的最大值,然后再过虑(HAVING)出工作量最大的日期。

因为聚合函数通过作用于一组数据而只返回一个单个值,因此,在SELECT语句中出现的元素要么为一个聚合函数的输入值,要么为GROUP BY语句的参数,否则会出错。
但是mysql的group by做过扩展了,select之后的列允许其不出现在group by之后,MySQL在执行这类查询语句时,它会默认理解为,没写到GROUP BY子句的列,其列值是唯一的,如果GROUP BY省略的列值其实并不唯一,将会默认取第一个获得的值,这样就会指代不明,那么最好不要使用这项功能。

3.4 如何实现分组查询

要获取每个员工完成工作量最多的两天。这个也可以通过Join来完成。

select d.user_name,c.timestr,workload
FROM (
       select facult_id,timestr,workload,
         (SELECT COUNT(*)
          FROM tasks b
          WHERE b.facult_id=a.facult_id AND a.workload<=b.workload) AS cnt
       FROM tasks a
       GROUP BY facult_id,timestr,workload) c
  JOIN faculty d ON c.facult_id=d.id
WHERE cnt <= 2;

其中,内部的查询结果cnt表示对于tasks表中某个给定记录,相同员工的工作里记录比其大的数量有多少。
内部查询的结果如下:

 select facult_id,timestr,workload,
         (SELECT COUNT(*)
          FROM tasks b
          WHERE b.facult_id=a.facult_id AND a.workload<=b.workload) AS cnt
       FROM tasks a
       GROUP BY facult_id,timestr,workload;
内部查询的结果

即每个工作量记录信息和同一员工的工作量排名。
cnt <= 2就代表该记录是某位员工的工作量最大两天之一。

每个员工完成工作量最多的两天

4. join的实现原理

join的实现是采用Nested Loop Join算法,就是通过驱动表的结果集作为循环基础数据,然后一条一条的通过该结果集中的数据作为过滤条件到下一个表中查询数据,然后合并结果。如果有多个join,则将前面的结果集作为循环数据,再一次作为循环条件到后一个表中查询数据。

比如我们以如下SQL语句为例:

EXPLAIN SELECT C.id, cust_name,T.workload
FROM customer C
  INNER JOIN faculty F
    ON C.cust_name = F.user_name
  INNER JOIN tasks T
    ON T.facult_id = F.id ;
EXPLAIN 连接查询

explain的输出看出,MySQL选择C作为驱动表,
首先通过Using WhereUsing join buffer来匹配F中的内容,然后在其结果的基础上通过主键的索引PRIMARY,faculty_id_uindex匹配到T表中的内容。
其过程类似于三次次嵌套的循环。

需要说明的是,C作为驱动表,通过Using WhereUsing join buffer来匹配F,是因为C.cust_name ,F.user_name都没有加索引,要获取具体的内容只能通过对全表的数据进行where过滤才能获取,而Using join buffer是指使用到了Cache(只有当join类型为ALL,index,rang或者是index_merge的时候才会使用join buffer),记录已经查询的结果,提高效率。
而对于TF之间通过T的主键T.id连接,所以join类型为eq_ref,也不用使用Using join buffer。

5. join语句的优化原则

  1. 用小结果集驱动大结果集,将筛选结果小的表首先连接,再去连接结果集比较大的表,尽量减少join语句中的Nested Loop的循环总次数;
  2. 优先优化Nested Loop的内层循环(也就是最外层的Join连接),因为内层循环是循环中执行次数最多的,每次循环提升很小的性能都能在整个循环中提升很大的性能;
  3. 对被驱动表的join字段上建立索引
  4. 当被驱动表的join字段上无法建立索引的时候,设置足够的Join Buffer Size

参考文章

  1. MySQL数据库对GROUP BY子句的功能扩展(1)
  2. SQL中GROUP BY语句与HAVING语句的使用
  3. Mysql Join语法以及性能优化
  4. mysql join的实现原理及优化思路
  5. Explicit vs implicit SQL joins
  6. Deprecation of "Old Style" JOIN Syntax: Only A Partial Thing
相关实践学习
基于CentOS快速搭建LAMP环境
本教程介绍如何搭建LAMP环境,其中LAMP分别代表Linux、Apache、MySQL和PHP。
全面了解阿里云能为你做什么
阿里云在全球各地部署高效节能的绿色数据中心,利用清洁计算为万物互联的新世界提供源源不断的能源动力,目前开服的区域包括中国(华北、华东、华南、香港)、新加坡、美国(美东、美西)、欧洲、中东、澳大利亚、日本。目前阿里云的产品涵盖弹性计算、数据库、存储与CDN、分析与搜索、云通信、网络、管理与监控、应用服务、互联网中间件、移动服务、视频服务等。通过本课程,来了解阿里云能够为你的业务带来哪些帮助 &nbsp; &nbsp; 相关的阿里云产品:云服务器ECS 云服务器 ECS(Elastic Compute Service)是一种弹性可伸缩的计算服务,助您降低 IT 成本,提升运维效率,使您更专注于核心业务创新。产品详情: https://www.aliyun.com/product/ecs
相关文章
|
14天前
|
关系型数据库 MySQL 索引
mysql 分析5语句的优化--索引添加删除
mysql 分析5语句的优化--索引添加删除
12 0
|
20天前
|
存储 关系型数据库 MySQL
轻松入门MySQL:数据库设计之范式规范,优化企业管理系统效率(21)
轻松入门MySQL:数据库设计之范式规范,优化企业管理系统效率(21)
|
14天前
|
SQL 缓存 关系型数据库
mysql性能优化-慢查询分析、优化索引和配置
mysql性能优化-慢查询分析、优化索引和配置
80 1
|
17小时前
|
SQL Oracle 关系型数据库
下次老板问你MySQL如何优化时,你可以这样说,老板默默给你加工资
现在进入国企或者事业单位做技术的网友越来越多了,随着去O的力度越来越大,很多国企单位都开始从Oracle向MySQL转移,相对于Oracle而言,MySQL最大的问题就是性能,所以,这个时候,在公司如果能够处理好MySQL的性能瓶颈,那么你也就很容易从人群中脱颖而出,受到老板的青睐。
7 1
|
1天前
|
关系型数据库 MySQL 中间件
【MySQL实战笔记】07 | 行锁功过:怎么减少行锁对性能的影响?-02 死锁和死锁检测
【4月更文挑战第19天】在高并发环境下,死锁发生在多个线程间循环等待资源时,导致无限期等待。MySQL中,死锁可通过`innodb_lock_wait_timeout`参数设置超时或`innodb_deadlock_detect`开启死锁检测来解决。默认的50s超时可能不适用于在线服务,而频繁检测会消耗大量CPU。应对热点行更新引发的性能问题,可以暂时关闭死锁检测(风险是产生大量超时),控制并发度,或通过分散记录减少锁冲突,例如将数据分拆到多行以降低死锁概率。
16 1
|
4天前
|
SQL 关系型数据库 MySQL
Python与MySQL数据库交互:面试实战
【4月更文挑战第16天】本文介绍了Python与MySQL交互的面试重点,包括使用`mysql-connector-python`或`pymysql`连接数据库、执行SQL查询、异常处理、防止SQL注入、事务管理和ORM框架。易错点包括忘记关闭连接、忽视异常处理、硬编码SQL、忽略事务及过度依赖低效查询。通过理解这些问题和提供策略,可提升面试表现。
25 6
|
9天前
|
SQL 关系型数据库 数据库
【后端面经】【数据库与MySQL】SQL优化:如何发现SQL中的问题?
【4月更文挑战第12天】数据库优化涉及硬件升级、操作系统调整、服务器/引擎优化和SQL优化。SQL优化目标是减少磁盘IO和内存/CPU消耗。`EXPLAIN`命令用于检查SQL执行计划,关注`type`、`possible_keys`、`key`、`rows`和`filtered`字段。设计索引时考虑外键、频繁出现在`where`、`order by`和关联查询中的列,以及区分度高的列。大数据表改结构需谨慎,可能需要停机、低峰期变更或新建表。面试中应准备SQL优化案例,如覆盖索引、优化`order by`、`count`和索引提示。优化分页查询时避免大偏移量,可利用上一批的最大ID进行限制。
33 3
|
11天前
|
存储 关系型数据库 MySQL
【MySQL实战笔记】 04 | 深入浅出索引(上)-02
【4月更文挑战第9天】InnoDB数据库使用B+树作为索引模型,其中主键索引的叶子节点存储完整行数据,非主键索引则存储主键值。主键查询只需搜索一棵树,而非主键查询需两次搜索,因此推荐使用主键查询以提高效率。在插入新值时,B+树需要维护有序性,可能导致数据页分裂影响性能。自增主键在插入时可避免数据挪动和页分裂,且占用存储空间小,通常更为理想。然而,如果场景仅需唯一索引,可直接设为主键以减少查询步骤。
13 1
【MySQL实战笔记】 04 | 深入浅出索引(上)-02
|
13天前
|
存储 SQL 关系型数据库
【MySQL实战笔记】03.事务隔离:为什么你改了我还看不见?-02
【4月更文挑战第7天】数据库通过视图实现事务隔离,不同隔离级别如读未提交、读已提交、可重复读和串行化采用不同策略。以可重复读为例,MySQL使用多版本并发控制(MVCC),每个事务有其独立的视图。回滚日志在无更早视图时被删除。长事务可能导致大量存储占用,应避免。事务启动可显式用`begin`或设置`autocommit=0`,但后者可能意外开启长事务。建议使用`autocommit=1`并显式管理事务,若需减少交互,可使用`commit work and chain`。
29 5
|
15天前
|
SQL 存储 关系型数据库
【MySQL实战笔记】02.一条SQL更新语句是如何执行的-2
【4月更文挑战第5天】两阶段提交是为确保`redo log`和`binlog`逻辑一致,避免数据不一致。若先写`redo log`, crash后数据可能丢失,导致恢复后状态错误;若先写`binlog`,crash则可能导致重复事务,影响数据库一致性。一天一备相较于一周一备,能缩短“最长恢复时间”,但需权衡额外的存储成本。
16 1