zl程序教程

您现在的位置是:首页 >  数据库

当前栏目

玩转Mysql系列 - 第11篇:深入了解连接查询及原理

mysql连接原理 查询 系列 深入 了解 11
2023-09-27 14:26:04 时间

打算提升sql技能的,可以加我微信itsoku,带你成为sql高手。

这是Mysql系列第11篇。

环境:mysql5.7.25,cmd命令中进行演示。

当我们查询的数据来源于多张表的时候,我们需要用到连接查询,连接查询使用率非常高,希望大家都务必掌握。

本文内容

  1. 笛卡尔积

  2. 内连接

  3. 外连接

  4. 左连接

  5. 右连接

  6. 表连接的原理

  7. 使用java实现连接查询,加深理解

准备数据

2张表:

t_team:组表。

t_employee:员工表,内部有个team_id引用组表的id。

  1. drop table if exists t_team;
  2. create table t_team(
  3.   id int not null AUTO_INCREMENT PRIMARY KEY comment '组id',
  4.   team_name varchar(32) not null default '' comment '名称'
  5. ) comment '组表';
  6. drop table if exists t_employee;
  7. create table t_employee(
  8.   id int not null AUTO_INCREMENT PRIMARY KEY comment '部门id',
  9.   emp_name varchar(32) not null default '' comment '员工名称',
  10.   team_id int not null default 0 comment '员工所在组id'
  11. ) comment '员工表表';
  12. insert into t_team values (1,'架构组'),(2,'测试组'),(3,'java组'),(4,'前端组');
  13. insert into t_employee values (1,'路人甲Java',1),(2,'张三',2),(3,'李四',3),(4,'王五',0),(5,'赵六',0);

t_team表4条记录,如下:

  1. mysql> select * from t_team;
  2. +----+-----------+
  3. | id | team_name |
  4. +----+-----------+
  5. |  1 | 架构组    |
  6. |  2 | 测试组    |
  7. |  3 | java组    |
  8. |  4 | 前端组    |
  9. +----+-----------+
  10. 4 rows in set (0.00 sec)

t_employee表5条记录,如下:

  1. mysql> select * from t_employee;
  2. +----+---------------+---------+
  3. | id | emp_name      | team_id |
  4. +----+---------------+---------+
  5. |  1 | 路人甲Java    |       1 |
  6. |  2 | 张三          |       2 |
  7. |  3 | 李四          |       3 |
  8. |  4 | 王五          |       0 |
  9. |  5 | 赵六          |       0 |
  10. +----+---------------+---------+
  11. 5 rows in set (0.00 sec)

笛卡尔积

介绍连接查询之前,我们需要先了解一下笛卡尔积。

笛卡尔积简单点理解:有两个集合A和B,笛卡尔积表示A集合中的元素和B集合中的元素任意相互关联产生的所有可能的结果。

假如A中有m个元素,B中有n个元素,A、B笛卡尔积产生的结果有m*n个结果,相当于循环遍历两个集合中的元素,任意组合。

java伪代码表示如下:

  1. for(Object eleA : A){
  2.     for(Object eleB : B){
  3.         System.out.print(eleA+","+eleB);
  4.     }
  5. }

过程:拿A集合中的第1行,去匹配集合B中所有的行,然后再拿集合A中的第2行,去匹配集合B中所有的行,最后结果数量为m*n。

sql中笛卡尔积语法

  1. select 字段 from 表1,表2[,表N];
  2. 或者
  3. select 字段 from 表1 join 表2 [join 表N];

示例:

  1. mysql> select * from t_team,t_employee;
  2. +----+-----------+----+---------------+---------+
  3. | id | team_name | id | emp_name      | team_id |
  4. +----+-----------+----+---------------+---------+
  5. |  1 | 架构组    |  1 | 路人甲Java    |       1 |
  6. |  2 | 测试组    |  1 | 路人甲Java    |       1 |
  7. |  3 | java组    |  1 | 路人甲Java    |       1 |
  8. |  4 | 前端组    |  1 | 路人甲Java    |       1 |
  9. |  1 | 架构组    |  2 | 张三          |       2 |
  10. |  2 | 测试组    |  2 | 张三          |       2 |
  11. |  3 | java组    |  2 | 张三          |       2 |
  12. |  4 | 前端组    |  2 | 张三          |       2 |
  13. |  1 | 架构组    |  3 | 李四          |       3 |
  14. |  2 | 测试组    |  3 | 李四          |       3 |
  15. |  3 | java组    |  3 | 李四          |       3 |
  16. |  4 | 前端组    |  3 | 李四          |       3 |
  17. |  1 | 架构组    |  4 | 王五          |       0 |
  18. |  2 | 测试组    |  4 | 王五          |       0 |
  19. |  3 | java组    |  4 | 王五          |       0 |
  20. |  4 | 前端组    |  4 | 王五          |       0 |
  21. |  1 | 架构组    |  5 | 赵六          |       0 |
  22. |  2 | 测试组    |  5 | 赵六          |       0 |
  23. |  3 | java组    |  5 | 赵六          |       0 |
  24. |  4 | 前端组    |  5 | 赵六          |       0 |
  25. +----+-----------+----+---------------+---------+
  26. 20 rows in set (0.00 sec)

t_team表4条记录,t_employee表5条记录,笛卡尔积结果输出了20行记录。

内连接

语法:

  1. select 字段 from 表1 inner join 表2 on 连接条件;
  2. select 字段 from 表1 join 表2 on 连接条件;
  3. select 字段 from 表1, 表2 [where 关联条件];

内连接相当于在笛卡尔积的基础上加上了连接的条件。

当没有连接条件的时候,内连接上升为笛卡尔积。

过程用java伪代码如下:

  1. for(Object eleA : A){
  2.     for(Object eleB : B){
  3.         if(连接条件是否为true){
  4.             System.out.print(eleA+","+eleB);
  5.         }
  6.     }
  7. }

示例1:有连接条件

查询员工及所属部门

  1. mysql> select t1.emp_name,t2.team_name from t_employee t1 inner join t_team t2 on t1.team_id = t2.id;
  2. +---------------+-----------+
  3. | emp_name      | team_name |
  4. +---------------+-----------+
  5. | 路人甲Java    | 架构组    |
  6. | 张三          | 测试组    |
  7. | 李四          | java组    |
  8. +---------------+-----------+
  9. 3 rows in set (0.00 sec)
  10. mysql> select t1.emp_name,t2.team_name from t_employee t1 join t_team t2 on t1.team_id = t2.id;
  11. +---------------+-----------+
  12. | emp_name      | team_name |
  13. +---------------+-----------+
  14. | 路人甲Java    | 架构组    |
  15. | 张三          | 测试组    |
  16. | 李四          | java组    |
  17. +---------------+-----------+
  18. 3 rows in set (0.00 sec)
  19. mysql> select t1.emp_name,t2.team_name from t_employee t1, t_team t2 where t1.team_id = t2.id;
  20. +---------------+-----------+
  21. | emp_name      | team_name |
  22. +---------------+-----------+
  23. | 路人甲Java    | 架构组    |
  24. | 张三          | 测试组    |
  25. | 李四          | java组    |
  26. +---------------+-----------+
  27. 3 rows in set (0.00 sec)

上面相当于获取了2个表的交集,查询出了两个表都有的数据。

示例2:无连接条件

无条件内连接,上升为笛卡尔积,如下:

  1. mysql> select t1.emp_name,t2.team_name from t_employee t1 inner join t_team t2;
  2. +---------------+-----------+
  3. | emp_name      | team_name |
  4. +---------------+-----------+
  5. | 路人甲Java    | 架构组    |
  6. | 路人甲Java    | 测试组    |
  7. | 路人甲Java    | java组    |
  8. | 路人甲Java    | 前端组    |
  9. | 张三          | 架构组    |
  10. | 张三          | 测试组    |
  11. | 张三          | java组    |
  12. | 张三          | 前端组    |
  13. | 李四          | 架构组    |
  14. | 李四          | 测试组    |
  15. | 李四          | java组    |
  16. | 李四          | 前端组    |
  17. | 王五          | 架构组    |
  18. | 王五          | 测试组    |
  19. | 王五          | java组    |
  20. | 王五          | 前端组    |
  21. | 赵六          | 架构组    |
  22. | 赵六          | 测试组    |
  23. | 赵六          | java组    |
  24. | 赵六          | 前端组    |
  25. +---------------+-----------+
  26. 20 rows in set (0.00 sec)

示例3:组合条件进行查询

查询架构组的员工,3种写法

  1. mysql> select t1.emp_name,t2.team_name from t_employee t1 inner join t_team t2 on t1.team_id = t2.id and t2.team_name = '架构组';
  2. +---------------+-----------+
  3. | emp_name      | team_name |
  4. +---------------+-----------+
  5. | 路人甲Java    | 架构组    |
  6. +---------------+-----------+
  7. 1 row in set (0.00 sec)
  8. mysql> select t1.emp_name,t2.team_name from t_employee t1 inner join t_team t2 on t1.team_id = t2.id where t2.team_name = '架构组';
  9. +---------------+-----------+
  10. | emp_name      | team_name |
  11. +---------------+-----------+
  12. | 路人甲Java    | 架构组    |
  13. +---------------+-----------+
  14. 1 row in set (0.00 sec)
  15. mysql> select t1.emp_name,t2.team_name from t_employee t1, t_team t2 where t1.team_id = t2.id and t2.team_name = '架构组';
  16. +---------------+-----------+
  17. | emp_name      | team_name |
  18. +---------------+-----------+
  19. | 路人甲Java    | 架构组    |
  20. +---------------+-----------+
  21. 1 row in set (0.00 sec)

上面3中方式解说。

方式1:on中使用了组合条件。

方式2:在连接的结果之后再进行过滤,相当于先获取连接的结果,然后使用where中的条件再对连接结果进行过滤。

方式3:直接在where后面进行过滤。

总结

内连接建议使用第3种语法,简洁:

select 字段 from 表1, 表2 [where 关联条件];

外连接

外连接涉及到2个表,分为:主表和从表,要查询的信息主要来自于哪个表,谁就是主表。

外连接查询结果为主表中所有记录。如果从表中有和它匹配的,则显示匹配的值,这部分相当于内连接查询出来的结果;如果从表中没有和它匹配的,则显示null。

最终:外连接查询结果 = 内连接的结果 + 主表中有的而内连接结果中没有的记录。

外连接分为2种:

左外链接:使用left join关键字,left join左边的是主表。

右外连接:使用right join关键字,right join右边的是主表。

左连接

语法

select 列 from 主表 left join 从表 on 连接条件;

示例1:

查询所有员工信息,并显示员工所在组,如下:

  1. mysql> SELECT
  2.         t1.emp_name,
  3.         t2.team_name
  4.     FROM
  5.         t_employee t1
  6.     LEFT JOIN
  7.         t_team t2
  8.     ON
  9.         t1.team_id = t2.id;
  10. +---------------+-----------+
  11. | emp_name      | team_name |
  12. +---------------+-----------+
  13. | 路人甲Java    | 架构组    |
  14. | 张三          | 测试组    |
  15. | 李四          | java组    |
  16. | 王五          | NULL      |
  17. | 赵六          | NULL      |
  18. +---------------+-----------+
  19. 5 rows in set (0.00 sec)

上面查询出了所有员工,员工team_id=0的,team_name为NULL。

示例2:

查询员工姓名、组名,返回组名不为空的记录,如下:

  1. mysql> SELECT
  2.         t1.emp_name,
  3.         t2.team_name
  4.     FROM
  5.         t_employee t1
  6.     LEFT JOIN
  7.         t_team t2
  8.     ON
  9.         t1.team_id = t2.id
  10.     WHERE
  11.         t2.team_name IS NOT NULL;
  12. +---------------+-----------+
  13. | emp_name      | team_name |
  14. +---------------+-----------+
  15. | 路人甲Java    | 架构组    |
  16. | 张三          | 测试组    |
  17. | 李四          | java组    |
  18. +---------------+-----------+
  19. 3 rows in set (0.00 sec)

上面先使用内连接获取连接结果,然后再使用where对连接结果进行过滤。

右连接

语法

select 列 from 从表 right join 主表 on 连接条件;

示例

我们使用右连接来实现上面左连接实现的功能,如下:

  1. mysql> SELECT
  2.         t2.team_name,
  3.         t1.emp_name
  4.     FROM
  5.         t_team t2
  6.     RIGHT JOIN
  7.         t_employee t1
  8.     ON
  9.         t1.team_id = t2.id;
  10. +-----------+---------------+
  11. | team_name | emp_name      |
  12. +-----------+---------------+
  13. | 架构组    | 路人甲Java    |
  14. | 测试组    | 张三          |
  15. | java组    | 李四          |
  16. NULL      | 王五          |
  17. NULL      | 赵六          |
  18. +-----------+---------------+
  19. 5 rows in set (0.00 sec)
  20. mysql> SELECT
  21.         t2.team_name,
  22.         t1.emp_name
  23.     FROM
  24.         t_team t2
  25.     RIGHT JOIN
  26.         t_employee t1
  27.     ON
  28.         t1.team_id = t2.id
  29.     WHERE
  30.         t2.team_name IS NOT NULL;
  31. +-----------+---------------+
  32. | team_name | emp_name      |
  33. +-----------+---------------+
  34. | 架构组    | 路人甲Java    |
  35. | 测试组    | 张三          |
  36. | java组    | 李四          |
  37. +-----------+---------------+
  38. 3 rows in set (0.00 sec)

理解表连接原理

准备数据

  1. drop table if exists test1;
  2. create table test1(
  3.   a int
  4. );
  5. drop table if exists test2;
  6. create table test2(
  7.   b int
  8. );
  9. insert into test1 values (1),(2),(3);
  10. insert into test2 values (3),(4),(5);
  1. mysql> select * from test1;
  2. +------+
  3. | a    |
  4. +------+
  5. |    1 |
  6. |    2 |
  7. |    3 |
  8. +------+
  9. 3 rows in set (0.00 sec)
  10. mysql> select * from test2;
  11. +------+
  12. | b    |
  13. +------+
  14. |    3 |
  15. |    4 |
  16. |    5 |
  17. +------+
  18. 3 rows in set (0.00 sec)

我们来写几个连接,看看效果。

示例1:内连接

  1. mysql> select * from test1 t1,test2 t2;
  2. +------+------+
  3. | a    | b    |
  4. +------+------+
  5. |    1 |    3 |
  6. |    2 |    3 |
  7. |    3 |    3 |
  8. |    1 |    4 |
  9. |    2 |    4 |
  10. |    3 |    4 |
  11. |    1 |    5 |
  12. |    2 |    5 |
  13. |    3 |    5 |
  14. +------+------+
  15. 9 rows in set (0.00 sec)
  16. mysql> select * from test1 t1,test2 t2 where t1.a = t2.b;
  17. +------+------+
  18. | a    | b    |
  19. +------+------+
  20. |    3 |    3 |
  21. +------+------+
  22. 1 row in set (0.00 sec)

9条数据正常。

示例2:左连接

  1. mysql> select * from test1 t1 left join test2 t2 on t1.a = t2.b;
  2. +------+------+
  3. | a    | b    |
  4. +------+------+
  5. |    3 |    3 |
  6. |    1 | NULL |
  7. |    2 | NULL |
  8. +------+------+
  9. 3 rows in set (0.00 sec)
  10. mysql> select * from test1 t1 left join test2 t2 on t1.a>10;
  11. +------+------+
  12. | a    | b    |
  13. +------+------+
  14. |    1 | NULL |
  15. |    2 | NULL |
  16. |    3 | NULL |
  17. +------+------+
  18. 3 rows in set (0.00 sec)
  19. mysql> select * from test1 t1 left join test2 t2 on 1=1;
  20. +------+------+
  21. | a    | b    |
  22. +------+------+
  23. |    1 |    3 |
  24. |    2 |    3 |
  25. |    3 |    3 |
  26. |    1 |    4 |
  27. |    2 |    4 |
  28. |    3 |    4 |
  29. |    1 |    5 |
  30. |    2 |    5 |
  31. |    3 |    5 |
  32. +------+------+
  33. 9 rows in set (0.00 sec)

上面的左连接第一个好理解。

第2个sql连接条件t1.a>10,这个条件只关联了test1表,再看看结果,是否可以理解?不理解的继续向下看,我们用java代码来实现连接查询。

第3个sql中的连接条件1=1值为true,返回结果为笛卡尔积。

java代码实现连接查询

下面是一个简略版的实现

  1. package com.itsoku.sql;
  2. import org.junit.Test;
  3. import java.util.ArrayList;
  4. import java.util.Arrays;
  5. import java.util.List;
  6. import java.util.Objects;
  7. import java.util.concurrent.CopyOnWriteArrayList;
  8. import java.util.stream.Collectors;
  9. public class Test1 {
  10.     public static class Table1 {
  11.         int a;
  12.         public int getA() {
  13.             return a;
  14.         }
  15.         public void setA(int a) {
  16.             this.a = a;
  17.         }
  18.         public Table1(int a) {
  19.             this.a = a;
  20.         }
  21.         @Override
  22.         public String toString() {
  23.             return "Table1{" +
  24.                     "a=" + a +
  25.                     '}';
  26.         }
  27.         public static Table1 build(int a) {
  28.             return new Table1(a);
  29.         }
  30.     }
  31.     public static class Table2 {
  32.         int b;
  33.         public int getB() {
  34.             return b;
  35.         }
  36.         public void setB(int b) {
  37.             this.b = b;
  38.         }
  39.         public Table2(int b) {
  40.             this.b = b;
  41.         }
  42.         public static Table2 build(int b) {
  43.             return new Table2(b);
  44.         }
  45.         @Override
  46.         public String toString() {
  47.             return "Table2{" +
  48.                     "b=" + b +
  49.                     '}';
  50.         }
  51.     }
  52.     public static class Record<R1R2> {
  53.         R1 r1;
  54.         R2 r2;
  55.         public R1 getR1() {
  56.             return r1;
  57.         }
  58.         public void setR1(R1 r1) {
  59.             this.r1 = r1;
  60.         }
  61.         public R2 getR2() {
  62.             return r2;
  63.         }
  64.         public void setR2(R2 r2) {
  65.             this.r2 = r2;
  66.         }
  67.         public Record(R1 r1, R2 r2) {
  68.             this.r1 = r1;
  69.             this.r2 = r2;
  70.         }
  71.         @Override
  72.         public String toString() {
  73.             return "Record{" +
  74.                     "r1=" + r1 +
  75.                     ", r2=" + r2 +
  76.                     '}';
  77.         }
  78.         public static <R1R2Record<R1R2build(R1 r1, R2 r2) {
  79.             return new Record(r1, r2);
  80.         }
  81.     }
  82.     public static enum JoinType {
  83.         innerJoin, leftJoin
  84.     }
  85.     public static interface Filter<R1R2> {
  86.         boolean accept(R1 r1, R2 r2);
  87.     }
  88.     public static <R1R2List<Record<R1R2>> join(List<R1> table1, List<R2> table2, JoinType joinType, Filter<R1, R2> onFilter, Filter<R1, R2> whereFilter) {
  89.         if (Objects.isNull(table1) || Objects.isNull(table2) || joinType == null) {
  90.             return new ArrayList<>();
  91.         }
  92.         List<Record<R1R2>> result = new CopyOnWriteArrayList<>();
  93.         for (R1 r1 : table1) {
  94.             List<Record<R1R2>> onceJoinResult = joinOn(r1, table2, onFilter);
  95.             result.addAll(onceJoinResult);
  96.         }
  97.         if (joinType == JoinType.leftJoin) {
  98.             List<R1> r1Record = result.stream().map(Record::getR1).collect(Collectors.toList());
  99.             List<Record<R1R2>> leftAppendList = new ArrayList<>();
  100.             for (R1 r1 : table1) {
  101.                 if (!r1Record.contains(r1)) {
  102.                     leftAppendList.add(Record.build(r1, null));
  103.                 }
  104.             }
  105.             result.addAll(leftAppendList);
  106.         }
  107.         if (Objects.nonNull(whereFilter)) {
  108.             for (Record<R1R2> record : result) {
  109.                 if (!whereFilter.accept(record.r1, record.r2)) {
  110.                     result.remove(record);
  111.                 }
  112.             }
  113.         }
  114.         return result;
  115.     }
  116.     public static <R1R2List<Record<R1R2>> joinOn(R1 r1, List<R2> table2, Filter<R1, R2> onFilter) {
  117.         List<Record<R1R2>> result = new ArrayList<>();
  118.         for (R2 r2 : table2) {
  119.             if (Objects.nonNull(onFilter) ? onFilter.accept(r1, r2) : true) {
  120.                 result.add(Record.build(r1, r2));
  121.             }
  122.         }
  123.         return result;
  124.     }
  125.     @Test
  126.     public void innerJoin() {
  127.         List<Table1> table1 = Arrays.asList(Table1.build(1), Table1.build(2), Table1.build(3));
  128.         List<Table2> table2 = Arrays.asList(Table2.build(3), Table2.build(4), Table2.build(5));
  129.         join(table1, table2, JoinType.innerJoinnullnull).forEach(System.out::println);
  130.         System.out.println("-----------------");
  131.         join(table1, table2, JoinType.innerJoin, (r1, r2) -> r1.a == r2.bnull).forEach(System.out::println);
  132.     }
  133.     @Test
  134.     public void leftJoin() {
  135.         List<Table1> table1 = Arrays.asList(Table1.build(1), Table1.build(2), Table1.build(3));
  136.         List<Table2> table2 = Arrays.asList(Table2.build(3), Table2.build(4), Table2.build(5));
  137.         join(table1, table2, JoinType.leftJoin, (r1, r2) -> r1.a == r2.bnull).forEach(System.out::println);
  138.         System.out.println("-----------------");
  139.         join(table1, table2, JoinType.leftJoin, (r1, r2) -> r1.a > 10null).forEach(System.out::println);
  140.     }
  141. }

代码中的innerJoin()方法模拟了下面的sql:

  1. mysql> select * from test1 t1,test2 t2;
  2. +------+------+
  3. | a    | b    |
  4. +------+------+
  5. |    1 |    3 |
  6. |    2 |    3 |
  7. |    3 |    3 |
  8. |    1 |    4 |
  9. |    2 |    4 |
  10. |    3 |    4 |
  11. |    1 |    5 |
  12. |    2 |    5 |
  13. |    3 |    5 |
  14. +------+------+
  15. 9 rows in set (0.00 sec)
  16. mysql> select * from test1 t1,test2 t2 where t1.a = t2.b;
  17. +------+------+
  18. | a    | b    |
  19. +------+------+
  20. |    3 |    3 |
  21. +------+------+
  22. 1 row in set (0.00 sec)

运行一下innerJoin()输出如下:

  1. Record{r1=Table1{a=1}, r2=Table2{b=3}}
  2. Record{r1=Table1{a=1}, r2=Table2{b=4}}
  3. Record{r1=Table1{a=1}, r2=Table2{b=5}}
  4. Record{r1=Table1{a=2}, r2=Table2{b=3}}
  5. Record{r1=Table1{a=2}, r2=Table2{b=4}}
  6. Record{r1=Table1{a=2}, r2=Table2{b=5}}
  7. Record{r1=Table1{a=3}, r2=Table2{b=3}}
  8. Record{r1=Table1{a=3}, r2=Table2{b=4}}
  9. Record{r1=Table1{a=3}, r2=Table2{b=5}}
  10. -----------------
  11. Record{r1=Table1{a=3}, r2=Table2{b=3}}

对比一下sql和java的结果,输出的结果条数、数据基本上一致,唯一不同的是顺序上面不一样,顺序为何不一致,稍微介绍

代码中的leftJoin()方法模拟了下面的sql:

  1. mysql> select * from test1 t1 left join test2 t2 on t1.a = t2.b;
  2. +------+------+
  3. | a    | b    |
  4. +------+------+
  5. |    3 |    3 |
  6. |    1 | NULL |
  7. |    2 | NULL |
  8. +------+------+
  9. 3 rows in set (0.00 sec)
  10. mysql> select * from test1 t1 left join test2 t2 on t1.a>10;
  11. +------+------+
  12. | a    | b    |
  13. +------+------+
  14. |    1 | NULL |
  15. |    2 | NULL |
  16. |    3 | NULL |
  17. +------+------+
  18. 3 rows in set (0.00 sec)

运行leftJoin(),结果如下:

  1. Record{r1=Table1{a=3}, r2=Table2{b=3}}
  2. Record{r1=Table1{a=1}, r2=null}
  3. Record{r1=Table1{a=2}, r2=null}
  4. -----------------
  5. Record{r1=Table1{a=1}, r2=null}
  6. Record{r1=Table1{a=2}, r2=null}
  7. Record{r1=Table1{a=3}, r2=null}

效果和sql的效果完全一致,可以对上。

现在我们来讨论java输出的顺序为何和sql不一致?

上面java代码中两个表的连接查询使用了嵌套循环,外循环每执行一次,内循环的表都会全部遍历一次,如果放到mysql中,就相当于内标全部扫描了一次(一次全表io读取操作),主表(外循环)如果有n条数据,那么从表就需要全表扫描n次,表的数据是存储在磁盘中,每次全表扫描都需要做io操作,io操作是最耗时间的,如果mysql按照上面的java方式实现,那效率肯定很低。

那mysql是如何优化的呢?

msql内部使用了一个内存缓存空间,就叫他join_buffer吧,先把外循环的数据放到join_buffer中,然后对从表进行遍历,从表中取一条数据和join_buffer的数据进行比较,然后从表中再取第2条和join_buffer数据进行比较,直到从表遍历完成,使用这方方式来减少从表的io扫描次数,当join_buffer足够大的时候,大到可以存放主表所有数据,那么从表只需要全表扫描一次(即只需要一次全表io读取操作)。

mysql中这种方式叫做Block Nested Loop

java代码改进一下,来实现join_buffer的过程。

java代码改进版本

  1. package com.itsoku.sql;
  2. import org.junit.Test;
  3. import java.util.ArrayList;
  4. import java.util.Arrays;
  5. import java.util.List;
  6. import java.util.Objects;
  7. import java.util.concurrent.CopyOnWriteArrayList;
  8. import java.util.stream.Collectors;
  9. import com.itsoku.sql.Test1.*;
  10. public class Test2 {
  11.     public static int joinBufferSize = 10000;
  12.     public static List<?> joinBufferList = new ArrayList<>();
  13.     public static <R1R2List<Record<R1R2>> join(List<R1> table1, List<R2> table2, JoinType joinType, Filter<R1, R2> onFilter, Filter<R1, R2> whereFilter) {
  14.         if (Objects.isNull(table1) || Objects.isNull(table2) || joinType == null) {
  15.             return new ArrayList<>();
  16.         }
  17.         List<Test1.Record<R1R2>> result = new CopyOnWriteArrayList<>();
  18.         int table1Size = table1.size();
  19.         int fromIndex = 0, toIndex = joinBufferSize;
  20.         toIndex = Integer.min(table1Size, toIndex);
  21.         while (fromIndex < table1Size && toIndex <= table1Size) {
  22.             joinBufferList = table1.subList(fromIndex, toIndex);
  23.             fromIndex = toIndex;
  24.             toIndex += joinBufferSize;
  25.             toIndex = Integer.min(table1Size, toIndex);
  26.             List<Record<R1R2>> blockNestedLoopResult = blockNestedLoop((List<R1>) joinBufferList, table2, onFilter);
  27.             result.addAll(blockNestedLoopResult);
  28.         }
  29.         if (joinType == JoinType.leftJoin) {
  30.             List<R1> r1Record = result.stream().map(Record::getR1).collect(Collectors.toList());
  31.             List<Record<R1R2>> leftAppendList = new ArrayList<>();
  32.             for (R1 r1 : table1) {
  33.                 if (!r1Record.contains(r1)) {
  34.                     leftAppendList.add(Record.build(r1, null));
  35.                 }
  36.             }
  37.             result.addAll(leftAppendList);
  38.         }
  39.         if (Objects.nonNull(whereFilter)) {
  40.             for (Record<R1R2> record : result) {
  41.                 if (!whereFilter.accept(record.r1, record.r2)) {
  42.                     result.remove(record);
  43.                 }
  44.             }
  45.         }
  46.         return result;
  47.     }
  48.     public static <R1R2List<Record<R1R2>> blockNestedLoop(List<R1> joinBufferList, List<R2> table2, Filter<R1, R2> onFilter) {
  49.         List<Record<R1R2>> result = new ArrayList<>();
  50.         for (R2 r2 : table2) {
  51.             for (R1 r1 : joinBufferList) {
  52.                 if (Objects.nonNull(onFilter) ? onFilter.accept(r1, r2) : true) {
  53.                     result.add(Record.build(r1, r2));
  54.                 }
  55.             }
  56.         }
  57.         return result;
  58.     }
  59.     @Test
  60.     public void innerJoin() {
  61.         List<Table1> table1 = Arrays.asList(Table1.build(1), Table1.build(2), Table1.build(3));
  62.         List<Table2> table2 = Arrays.asList(Table2.build(3), Table2.build(4), Table2.build(5));
  63.         join(table1, table2, JoinType.innerJoinnullnull).forEach(System.out::println);
  64.         System.out.println("-----------------");
  65.         join(table1, table2, JoinType.innerJoin, (r1, r2) -> r1.a == r2.bnull).forEach(System.out::println);
  66.     }
  67.     @Test
  68.     public void leftJoin() {
  69.         List<Table1> table1 = Arrays.asList(Table1.build(1), Table1.build(2), Table1.build(3));
  70.         List<Table2> table2 = Arrays.asList(Table2.build(3), Table2.build(4), Table2.build(5));
  71.         join(table1, table2, JoinType.leftJoin, (r1, r2) -> r1.a == r2.bnull).forEach(System.out::println);
  72.         System.out.println("-----------------");
  73.         join(table1, table2, JoinType.leftJoin, (r1, r2) -> r1.a > 10null).forEach(System.out::println);
  74.     }
  75. }

执行innerJoin(),输出:

  1. Record{r1=Table1{a=1}, r2=Table2{b=3}}
  2. Record{r1=Table1{a=2}, r2=Table2{b=3}}
  3. Record{r1=Table1{a=3}, r2=Table2{b=3}}
  4. Record{r1=Table1{a=1}, r2=Table2{b=4}}
  5. Record{r1=Table1{a=2}, r2=Table2{b=4}}
  6. Record{r1=Table1{a=3}, r2=Table2{b=4}}
  7. Record{r1=Table1{a=1}, r2=Table2{b=5}}
  8. Record{r1=Table1{a=2}, r2=Table2{b=5}}
  9. Record{r1=Table1{a=3}, r2=Table2{b=5}}
  10. -----------------
  11. Record{r1=Table1{a=3}, r2=Table2{b=3}}

执行leftJoin(),输出:

  1. Record{r1=Table1{a=3}, r2=Table2{b=3}}
  2. Record{r1=Table1{a=1}, r2=null}
  3. Record{r1=Table1{a=2}, r2=null}
  4. -----------------
  5. Record{r1=Table1{a=1}, r2=null}
  6. Record{r1=Table1{a=2}, r2=null}
  7. Record{r1=Table1{a=3}, r2=null}

结果和sql的结果完全一致。

扩展

表连接中还可以使用前面学过的group byhavingorder bylimit

这些关键字相当于在表连接的结果上在进行操作,大家下去可以练习一下,加深理解。

Mysql系列目录

  1. 第1篇:mysql基础知识

  2. 第2篇:详解mysql数据类型(重点)

  3. 第3篇:管理员必备技能(必须掌握)

  4. 第4篇:DDL常见操作

  5. 第5篇:DML操作汇总(insert,update,delete)

  6. 第6篇:select查询基础篇

  7. 第7篇:玩转select条件查询,避免采坑

  8. 第8篇:详解排序和分页(order by & limit)

  9. 第9篇:分组查询详解(group by & having)

  10. 第10篇:常用的几十个函数详解

mysql系列大概有20多篇,喜欢的请关注一下,欢迎大家加我微信itsoku或者留言交流mysql相关技术!

来源:https://itsoku.blog.csdn.net/article/details/101047548