2

这里有两个返回相同结果集的查询,但哪个是最佳语句或无关紧要?

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

谢谢。

4

5 回答 5

3

首先使用的执行计划UNION显示的步骤要少得多,不幸的是,执行计划并不是一切,还有表扫描、逻辑读取、CPU 使用率,所以这不是全部,它在很大程度上取决于您的数据和你的指数。

使用 dupes 时,第一个查询应该执行得更好,因为在UNION连接之前删除重复项会导致对表 3 的表扫描次数减少。如果 table1 和 table2 中没有重复项,那么应该没有区别。

这可以通过一些示例数据来证明。我所有的示例都使用以下 5 个表(T4 和 T5 只是将输出转储到其中,因此您不必在 SQL fiddle 页面向下滚动数英里即可查看执行计划)

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

SQL Fiddle 上的示例显示插入到 T4(UNION之前JOIN)性能更好。我已经运行了 25 次,其中 22 次插入到 T4 运行得更快。没有足够的数据从等式中消除服务器负载,因此正如预期的那样,存在一些异常情况。在此示例中,插入的顺序颠倒了,再次看到了类似的结果。

示例 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);

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

样本数据

样本数据 2

最后,重申已经提出的观点,如果您不期望被欺骗/不关心被欺骗,那么UNION ALL将提高性能,但是由于没有欺骗,两种方法的性能应该大致相似,这应该会改善两种方法在同等程度上。我没有对此进行测试,但更改我用来检查的测试数据应该不是一项艰巨的任务。

编辑

我刚刚尝试了 SQL Fiddle 上的查询,它们显示出的差异比在我的本地机器上的差异要大得多,所以用少许盐来处理这些示例并在您自己的服务器上进行测试,创建一个公平的要容易得多测试环境!

于 2012-06-18T22:31:34.230 回答
2

OK,首先关闭,id在选择列表中是模棱两可的;我们想要A.id还是B.id

其次,假设 id 是所有表中的索引字段,去重和连接都是 NlogM 操作,其中 N 是“左侧”的行数,M 是“右侧”的行数。对于 N 中的每一行,必须找到或未找到 M 中的匹配行(连接时,将 M 中找到的行包含在结果中;合并时,排除 M 中找到的行)。这意味着最小化左侧的基数将提供最佳性能。

因此,任一查询的复杂性很大程度上取决于表 1 和表 2 之间有多少共享 ID。在零共性(没有相同的行 ID)和每个表 100 行的情况下,第一个查询将执行一个 100log100 联合,然后一个 200log100 连接,第二个查询将执行两个 100log100 连接,然后是一个 100log100 联合,这将在相同的时间内执行。然而,在 100% 的通用性下(表 1 中的每一行也在 2 中),第一个查询将执行 100log100 联合,然后是 100log100 连接(因为 1 和 2 的 UNION 将等同于表 1),而第二个查询仍将执行两个 100log100 连接和一个 100log100 联合。由于最坏情况相同,但查询 1 的最佳情况是查询 2 的三分之二,所以我选择查询 1。

但是,正如评论者所说,如果您不期望任何欺骗,则 UNION ALL 在两个查询中都会表现得更好。A 和 B 的 UNION ALL 的结果是 A+B,它只受每个集合的访问时间的约束(我没有考虑过)。通过不期望欺骗,两个查询都可以减少到第一个查询的最佳情况下的性能。

于 2012-06-18T21:59:25.577 回答
0

用于EXPLAIN ANALYZE SELECT A.id, ...每个查询并比较结果。他们可能是一样的。

于 2012-06-18T21:18:16.943 回答
0

为每个查询运行 postgres 的EXPLAIN并查看执行成本。

于 2012-06-18T21:20:17.217 回答
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)
于 2012-06-19T14:15:42.013 回答