MySQL知识总结
《MySQL是怎样运行的》知识总结
11 连接原理
表的结构
create table t1
(
m1 int,
n1 char(1)
);
create table t2
(
m2 int,
n2 char(1)
);
insert into t1
values (1, 'a'),
(2, 'b'),
(3, 'c');
insert into t2
values (2, 'b'),
(3, 'c'),
(4, 'd');
两表连接
select *
from t1,
t2;
连接就是把各表的记录取出来,依次进行匹配,把
匹配的组合
发送给客户端,上面的语句就是把t1、t2表中的记录连接起来,组成一个更大的记录。连接查询的结果包含了t1、t2各表中的
每一条记录互相匹配组合
,这个结果集就被成为笛卡尔积
。我们可以连接任意数量的表,如果不附加任何限制条件,表连接起来的笛卡尔积可能非常巨大,所以连接时进行过滤是有必要的。
select *
from t1,
t2
where t1.m1 > 1
and t2.n2 < 'd'
and t1.m1 = t2.m2;
这个连接查询有3个条件,查询的过程大致如下:
- 确定第一个需要查询的表,进行
单表查询
,这个表成为驱动表
- 从驱动表中每获取一条记录,都需要到t2表中查找
匹配记录(符合过滤条件的记录)
,这里的过滤条件是t1.m1 = t2.m2
,这时t2表被称为被驱动表
,步骤1中查询t1表得到的第一记录中m1 = 2
,那么连接的过滤条件是t2.m2 = 2
,然后再根据条件进行t2表的单表查询
这个例子中,驱动表需要查询1次,被驱动表需要查询2次
每一次从驱动表中获取了记录就会去被驱动表查询匹配的数据
内外连接
-
内连接:对于内连接的两个表,若驱动表中的记录在被驱动表中找不到匹配的记录,就
不加入到最终的结果集
-
外连接:即使被驱动表中没有匹配的记录,也加入最终结果集
-
左外连接:选取左表为驱动表(右连接则相反)
-
连接条件
- where:不符合where子句的过滤条件的记录不会被加入结果集中
- on:对于外连接,如果无法在被驱动表中找到符合on子句过滤条件的记录,这些记录也会被加入到结果集
在内连接中,on、where子句是等价的
对于外连接,驱动表、被驱动表的关系很重要,因为驱动表的记录即使在被驱动表中找不到匹配的记录,也会被加入到结果集中
嵌套循环连接
两表连接中,驱动表只会查询1次,被驱动表可能会查询多次(取决于对驱动表查询后的记录数量),这个过程像是一个嵌套循环
。
对于嵌套循环连接算法,每一次从驱动表中查询到记录就立刻到被驱动表中进行匹配查询,如果得到了匹配的记录就把组合返回给客户端,
“结果集”是一个抽象的概念
。
索引加快连接速度
explain
select *
from t1,
t2
where t1.m1 > 1
and t2.n2 > 'a'
and t1.m1 = t2.m2;
- 在t2表中
m2列上建立索引
,针对m2列的条件进行等值查询,可能
用到ref访问方法,对t2进行查询后,还需要在回表后再判断t2.n2 > 'a'
- 如果在m2上建立主键或不允许为null的唯一索引,那么访问方法是
eq_ref
- 在t2表中
n2列上建立索引
,对于条件t2.n2 > 'a'
,可能有用到的访问方法是range,如果使用range访问方法,那么还需要在回表后判断t2表中的m2列是否成立。- 如果t2表上在m2、n2上有索引,那就需要挑选一个成本较低的索引进行查询。
使用索引
的情况下,连接查询的查询列表
、查询条件
可能只涉及了被驱动表的部分列
,这些列又全是索引的一部分,那么这种情况下,最坏的情况也可以使用index的访问方法,所以最好不使用*作为查询列表。
基于块的嵌套循环查询
现实生活中的数据记录是非常多的,假设不能使用索引进行查询
,对于驱动表结果集的每一条记录,都需要对被驱动表进行全表扫描。这会导致IO的代价非常大(比如表的数据非常多,而内存不足,表的记录一部分在内存,一部分在磁盘,这就需要将内存中的数据释放后,再将磁盘的数据加载到内存)。
MySQL引入了连接缓冲区的概念,执行连接查询时,会在内存申请一块固定大小
的内存,再把若干条驱动表
的结果集记录
加载到缓冲区中,然后查询被驱动表
,每一条被驱动表的记录都与缓冲区的多条记录进行匹配,匹配是在内存中完成的,这显著减少了被驱动表的IO代价。
最好的情况就是,缓冲区的大小能容纳所有的驱动表的结果集记录,这样只需要访问一次被驱动表就可以完成连接操作。可以通过启动选项、系统变量join_buffer_size
进行配置,默认大小是256k。
缓冲区不会存放驱动表的记录的所有列,只有查询列表的列、过滤条件会被放入缓冲区,所以查询列表尽量不使用*。