这是两个返回相同结果集的查询,但这是最佳语句还是不重要?
SELECT A.id, B.somefield FROM (
SELECT id from table1
UNION
SELECT id from table2
) A LEFT JOIN table3 B on A.id = B.id
或
SELECT A.id, B.somefield FROM table1 A LEFT JOIN table3 B on A.id = B.id
UNION
SELECT A.id, B.somefield FROM table2 B LEFT JOIN table3 B on A.id = B.id
我意识到我可以为他们提供充足的数据并运行一些测试,但是如果一个人更快,我对'为什么'感兴趣? (我正在使用postgresql,以防它影响事物)。
感谢。
答案 0 :(得分:3)
使用UNION
的执行计划首先显示的步骤少得多,不幸的是执行计划并不是一切,还有表扫描,逻辑读取,CPU使用情况,所以这不是全部,而是结束所有这些它在很大程度上取决于您的数据和您的指标。
使用dupes时,第一个查询应该执行得更好,因为UNION
删除重复项会在连接之前发生,从而导致表3上的表扫描数量较少。如果table1和table2中没有重复项应该没有区别。
这可以通过一些样本数据来证明。我的所有示例都使用了下面的5个表(T4和T5只是将输出转储到所有内容中,因此您无需在SQL小提示页面上向下滚动数英里以查看执行计划)
CREATE TABLE T1 (ID INT NOT NULL);
CREATE TABLE T2 (ID INT NOT NULL);
CREATE TABLE T3 (FK INT NOT NULL, SomeValue VARCHAR(10) NOT NULL);
CREATE TABLE T4 (ID INT NOT NULL, SomeValue VARCHAR(10) NULL);
CREATE TABLE T5 (ID INT NOT NULL, SomeValue VARCHAR(10) NULL);
并且所有人都使用以下内容进行测试(反过来也是为了使任何查询计划缓存更加灵活):
INSERT INTO T4
SELECT ID, SomeValue
FROM T1
LEFT JOIN T3
ON ID = FK
UNION
SELECT ID, SomeValue
FROM T2
LEFT JOIN T3
ON ID = FK;
INSERT INTO T5
SELECT ID, SomeValue
FROM ( SELECT ID
FROM T1
UNION
SELECT ID
FROM T2
) T
LEFT JOIN T3
ON ID = FK;
示例1 - T1包含同样位于T2
的行INSERT INTO T1 (ID)
SELECT *
FROM GENERATE_SERIES(0, 40000);
INSERT INTO T2 (ID)
SELECT *
FROM GENERATE_SERIES(20000, 60000);
INSERT INTO T3 (FK, SomeValue)
SELECT *, 'VALUE'
FROM GENERATE_SERIES(10000, 50000);
Example on SQL Fiddle表示在UNION
之前插入到T4(JOIN
)效果更好。我已经运行了25次,插入到T4,在这22次中运行得更快。没有足够的数据可以从等式中删除服务器负载,因此可以预期存在一些异常现象。插入顺序在this example中反转,再次看到类似的结果。
示例2 - table1和table2中没有重复
INSERT INTO T1 (ID)
SELECT *
FROM GENERATE_SERIES(0, 30000);
INSERT INTO T2 (ID)
SELECT *
FROM GENERATE_SERIES(30001, 60000);
INSERT INTO T3 (FK, SomeValue)
SELECT *, 'VALUE'
FROM GENERATE_SERIES(10000, 50000);
在此示例中,执行时间彼此更接近,并且经常在哪种方法执行得更快之间切换。
最后,重申已经提出的观点,如果你不期待欺骗/不关心欺骗,那么UNION ALL
将提高性能,但由于没有欺骗,性能应该与这两种方法都应该同等地改进两种方法。我没有对此进行过测试,但改变我用来检查的测试数据应该不是一件大事。
修改强>
我刚刚在SQL Fiddle上尝试了这些查询,它们显示的差异比我们在本地计算机上的差异要大得多,所以请将这些示例放在一些盐上并在您自己的服务器上进行测试,这样做要容易得多。创造一个公平的测试环境!
答案 1 :(得分:2)
好的,首先,选择列表中的id
不明确;我们想要A.id
还是B.id
?
其次,假设id是所有表中的索引字段,重复数据删除和连接都是NlogM操作,其中N是“左”侧的行数,M是“右”侧的行数。对于N中的每一行,必须找到或找不到M中的匹配行(当连接时,在M中找到的行包含在结果中;当联合时,在M中找到的行被排除在外)。这意味着最小化左侧基数将带来最佳表现。
因此,任一查询的复杂性在很大程度上取决于表1和表2之间有多少共享ID。零共性(没有行ID相同)和每个表100行,第一个查询将执行一个100log100 union然后是200log100连接,第二个查询将执行两个100log100连接,然后执行一个100log100联合,它将在相同的时间内执行。但是,具有100%的通用性(表1中的每一行也在2中),第一个查询将执行100log100联合,然后是100log100联接(因为1和2的UNION将等同于表1),而第二个查询将执行100log100联合。查询仍将执行两个100log100联接和一个100log100联合。由于最坏情况是相同的,但查询1的最佳情况是查询2的三分之二,我会去查询1。
然而,正如评论者所说,如果你不期望任何欺骗,UNION ALL将在两个查询中表现更好。 A和B的UNION ALL的结果是A + B,它仅受每组访问时间的限制(我没有考虑过)。通过不期望欺骗,两个查询都可以被削减到第一个查询的最佳情况。
答案 2 :(得分:0)
对每个查询使用EXPLAIN ANALYZE SELECT A.id, ...
并比较结果。它们可能是一样的。
答案 3 :(得分:0)
为每个查询运行postgres'EXPLAIN,并查看执行成本。
答案 4 :(得分:0)
下面的查询通过@GarethD生成与两者不同的查询计划,但执行大致相同的操作(添加主键后):
-- EXPLAIN ANALYZE
WITH ttt AS (
SELECT COALESCE(t1.id,t2.id) AS id
FROM t1
FULL OUTER JOIN t2 ON t1.id = t2.id
)
INSERT INTO t6 (id, somevalue)
SELECT tx.id AS id
, t3.somevalue AS somevalue
FROM ttt tx
LEFT JOIN t3 ON tx.id = t3.fk
;
注意:在查询计划中,COALESCE()函数不存在,因此它实际上被视为运算符(这是好的):
QUERY PLAN
---------------------------------------------------------------------------------------------------------------------------
Insert on t6 (cost=3761.65..5761.69 rows=40001 width=10) (actual time=1643.429..1643.429 rows=0 loops=1)
CTE ttt
-> Hash Full Join (cost=1004.80..2488.62 rows=40001 width=8) (actual time=129.768..399.898 rows=60001 loops=1)
Hash Cond: (t1.id = t2.id)
-> Seq Scan on t1 (cost=0.00..557.01 rows=40001 width=4) (actual time=0.008..60.693 rows=40001 loops=1)
-> Hash (cost=533.80..533.80 rows=37680 width=4) (actual time=129.737..129.737 rows=40001 loops=1)
Buckets: 4096 Batches: 1 Memory Usage: 938kB
-> Seq Scan on t2 (cost=0.00..533.80 rows=37680 width=4) (actual time=0.016..62.236 rows=40001 loops=1)
-> Hash Left Join (cost=1273.02..3273.06 rows=40001 width=10) (actual time=265.672..999.408 rows=60001 loops=1)
Hash Cond: (tx.id = t3.fk)
-> CTE Scan on ttt tx (cost=0.00..800.02 rows=40001 width=4) (actual time=129.776..603.317 rows=60001 loops=1)
-> Hash (cost=597.01..597.01 rows=40001 width=10) (actual time=135.854..135.854 rows=40001 loops=1)
Buckets: 4096 Batches: 2 Memory Usage: 627kB
-> Seq Scan on t3 (cost=0.00..597.01 rows=40001 width=10) (actual time=0.009..66.008 rows=40001 loops=1)
Total runtime: 1644.480 ms
通过更好的调整(更少的work_mem,强制基于磁盘的执行,以及更低的random_page_cost以促进索引使用),计划甚至变得更好:
SET work_mem = 64;
SET random_page_cost = 2.1;
SET seq_page_cost = 2;
QUERY PLAN
--------------------------------------------------------------------------------------------------------------------------------------
Insert on t6 (cost=4404.54..6654.58 rows=40001 width=10) (actual time=1573.465..1573.465 rows=0 loops=1)
CTE ttt
-> Merge Full Join (cost=0.00..2758.52 rows=40001 width=8) (actual time=0.048..348.906 rows=60001 loops=1)
Merge Cond: (t1.id = t2.id)
-> Index Scan using t1_pkey on t1 (cost=0.00..1103.37 rows=40001 width=4) (actual time=0.022..67.840 rows=40001 loops=1)
-> Index Scan using t2_pkey on t2 (cost=0.00..1084.15 rows=37680 width=4) (actual time=0.018..68.583 rows=40001 loops=1)
-> Hash Left Join (cost=1646.02..3896.06 rows=40001 width=10) (actual time=170.840..957.899 rows=60001 loops=1)
Hash Cond: (tx.id = t3.fk)
-> CTE Scan on ttt tx (cost=0.00..800.02 rows=40001 width=4) (actual time=0.055..544.544 rows=60001 loops=1)
-> Hash (cost=794.01..794.01 rows=40001 width=10) (actual time=170.130..170.130 rows=40001 loops=1)
Buckets: 1024 Batches: 32 Memory Usage: 42kB
-> Seq Scan on t3 (cost=0.00..794.01 rows=40001 width=10) (actual time=0.009..79.751 rows=40001 loops=1)
Total runtime: 1574.108 ms
(13 rows)