隐式内部连接 - 它们是不是相等?
Posted
技术标签:
【中文标题】隐式内部连接 - 它们是不是相等?【英文标题】:implicit inner joins - are they equal?隐式内部连接 - 它们是否相等? 【发布时间】:2020-01-26 18:52:29 【问题描述】:在我看来,这两个 SELECT 是完全相等的(但我想将第一个重写为第二个,以在我的情况下帮助优化器)——无论表 a、b、c、d 中的任何数据,两个 SELECT 都将产生完全相同的相同的结果。你同意吗?谢谢!
create table a (id number);
create table b (id number);
create table c (id number);
create table d (id number);
--Q1
select * from a,b,c,d
where a.id = b.id
and a.id = c.id
and a.id = d.id;
--Q2
select * from a,b,c,d
where a.id = b.id
and a.id = c.id
and a.id = d.id
-- Q2 differs from Q1 only in the next 3 lines
and b.id = c.id
and b.id = d.id
and c.id = d.id;
【问题讨论】:
从不在FROM
子句中使用逗号。 始终使用正确、明确、标准 JOIN
语法。期间。
+1 表示 Gordon 的评论 .comas 产生了所有 4 个表的笛卡尔积,无缘无故地导致了糟糕的性能。使用join
。
是的,这些都是等价的。您正在加入同一 id
列上的所有表。传递属性:a=b, b=c --> a=c
@Bonzay,逗号连接经过优化,可以执行与显式连接完全相同的操作。但是,仍然糟糕的编程。现代、明确的JOIN
语法更易于编写(没有错误)、更易于阅读(和维护),并且在需要时更易于转换为外连接。
我认为任何现代优化器都不会为这两个查询生成不同的计划。您使用的是哪种 DBMS 产品?
【参考方案1】:
我将解决这些不平等是否总是正确的问题。答案是“不”,不是在 SQL 中。在大多数情况下,它们是等价的。隐式类型转换会出现问题。
特别是,如果a.id
是一个数字而其他列是字符串,那么您会遇到以下情况:
1 = '1' -- true
1 = '1.00' -- true
'1' = '1.00' -- false
你可以在这个 dbfiddle 上看到 this。使用JOIN
s 进行设置很简单,但由于我不会编写在FROM
子句中包含逗号的代码,所以我将把这个练习留给你。
实际上,用于连接的 id 应该是相同的类型。如果不是,您甚至不能声明外键关系。除了最佳实践之外,这两个查询不会自动等效。
注意:如果您使用正确、明确、标准 JOIN
语法,这同样适用,我强烈建议您专门学习和使用。
【讨论】:
非常感谢您确认当 id 属于同一类型时两个查询是相等的,也感谢您对类型转换以及它如何使查询不同的非常有趣的注释。是的,我用标准语法编写查询。在这里,我正在重写一个旧的生产查询,我想做最小的更改。因此,对于我的示例,我还使用了我继承的代码使用的旧语法,因为在旧语法中它可能与新语法不同。如果您只想添加条件而不重写旧的怪物查询,请称我为懦夫 :-)【参考方案2】:是的,结果将完全相同。从代数的角度考虑,如果 a = b 且 b = c,则 a = c。这意味着第二个查询中的最后三个条件是多余的。
我会说最好还是坚持原来的查询。充其量,查询优化器将需要执行更多的比较。
【讨论】:
你可以在这里玩和测试:dbfiddle.uk/…【参考方案3】:人们会期望 Oracle 应用 transitive closure 的规则并使用您提供的信息来证明多余的谓词是不必要的 (and it often does so, as I've shown in this blog post),但它似乎并没有这样做案例(没有约束或索引)。获取这两个查询的执行计划:
SELECT s.sql_id, p.*
FROM v$sql s, TABLE (
dbms_xplan.display_cursor (
s.sql_id, s.child_number, 'ALLSTATS LAST'
)
) p
WHERE s.sql_text LIKE '%a,b,c,d%'
产量:
SQL_ID d54mttn9psd29, child number 0
-------------------------------------
--Q2
select * from a,b,c,d
where a.id = b.id
and a.id = c.id
and
a.id = d.id
-- Q2 differs from Q1 only in the next 3 lines
and b.id = c.id
and b.id = d.id
and c.id = d.id
Plan hash value: 3564259801
-------------------------------------------------------------------------
| Id | Operation | Name | E-Rows | OMem | 1Mem | Used-Mem |
-------------------------------------------------------------------------
| 0 | SELECT STATEMENT | | | | | |
|* 1 | HASH JOIN | | 1 | 1068K| 1068K| 240K (0)|
|* 2 | HASH JOIN | | 1 | 1209K| 1209K| 505K (0)|
|* 3 | HASH JOIN | | 1 | 1506K| 1506K| 461K (0)|
| 4 | TABLE ACCESS FULL| A | 1 | | | |
| 5 | TABLE ACCESS FULL| B | 1 | | | |
| 6 | TABLE ACCESS FULL | C | 1 | | | |
| 7 | TABLE ACCESS FULL | D | 1 | | | |
-------------------------------------------------------------------------
SQL_ID 3zzwv0z5tq84f, child number 0
-------------------------------------
--Q1
select * from a,b,c,d
where a.id = b.id
and a.id = c.id
and
a.id = d.id
Plan hash value: 255250992
---------------------------------------------------------------------------
| Id | Operation | Name | E-Rows | OMem | 1Mem | Used-Mem |
---------------------------------------------------------------------------
| 0 | SELECT STATEMENT | | | | | |
|* 1 | HASH JOIN | | 1 | 1068K| 1068K| 178K (0)|
| 2 | MERGE JOIN CARTESIAN | | 1 | | | |
| 3 | MERGE JOIN CARTESIAN| | 1 | | | |
| 4 | TABLE ACCESS FULL | B | 1 | | | |
| 5 | BUFFER SORT | | 1 | 73728 | 73728 | |
| 6 | TABLE ACCESS FULL | C | 1 | | | |
| 7 | BUFFER SORT | | 1 | 73728 | 73728 | |
| 8 | TABLE ACCESS FULL | D | 1 | | | |
| 9 | TABLE ACCESS FULL | A | 1 | | | |
---------------------------------------------------------------------------
这令人惊讶。在没有对空表计算统计信息的情况下,E-Rows 值是正确的,但内存估计值相差甚远,并且两个查询的内存估计值不同。
这并不意味着一种方法总是比另一种更好甚至不同,但它可能。例如。当我将实际数据添加到表中并计算统计数据时:
INSERT INTO a SELECT LEVEL FROM dual CONNECT BY LEVEL <= 10000;
INSERT INTO b SELECT LEVEL FROM dual CONNECT BY LEVEL <= 10000;
INSERT INTO c SELECT LEVEL FROM dual CONNECT BY LEVEL <= 10000;
INSERT INTO d SELECT LEVEL FROM dual CONNECT BY LEVEL <= 10000;
BEGIN
DBMS_STATS.GATHER_TABLE_STATS('TEST', 'A');
DBMS_STATS.GATHER_TABLE_STATS('TEST', 'B');
DBMS_STATS.GATHER_TABLE_STATS('TEST', 'C');
DBMS_STATS.GATHER_TABLE_STATS('TEST', 'D');
END;
然后,我得到了与哈希连接相同的执行计划,而不是合并连接:
SQL_ID d54mttn9psd29, child number 0
-------------------------------------
--Q2
select * from a,b,c,d
where a.id = b.id
and a.id = c.id
and
a.id = d.id
-- Q2 differs from Q1 only in the next 3 lines
and b.id = c.id
and b.id = d.id
and c.id = d.id
Plan hash value: 2782485219
-------------------------------------------------------------------------
| Id | Operation | Name | E-Rows | OMem | 1Mem | Used-Mem |
-------------------------------------------------------------------------
| 0 | SELECT STATEMENT | | | | | |
|* 1 | HASH JOIN | | 1 | 1836K| 1836K| 2032K (0)|
|* 2 | HASH JOIN | | 1 | 2161K| 2161K| 2145K (0)|
| 3 | TABLE ACCESS FULL | C | 10000 | | | |
|* 4 | HASH JOIN | | 10000 | 2546K| 2546K| 2211K (0)|
| 5 | TABLE ACCESS FULL| A | 10000 | | | |
| 6 | TABLE ACCESS FULL| B | 10000 | | | |
| 7 | TABLE ACCESS FULL | D | 10000 | | | |
-------------------------------------------------------------------------
SQL_ID 3zzwv0z5tq84f, child number 0
-------------------------------------
--Q1
select * from a,b,c,d
where a.id = b.id
and a.id = c.id
and
a.id = d.id
Plan hash value: 388154631
-------------------------------------------------------------------------
| Id | Operation | Name | E-Rows | OMem | 1Mem | Used-Mem |
-------------------------------------------------------------------------
| 0 | SELECT STATEMENT | | | | | |
|* 1 | HASH JOIN | | 10000 | 2546K| 2546K| 2099K (0)|
| 2 | TABLE ACCESS FULL | D | 10000 | | | |
|* 3 | HASH JOIN | | 10000 | 2546K| 2546K| 2146K (0)|
| 4 | TABLE ACCESS FULL | C | 10000 | | | |
|* 5 | HASH JOIN | | 10000 | 2546K| 2546K| 2211K (0)|
| 6 | TABLE ACCESS FULL| A | 10000 | | | |
| 7 | TABLE ACCESS FULL| B | 10000 | | | |
-------------------------------------------------------------------------
这里的学习和往常一样。不要相信你的直觉。不要相信互联网上的任何旧博客文章。查看执行计划并进行衡量。
我为此使用了 Oracle Database 18c Express Edition。
【讨论】:
以上是关于隐式内部连接 - 它们是不是相等?的主要内容,如果未能解决你的问题,请参考以下文章