哈希联接与嵌套循环

Posted

技术标签:

【中文标题】哈希联接与嵌套循环【英文标题】:Hash Join vs. Nested Loop 【发布时间】:2018-01-15 15:12:44 【问题描述】:

我是 SQL 新手,我有一个问题。

我有这个 SQL 代码:

DROP TABLE if exists students;
DROP TABLE if exists grades;

CREATE TABLE students(
    s_id integer NOT NULL PRIMARY KEY,
    s_name text,
    s_last_name text,
    curr_year integer
);

CREATE TABLE grades(
    s_id integer NOT NULL PRIMARY KEY,
    course text,
    c_year integer,
    grade integer,
    FOREIGN KEY (s_id) REFERENCES students
);

INSERT INTO students (s_id, s_name, s_last_name, curr_year)
VALUES (1, 'A', 'S', 3);

INSERT INTO students (s_id, s_name, s_last_name, curr_year)
VALUES (2, 'A', 'A', 2);

INSERT INTO students (s_id, s_name, s_last_name, curr_year)
VALUES (3, 'V', 'B', 1);

INSERT INTO students (s_id, s_name, s_last_name, curr_year)
VALUES (4, 'K', 'N', 2);

INSERT INTO grades (s_id, course, c_year, grade)
VALUES (1, 'DB', 2, 98);

INSERT INTO grades (s_id, course, c_year, grade)
VALUES (2, 'OS', 3, 90);

INSERT INTO grades (s_id, course, c_year, grade)
VALUES (3, 'DB', 2, 94);

EXPLAIN ANALYZE
    SELECT * 
    FROM students s JOIN grades gr
    ON s.s_id = gr.s_id
    WHERE curr_year > 0;



CREATE INDEX student_details ON students(s_id, s_name, s_last_name);
CREATE INDEX student_courses ON grades(s_id, course);

EXPLAIN ANALYZE
    SELECT * 
    FROM students s JOIN grades gr
    ON s.s_id = gr.s_id
    WHERE curr_year > 0;


DROP INDEX student_details;
DROP INDEX student_courses;
DROP TABLE students CASCADE;
DROP TABLE grades CASCADE;

我尝试理解解释输出。 在创建索引之前,我得到了哈希合并。这是解释输出:

 Hash Join  (cost=23.50..51.74 rows=270 width=116) (actual time=0.039..0.050 rows=3 loops=1)
   Hash Cond: (gr.s_id = s.s_id)
   ->  Seq Scan on grades gr  (cost=0.00..21.30 rows=1130 width=44) (actual time=0.005..0.008 rows=3 loops=1)
   ->  Hash  (cost=20.12..20.12 rows=270 width=72) (actual time=0.021..0.021 rows=4 loops=1)
         Buckets: 1024  Batches: 1  Memory Usage: 9kB
         ->  Seq Scan on students s  (cost=0.00..20.12 rows=270 width=72) (actual time=0.006..0.011 rows=4 loops=1)
               Filter: (curr_year > 0)
 Planning time: 0.147 ms
 Execution time: 0.089 ms
(9 rows)

在创建索引后,我得到了嵌套循环:

Nested Loop  (cost=0.00..2.12 rows=1 width=116) (actual time=0.031..0.116 rows=3 loops=1)
   Join Filter: (s.s_id = gr.s_id)
   Rows Removed by Join Filter: 9
   ->  Seq Scan on students s  (cost=0.00..1.05 rows=1 width=72) (actual time=0.012..0.018 rows=4 loops=1)
         Filter: (curr_year > 0)
   ->  Seq Scan on grades gr  (cost=0.00..1.03 rows=3 width=44) (actual time=0.003..0.009 rows=3 loops=4)
 Planning time: 0.396 ms
 Execution time: 0.170 ms

但我不知道为什么?为什么我的情况下的索引使嵌套循环比散列连接更受欢迎? 我会很高兴得到解释。

非常感谢!

【问题讨论】:

对于只有少量记录的表,优化根本没有意义。提示:计划者已经花费了比执行计划更多的时间来生成计划。 如果这是基于真实世界的示例,那么这些索引毫无意义。两个表中的sid 上已经有一个聚集索引,因此在索引的开头包含此列将使索引无用。 【参考方案1】:

“嵌套循环”连接有点用词不当。从技术上讲,它指的是嵌套循环——顾名思义:

for every row in table1
    for every row in table2
        compare values and execute join

在实践中,这通常是这样的:

for every row in table1
    for every matching row in table2
        execute join

区别很细微,但“匹配”意味着嵌套循环连接可以使用索引。因此,嵌套循环连接的性能可能非常差(如果表相对较大且没有索引),或者它可能具有非常好的性能(如果它可以利用索引)。

【讨论】:

【参考方案2】:

Glib 回答:因为查询规划器认为它更快。

最佳猜测:当您拥有索引时,查询计划器可以使用它从索引中读取数据的顺序来执行嵌套循环,而无需排序,比散列更快。没有索引它会做排序,排序+循环的组合比哈希慢。

【讨论】:

【参考方案3】:

似乎没有人回答这个问题。 这两个执行计划都没有使用索引,那么为什么当您创建索引时,计划程序会从使用 HJ 更改为使用 NL。 我的猜测是,在创建索引之后,可以使用比以前更好的表和列统计信息。我不知道创建索引是否会导致填充表统计信息,或者规划器是否使用索引上的统计信息来选择路径。跟踪计划者并找出答案会很有趣。我是甲骨文的人,可以在那里做。我不知道如何在 PostgreSQL 上进行分析。

例如,如果您查看

上的顺序扫描

学生 HJ 行数估计为 1130 NL 行估计为 1 实际行数为 4

成绩 HJ 行估计为 270 NL 行估计为 3 实际行数为 3

NL 统计数据更准确,因此在您创建索引后,统计数据发生了一些变化。 快速测试是删除索引,看看规划器做了什么。如果它仍然使用相同的统计数据进行 NL,则它必须是表上统计数据的变化。如果它以 HJ 估计返回 HJ,则它必须是索引上的统计数据。

变得更奇怪了。 所以我删除了索引,它继续使用 NL。很好,有道理。创建索引必须具有更新的表统计信息。 所以我删除表,重新创建,然后不是创建索引,而是分析表。 猜猜看,它使用HJ 但是...统计数据是准确的,就像 NL 一样。 更多问题... 猜测创建索引必须做一些不同于简单运行分析的统计分析。

【讨论】:

如果我记得正确创建索引会隐式创建列统计信息。当指数下降时,这些不会消失,并且很容易改变 CBO 评估。

以上是关于哈希联接与嵌套循环的主要内容,如果未能解决你的问题,请参考以下文章

Postgres 哈希连接与嵌套循环决策

数据库(比如MYSQL) ,表连结查询与子查询哪个效率高些? 为啥

联接 (SQL Server)

SQL Server 2008 中嵌套循环连接和哈希连接的区别

Nested-Loop Join Algorithms

无限循环与嵌套循环