SQL——联接分类和本质

版权声明:本文为博主原创文章,未经博主允许不得转载。 https://blog.csdn.net/mzl87/article/details/84697582

目录

1、嵌套循环联接

2、合并联接

3、哈希联接


联接(Join)是将两个表合并为一个表的操作。SOL的联接分为外联接、内联接和交叉联接。本文将主要通过查询执行计划的角度从本质上讲解联接。

1、嵌套循环联接

嵌套循环联接(Nested Loop Join)也称为“嵌套选代”,它将一个联接输入用作外部输入表(显示为图形执行计划中的顶端输入),将另一个联接输入用作内部(底端)输入表。外部循环逐行处理外部输入表。内部循环会针对每个外部行执行,在内部输入表中搜索匹配行。

最简单的情况是,搜索时扫描整个表或索引,这称为“单纯嵌套循环联接”。如果搜索时使用索引,则称为“索引嵌套循环联接”。如果将索引生成为查询计划的一部分(并在查询完成后立即将索引破坏),则称为“临时索引套循环联接”。查询优化器考虑了所有这些不同的情况。

如果外部输入较小而内部输入较大,且预先创建了案引,则使用嵌套循环联接尤其有效,在许多小事务中(如那些只影响较小的一组行的事务),索引嵌套循环联接优于合并联接和哈希联接。但在大型查询中,嵌套循环联接通常不是最佳选择。

在程序中可以理解为嵌套的for循环语句,先是对外部输入表循环找到每行数据,再使用循环对内部输入表的每行数据与外部输入表的数据进行匹配,直到这2个循环都完成。例如创建班级表和学生表,然后对这两个表进行内联接,则系统将采用嵌套循环联接对这两个表进行处理。如下:

IF OBJECT_ID('Class') IS NOT NULL
    DROP TABLE Class;
GO

CREATE TABLE Class
(
    CID INT NOT NULL PRIMARY KEY,
    CName VARCHAR(10) NOT NULL
);
GO

IF OBJECT_ID('Student') IS NOT NULL
    DROP TABLE Student;
GO

CREATE TABLE Student
(
    SID INT IDENTITY PRIMARY KEY,
    CID INT NOT NULL,
    SName VARCHAR(10) NOT NULL,
    CONSTRAINT FK_Student_Class FOREIGN KEY(CID) REFERENCES Class(CID)
);
GO

--插入测试数据
INSERT INTO Class VALUES
(1,'01'),
(2,'02');

INSERT INTO Student(CID,SName) VALUES
(1,'s11'),
(1,'s12'),
(2,'s21'),
(2,'s22'),
(2,'s23');

内联接查询这2个表,同时返回查询的执行计划,查询代码如下:

--内联接
SELECT *
FROM Student AS S
INNER JOIN Class AS C
ON S.CID = C.CID;

 

执行计划:

 

2、合并联接

合并联接(Merge Join)要求两个输入都在合并列上排序,其由联接谓词的等效(ON)子句定义。通常,利用査询优化器扫描索引(如果在适当的一组列上存在索引),或在合并联接的下面放一个排序运算符。在极少数情况下,虽然可能有多个等效子句,但只用其中一些可用的等效子句获得合并列。

由于每个输入都已排序,因此 Merge Join运算符将从每个输入获取一行并将其进行比较。例如,对于内联接操作( INNER.JON),如果获取的行相等则返回该行。如果行不相等,则抛弃值较小的行并从该输入获得另一行进行比较。这一过程将重复进行,直到处理完所有的行为止。

合并联接操作可以是常规操作,也可以是多对多操作。多对多合并联接使用临时表存储行。如果每个输入中有重复值,则在处理其中一个输入中的每个重复项时,另一个输入必须重绕到重复项的开始位置。

如果存在驻留谓词,则所有满足合并谓词的行都将对该驻留谓词取值,而只返回那些满足该驻留谓词的行。

合并联接本身的速度很快,但如果需要执行排序操作,选择合并联接就会非常费时。然而,如果数据量很大且能够从现有B树索引中获得预排序的所需数据,则合并联接通常是最快的可用联接算法。例如仍然使用前面创建的班级和学生表,由于合并联接在两表数据量并不小而且联接列已排序的情况下发生,所以需要向表中添加大量数据,同时还要为进行联接的列CID排序,具体SQL代码如下:

SET NOCOUNT ON
--创建大量的测试数据
DECLARE @i INT =3
WHILE @i <1000
BEGIN
    INSERT INTO Class VALUES(@i,'01')
    DECLARE @j INT  =0
    WHILE @j<10
    BEGIN
         INSERT INTO Student(CID,SName) VALUES
         (@i,'s'+CONVERT(VARCHAR(5),@i))
         SET @j += 1
    END
    SET @i += 1
END
GO

--创建索引
CREATE INDEX IX_Student_CID
ON Student(CID) INCLUDE(SID,SName);

 

接下来运行两个表的联接查询(上面的查询语句),可以看到执行计划中使用了合并联接:

 

3、哈希联接

哈希联接(Hash Join)有两种输入,即生成输入和探测输入。如果两个联接输入都大,而且大小差不多,则预先排序的合并联接提供的性能与哈希联接相近。但是,如果这两个输入的大小相差很大,则哈希联接操作通常快得多。

哈希联接先扫描或计算整个生成输入,然后在内存中生成哈希表。根据计算得出的希键的哈希值,将每行插入哈希存储桶。如果整个生成输入小于可用内存,则可以将所有行都插入哈希表中。生成阶段之后是探测阶段。一次一行地对整个探测输入进行扫描或计算,并为每个探测行计算哈希键的值,扫描相应的哈希存储桶并生成匹配项。

哈希联接一般在一张小表和一张大表进行联接时应用。例如对于班级表和学生表,明显班级表要比学生表小很多,在去掉学生表上对CID的排序后,这两个表在进行联接运算时将使用哈希联接。详细代码如下:

--删除索引
DROP INDEX IX_Student_CID ON Student;
GO

--执行查询
SELECT *
FROM Student AS S
INNER JOIN Class AS C
ON S.CID = C.CID

执行计划如下:

 

PS:可以通过上面的执行计划比较,看出各个联接的用处,这也在性能优化上提供了一种参考     

猜你喜欢

转载自blog.csdn.net/mzl87/article/details/84697582
今日推荐