浅析Mysql Join语法以及性能优化
一.join语法概述
join 用于多表中字段之间的联系,语法如下:
... from table1 inner|left|right join table2 on conditiona
table1:左表;table2:右表。
join 按照功能大致分为如下三类:
inner join(内连接,或等值连接):取得两个表中存在连接匹配关系的记录。
left join(左连接):取得左表(table1)完全记录,即是右表(table2)并无对应匹配记录。
right join(右连接):与 left join 相反,取得右表(table2)完全记录,即是左表(table1)并无匹配对应记录。
注意:mysql不支持full join,不过可以通过union 关键字来合并 left join 与 right join来模拟full join.
接下来给出一个列子用于解释下面几种分类。如下两个表(a,b)
mysql> select a.id,a.name,b.name from a,b where a.id=b.id;
+----+-----------+-------------+
| id | name | name |
+----+-----------+-------------+
| 1 | pirate | rutabaga |
| 2 | monkey | pirate |
| 3 | ninja | darth vader |
| 4 | spaghetti | ninja |
+----+-----------+-------------+
4 rows in set (0.00 sec)
二.inner join
内连接,也叫等值连接,inner join产生同时符合a和b的一组数据。
mysql> select * from a inner join b on a.name = b.name;
+----+--------+----+--------+
| id | name | id | name |
+----+--------+----+--------+
| 1 | pirate | 2 | pirate |
| 3 | ninja | 4 | ninja |
+----+--------+----+--------+
三.left join
mysql> select * from a left join b on a.name = b.name;
#或者:select * from a left outer join b on a.name = b.name;
+----+-----------+------+--------+
| id | name | id | name |
+----+-----------+------+--------+
| 1 | pirate | 2 | pirate |
| 2 | monkey | null | null |
| 3 | ninja | 4 | ninja |
| 4 | spaghetti | null | null |
+----+-----------+------+--------+
4 rows in set (0.00 sec)
left join,(或left outer join:在mysql中两者等价,推荐使用left join.)左连接从左表(a)产生一套完整的记录,与匹配的记录(右表(b)) .如果没有匹配,右侧将包含null。
如果想只从左表(a)中产生一套记录,但不包含右表(b)的记录,可以通过设置where语句来执行,如下:
mysql> select * from a left join b on a.name=b.name where a.id is null or b.id is null;
+----+-----------+------+------+
| id | name | id | name |
+----+-----------+------+------+
| 2 | monkey | null | null |
| 4 | spaghetti | null | null |
+----+-----------+------+------+
2 rows in set (0.00 sec)
同理,还可以模拟inner join. 如下:
mysql> select * from a left join b on a.name=b.name where a.id is not null and b.id is not null;
+----+--------+------+--------+
| id | name | id | name |
+----+--------+------+--------+
| 1 | pirate | 2 | pirate |
| 3 | ninja | 4 | ninja |
+----+--------+------+--------+
2 rows in set (0.00 sec)
求差集:
根据上面的例子可以求差集,如下:
select * from a left join b on a.name = b.name
where b.id is null
union
select * from a right join b on a.name = b.name
where a.id is null;
# 结果
+------+-----------+------+-------------+
| id | name | id | name |
+------+-----------+------+-------------+
| 2 | monkey | null | null |
| 4 | spaghetti | null | null |
| null | null | 1 | rutabaga |
| null | null | 3 | darth vader |
+------+-----------+------+-------------+
四.right join
mysql> select * from a right join b on a.name = b.name;
+------+--------+----+-------------+
| id | name | id | name |
+------+--------+----+-------------+
| null | null | 1 | rutabaga |
| 1 | pirate | 2 | pirate |
| null | null | 3 | darth vader |
| 3 | ninja | 4 | ninja |
+------+--------+----+-------------+
4 rows in set (0.00 sec)
同left join。
五.cross join
cross join:交叉连接,得到的结果是两个表的乘积,即笛卡尔积
笛卡尔(descartes)乘积又叫直积。假设集合a={a,b},集合b={0,1,2},则两个集合的笛卡尔积为{(a,0),(a,1),(a,2),(b,0),(b,1), (b,2)}。可以扩展到多个集合的情况。类似的例子有,如果a表示某学校学生的集合,b表示该学校所有课程的集合,则a与b的笛卡尔积表示所有可能的选课情况。
mysql> select * from a cross join b;
+----+-----------+----+-------------+
| id | name | id | name |
+----+-----------+----+-------------+
| 1 | pirate | 1 | rutabaga |
| 2 | monkey | 1 | rutabaga |
| 3 | ninja | 1 | rutabaga |
| 4 | spaghetti | 1 | rutabaga |
| 1 | pirate | 2 | pirate |
| 2 | monkey | 2 | pirate |
| 3 | ninja | 2 | pirate |
| 4 | spaghetti | 2 | pirate |
| 1 | pirate | 3 | darth vader |
| 2 | monkey | 3 | darth vader |
| 3 | ninja | 3 | darth vader |
| 4 | spaghetti | 3 | darth vader |
| 1 | pirate | 4 | ninja |
| 2 | monkey | 4 | ninja |
| 3 | ninja | 4 | ninja |
| 4 | spaghetti | 4 | ninja |
+----+-----------+----+-------------+
16 rows in set (0.00 sec)
#再执行:mysql> select * from a inner join b; 试一试
#在执行mysql> select * from a cross join b on a.name = b.name; 试一试
实际上,在 mysql 中(仅限于 mysql) cross join 与 inner join 的表现是一样的,在不指定 on 条件得到的结果都是笛卡尔积,反之取得两个表完全匹配的结果。 inner join 与 cross join 可以省略 inner 或 cross 关键字,因此下面的 sql 效果是一样的:
... from table1 inner join table2
... from table1 cross join table2
... from table1 join table2
六.full join
mysql> select * from a left join b on b.name = a.name
-> union
-> select * from a right join b on b.name = a.name;
+------+-----------+------+-------------+
| id | name | id | name |
+------+-----------+------+-------------+
| 1 | pirate | 2 | pirate |
| 2 | monkey | null | null |
| 3 | ninja | 4 | ninja |
| 4 | spaghetti | null | null |
| null | null | 1 | rutabaga |
| null | null | 3 | darth vader |
+------+-----------+------+-------------+
6 rows in set (0.00 sec)
全连接产生的所有记录(双方匹配记录)在表a和表b。如果没有匹配,则对面将包含null。
七.性能优化
1.显示(explicit) inner join vs 隐式(implicit) inner join
如:
select * from
table a inner join table b
on a.id = b.id;
vs
select a.*, b.*
from table a, table b
where a.id = b.id;
我在数据库中比较(10w数据)得之,它们用时几乎相同,第一个是显示的inner join,后一个是隐式的inner join。
2.left join/right join vs inner join
尽量用inner join.避免 left join 和 null.
在使用left join(或right join)时,应该清楚的知道以下几点:
(1). on与 where的执行顺序
on 条件(“a left join b on 条件表达式”中的on)用来决定如何从 b 表中检索数据行。如果 b 表中没有任何一行数据匹配 on 的条件,将会额外生成一行所有列为 null 的数据,在匹配阶段 where 子句的条件都不会被使用。仅在匹配阶段完成以后,where 子句条件才会被使用。它将从匹配阶段产生的数据中检索过滤。
所以我们要注意:在使用left (right) join的时候,一定要在先给出尽可能多的匹配满足条件,减少where的执行。如:
pass
select * from a
inner join b on b.name = a.name
left join c on c.name = b.name
left join d on d.id = c.id
where c.status>1 and d.status=1;
great
select * from a
inner join b on b.name = a.name
left join c on c.name = b.name and c.status>1
left join d on d.id = c.id and d.status=1
从上面例子可以看出,尽可能满足on的条件,而少用where的条件。从执行性能来看第二个显然更加省时。
(2).注意on 子句和 where 子句的不同
如作者举了一个列子:
mysql> select * from product left join product_details
on (product.id = product_details.id)
and product_details.id=2;
+----+--------+------+--------+-------+
| id | amount | id | weight | exist |
+----+--------+------+--------+-------+
| 1 | 100 | null | null | null |
| 2 | 200 | 2 | 22 | 0 |
| 3 | 300 | null | null | null |
| 4 | 400 | null | null | null |
+----+--------+------+--------+-------+
4 rows in set (0.00 sec)
mysql> select * from product left join product_details
on (product.id = product_details.id)
where product_details.id=2;
+----+--------+----+--------+-------+
| id | amount | id | weight | exist |
+----+--------+----+--------+-------+
| 2 | 200 | 2 | 22 | 0 |
+----+--------+----+--------+-------+
1 row in set (0.01 sec)
从上可知,第一条查询使用 on 条件决定了从 left join的 product_details表中检索符合的所有数据行。第二条查询做了简单的left join,然后使用 where 子句从 left join的数据中过滤掉不符合条件的数据行。
(3).尽量避免子查询,而用join
往往性能这玩意儿,更多时候体现在数据量比较大的时候,此时,我们应该避免复杂的子查询。如下:
pass
insert into t1(a1) select b1 from t2 where not exists(select 1 from t1 where t1.id = t2.r_id);
great
insert into t1(a1)
select b1 from t2
left join (select distinct t1.id from t1 ) t1 on t1.id = t2.r_id
where t1.id is null;
推荐阅读
-
浅析Mysql Join语法以及性能优化
-
Mysql Join语法解析与性能分析详解_MySQL
-
Mysql Join语法解析与性能分析详解_MySQL
-
MySQL查询语句执行过程及性能优化-查询过程及优化方法(JOIN/ORD_MySQL
-
MySQL查询语句执行过程及性能优化-查询过程及优化方法(JOIN/ORD_MySQL
-
MySQL性能优化之Block Nested-Loop Join(BNL)
-
MySQL性能优化之Block Nested-Loop Join(BNL)
-
mysql分解连接的小结(来自于高性能MySQL以及自己网站性能优化)
-
mysql:21个性能优化最佳实践之5[在Join表的时候使用相当类型的_MySQL
-
mysql分解连接的小结(来自于高性能MySQL以及自己网站性能优化)