百度360必应搜狗淘宝本站头条
当前位置:网站首页 > IT技术 > 正文

SQL之JOIN优化

wptr33 2025-02-26 14:04 26 浏览

一、JOIN算法原理

1、JOIN回顾

JOIN 是 MySQL 用来进行联表操作的,用来匹配两个表的数据,筛选并合并出符合我们要求的结果集。

JOIN 操作有多种方式,取决于最终数据的合并效果。常用连接方式的有以下几种:


2、驱动表的定义

什么是驱动表?

多表关联查询时,第一个被处理的表,使用此表的记录去关联其他表, 驱动表的确定很关键,会直接影响多表连接的关联顺序,也决定了后续关联时的查询性能。

驱动表的选择遵循一个原则:

在对最终结果集没影响的前提下,优先选择结果集最小的那张表作为驱动表


3、三种JOIN算法

1、Simple Nested-Loop Join(简单的嵌套循环连接)

简单来说嵌套循环连接算法就是一个双层for 循环 ,通过循环外层表的行数据,逐个与内层表的所有行数据进行比较来获取结果。

这种算法是最简单的方案,性能也一般。对内循环没优化。

例如有这样一条SQL:


-- 连接用户表与订单表 连接条件是 u.id = o.user_id
select * from user t1 left join order t2 on t1.id = t2.user_id;
-- user表为驱动表,order表为被驱动表


转换成代码执行时的思路是这样的:


for(user表行 uRow : user表){
  for(Order表的行 oRow : order表){
    if(uRow.id = oRow.user_id){
      return uRow;
    }
  }
}


匹配过程如下图


SNL 的特点

  1. 简单粗暴容易理解,就是通过双层循环比较数据来获得结果
  2. 查询效率会非常慢,假设 A 表有 N 行,B 表有 M 行。SNL 的开销如下:
  • A 表扫描 1 次。
  • B 表扫描 M 次。
  • 一共有 N 个内循环,每个内循环要 M 次,一共有内循环 N * M 次


2、Index Nested-Loop Join(索引嵌套循环连接)

  • Index Nested-Loop Join 其优化的思路: 主要是为了减少内层表数据的匹配次数 , 最大的区别在于,用来进行 join 的字段已经在被驱动表中建立了索引。
  • 从原来的 匹配次数 = 外层表行数 * 内层表行数 , 变成了 匹配次数 = 外层表的行数 * 内层表索引的高度 ,极大的提升了 join的性能。
  • 当 order 表的 user_id 为索引的时候执行过程会如下图:


注意:使用Index Nested-Loop Join 算法的前提是匹配的字段必须建立了索引。


3、Block Nested-Loop Join,块嵌套循环

如果 join 的字段有索引,MySQL 会使用 INL 算法。如果没有的话,MySQL 会如何处理?

因为不存在索引了,所以被驱动表需要进行扫描。这里 MySQL 并不会简单粗暴的应用 SNL 算法,而是加入了 buffffer 缓冲区,降低了内循环的个数,也就是被驱动表的扫描次数。


  • 在外层循环扫描 user表中的所有记录。扫描的时候,会把需要进行 join 用到的列都缓存到buffffer 中。buffffer 中的数据有一个特点,里面的记录不需要一条一条地取出来和 order 表进行比较,而是整个 buffffer 和 order表进行批量比较。
  • 如果我们把 buffffer 的空间开得很大,可以容纳下 user 表的所有记录,那么 order 表也只需要访问一次。
  • MySQL 默认 buffffer 大小 256K,如果有 n 个 join 操作,会生成 n-1个join buffffer。


mysql> show variables like '%join_buffer%';
+------------------+--------+
| Variable_name 	 | Value  |
+------------------+--------+
| join_buffer_size | 262144 |
+------------------+--------+
  
mysql> set session join_buffer_size=262144;
Query OK, 0 rows affected (0.00 sec)


4、总结

  1. 永远用小结果集驱动大结果集(其本质就是减少外层循环的数据数量);
  2. 为匹配的条件增加索引(减少内层表的循环匹配次数);
  3. 增大join buffffer size的大小(一次缓存的数据越多,那么内层包的扫表次数就越少);
  4. 减少不必要的字段查询(字段越少,join buffffer 所缓存的数据就越多;


二、in和exists函数

上面我们说了 小表驱动大表,就是小的数据集驱动大的数据集, 主要是为了减少数据库的连接次数,根据具体情况的不同,又出现了两个函数 exists 和 in 函数。

创建部门表与员工表,并插入数据


-- 部门表
CREATE TABLE department (
id INT(11) PRIMARY KEY,
deptName VARCHAR(30) ,
address VARCHAR(40)
) ;

-- 部门表测试数据
INSERT INTO `department` VALUES (1, '研发部', '1层');
INSERT INTO `department` VALUES (2, '人事部', '3层');
INSERT INTO `department` VALUES (3, '市场部', '4层');
INSERT INTO `department` VALUES (5, '财务部', '2层');

-- 员工表
CREATE TABLE employee (
id INT(11) PRIMARY KEY,
NAME VARCHAR(20) ,
dep_id INT(11) ,
age INT(11) ,
salary DECIMAL(10, 2)
);

-- 员工表测试数据
INSERT INTO `employee` VALUES (1, '鲁班', 1, 15, 1000.00);
INSERT INTO `employee` VALUES (2, '后裔', 1, 22, 2000.00)
INSERT INTO `employee` VALUES (4, '阿凯', 2, 20, 3000.00);
INSERT INTO `employee` VALUES (5, '露娜', 2, 30, 3500.00);
INSERT INTO `employee` VALUES (6, '李白', 3, 25, 5000.00);
INSERT INTO `employee` VALUES (7, '韩信', 3, 50, 5000.00);
INSERT INTO `employee` VALUES (8, '蔡文姬', 3, 35, 4000.00);
INSERT INTO `employee` VALUES (3, '孙尚香', 4, 20, 2500.00);


1、in 函数

假设: department表的数据小于 employee表数据, 将所有部门下的员工都查出来,应该使用 in 函数


-- 编写SQL,使in 函数
SELECT * FROM employee e WHERE e.dep_id IN (SELECT id FROM department);


in函数的执行原理

in 语句, 只执行一次, 将 department 表中的所有id字段查询出来并且缓存。

然后, 检查 department 表中的id与 employee 表中的 dep_id 是否相等, 如果相等 添加到结果集, 直到遍历完 department 所有的记录。


-- 先循环: select id from department; 相当于得到了小表的数据
-- 后循环: select * from employee where e.dep_id = d.id;

for(i = 0; i < $dept.length; i++){ -- 小表
  for(j = 0 ; j < $emp.legth; j++){ -- 大表
    if($dept[i].id == $emp[j].dep_id){
      $result[i] = $emp[j]
      break;
    }
  }
}


结论: 如果子查询得出的结果集记录较少,主查询中的表较大且又有索引时应该用 in


2、exists 函数

假设: department表的数据大于 employee表数据, 将所有部门下的的员工都查出来,应该使用exists 函数。

explain SELECT * FROM employee e WHERE EXISTS
(SELECT id FROM department d WHERE d.id = e.dep_id);

exists 特点

exists 子句返回的是一个 布尔值,如果有返回数据,则返回值是 true ,反之是 false 。

如果结果为 true , 外层的查询语句会进行匹配,否则 外层查询语句将不进行查询或者查不出任何记录。


exists 函数的执行原理

-- 先循环: SELECT * FROM employee e;
-- 再判断: SELECT id FROM department d WHERE d.id = e.dep_id

for(j = 0; j < $emp.length; j++){ -- 小表
                                 
  -- 遍历循环外表,检查外表中的记录有没有和内表的的数据一致的, 匹配得上就放入结果集。
  if(exists(emp[i].dep_id)){ -- 大表
  	$result[i] = $emp[i];
  }
}


3、in 和 exists 的区别

  • 如果子查询得出的结果集记录较少,主查询中的表较大且又有索引时应该用 in。
  • 如果主查询得出的结果集记录较少,子查询中的表较大且又有索引时应该用 exists。
  • in后面跟的是小表,exists后面跟的是大表。

相关推荐

oracle数据导入导出_oracle数据导入导出工具

关于oracle的数据导入导出,这个功能的使用场景,一般是换服务环境,把原先的oracle数据导入到另外一台oracle数据库,或者导出备份使用。只不过oracle的导入导出命令不好记忆,稍稍有点复杂...

继续学习Python中的while true/break语句

上次讲到if语句的用法,大家在微信公众号问了小编很多问题,那么小编在这几种解决一下,1.else和elif是子模块,不能单独使用2.一个if语句中可以包括很多个elif语句,但结尾只能有一个else解...

python continue和break的区别_python中break语句和continue语句的区别

python中循环语句经常会使用continue和break,那么这2者的区别是?continue是跳出本次循环,进行下一次循环;break是跳出整个循环;例如:...

简单学Python——关键字6——break和continue

Python退出循环,有break语句和continue语句两种实现方式。break语句和continue语句的区别:break语句作用是终止循环。continue语句作用是跳出本轮循环,继续下一次循...

2-1,0基础学Python之 break退出循环、 continue继续循环 多重循

用for循环或者while循环时,如果要在循环体内直接退出循环,可以使用break语句。比如计算1至100的整数和,我们用while来实现:sum=0x=1whileTrue...

Python 中 break 和 continue 傻傻分不清

大家好啊,我是大田。今天分享一下break和continue在代码中的执行效果是什么,进一步区分出二者的区别。一、continue例1:当小明3岁时不打印年龄,其余年龄正常循环打印。可以看...

python中的流程控制语句:continue、break 和 return使用方法

Python中,continue、break和return是控制流程的关键语句,用于在循环或函数中提前退出或跳过某些操作。它们的用途和区别如下:1.continue(跳过当前循环的剩余部分,进...

L017:continue和break - 教程文案

continue和break在Python中,continue和break是用于控制循环(如for和while)执行流程的关键字,它们的作用如下:1.continue:跳过当前迭代,...

作为前端开发者,你都经历过怎样的面试?

已经裸辞1个月了,最近开始投简历找工作,遇到各种各样的面试,今天分享一下。其实在职的时候也做过面试官,面试官时,感觉自己问的问题很难区分候选人的能力,最好的办法就是看看候选人的github上的代码仓库...

面试被问 const 是否不可变?这样回答才显功底

作为前端开发者,我在学习ES6特性时,总被const的"善变"搞得一头雾水——为什么用const声明的数组还能push元素?为什么基本类型赋值就会报错?直到翻遍MDN文档、对着内存图反...

2023金九银十必看前端面试题!2w字精品!

导文2023金九银十必看前端面试题!金九银十黄金期来了想要跳槽的小伙伴快来看啊CSS1.请解释CSS的盒模型是什么,并描述其组成部分。答案:CSS的盒模型是用于布局和定位元素的概念。它由内容区域...

前端面试总结_前端面试题整理

记得当时大二的时候,看到实验室的学长学姐忙于各种春招,有些收获了大厂offer,有些还在苦苦面试,其实那时候的心里还蛮忐忑的,不知道自己大三的时候会是什么样的一个水平,所以从19年的寒假放完,大二下学...

由浅入深,66条JavaScript面试知识点(七)

作者:JakeZhang转发链接:https://juejin.im/post/5ef8377f6fb9a07e693a6061目录由浅入深,66条JavaScript面试知识点(一)由浅入深,66...

2024前端面试真题之—VUE篇_前端面试题vue2020及答案

添加图片注释,不超过140字(可选)1.vue的生命周期有哪些及每个生命周期做了什么?beforeCreate是newVue()之后触发的第一个钩子,在当前阶段data、methods、com...

今年最常见的前端面试题,你会做几道?

在面试或招聘前端开发人员时,期望、现实和需求之间总是存在着巨大差距。面试其实是一个交流想法的地方,挑战人们的思考方式,并客观地分析给定的问题。可以通过面试了解人们如何做出决策,了解一个人对技术和解决问...