这里最优的JOIN方法是什么?

时间:2012-06-18 21:15:33

标签: sql postgresql join

这是两个返回相同结果集的查询,但这是最佳语句还是不重要?

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,以防它影响事物)。

感谢。

5 个答案:

答案 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);

在此示例中,执行时间彼此更接近,并且经常在哪种方法执行得更快之间切换。

Sample Data

Sample Data 2

最后,重申已经提出的观点,如果你不期待欺骗/不关心欺骗,那么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)