哪个联接顺序更快?

时间:2016-01-21 09:54:21

标签: sql-server query-optimization

我正在查看一个MS SQL Server数据库,该数据库是由数据库设计专家公司开发的(或者我被告知),我注意到了JOIN /索引的一种奇怪模式。它与我的工作完全颠倒了,所以我想知道它是否有一些性能优势(数据库相当大)。

表结构(简化的伪代码)是:

表JOBS(约1K行):

  • job_id [int,primary key]
  • server_id [int,foreign key]
  • job_name [string]

表JOB_HISTORY(约17M行):

  • history_id [int,primary key]
  • job_id [int,foreign key]
  • server_id [int,foreign key]
  • job_start [datetime]
  • job_duration [int]

注意两个表中server_id所在的非规范化。

他们做的是:

select
    t1.job_name, t2.job_start, t2.job_duration
from
    JOBS t1
inner join 
    JOB_HISTORY t2 on (t1.job_id = t2.job_id and t1.server_id = t2.server_id)
where
    t1.server_id = @param_server_id
    and t2.job_start >= @param_from
    and t2.job_start <= @param_to

他们有索引:

  • JOBS =&gt; (SERVER_ID)
  • JOB_HISTORY =&gt; (job_id,server_id,job_start)

换句话说,当他们选择行时,他们首先从JOBS表中过滤作业,然后查找相关的JOB_HISTORY条目。由于索引,这就是DB被迫做的事情。

我会做的是自下而上的版本:

select
    t1.job_name, t2.job_start, t2.job_duration
from
    JOB_HISTORY t2
inner join 
    JOBS t1 on (t1.job_id = t2.job_id)
where
    t2.server_id = @param_server_id
    and t2.job_start >= @param_from
    and t2.job_start <= @param_to

一个索引:

  • JOB_HISTORY =&gt; (server_id,job_start)

所以,基本上,我直接从大JOB_HISTORY中选择相关行,然后只查找JOBS表中的附加数据。

是否有理由偏爱另一个?

2 个答案:

答案 0 :(得分:3)

好吧,我有点无聊,所以我想为你重新创造这个。首先设置(我使用数字表生成大约1K和17M行,当然,这是所有随机数据,并不代表您的系统:)我还假设它是一个聚簇索引在每张桌子上,即使你暗示你也没有。

    USE TempDB;
    GO
    DROP TABLE IF EXISTS #Jobs;
    DROP TABLE IF EXISTS #Job_History;

    CREATE TABLE #Jobs
        (
         job_id INT IDENTITY PRIMARY KEY
        ,server_id INT
        ,job_name VARCHAR(50)
        );

    CREATE TABLE #Job_History
        (
         history_id INT IDENTITY PRIMARY KEY
        ,job_id INT
        ,server_id INT
        ,job_start DATETIME DEFAULT SYSDATETIME()
        ,job_duration INT DEFAULT ABS(CHECKSUM(NEWID())) % 5000
        );
    GO

    INSERT  INTO #Jobs
            SELECT  server_id = N.n
                   ,job_name = CONVERT(VARCHAR(50), NEWID())
            FROM    DBA.Dim.Numbers N
            WHERE   n < 1000;

    INSERT  INTO #JOB_HISTORY
            ( job_id
            ,server_id
            )
            SELECT  job_id = j1.job_id
                   ,server_id = j1.server_id
            FROM    #Jobs j1
                    CROSS JOIN DBA.Dim.Numbers n
            WHERE   n < 17000;

现在,案例1(他们的方式)

    DROP INDEX IF EXISTS Idx_Job_hist ON #Job_History;
    CREATE NONCLUSTERED INDEX Idx_Job_Hist ON #Job_History (job_id, server_id, job_start);

    DBCC FREEPROCCACHE
    DBCC DROPCLEANBUFFERS

    DECLARE @param_server_id INT = 1234
    DECLARE @param_from INT = 500
    DECLARE @param_to INT = 1000

    select
        t1.job_name, t2.job_start, t2.job_duration
    from
        #JOBS t1
    inner join 
        #JOB_HISTORY t2 on (t1.job_id = t2.job_id and t1.server_id = t2.server_id)
    where
        t1.server_id = @param_server_id
        and t2.job_start >= @param_from
        and t2.job_start <= @param_to;

案例2(你的方式)

    DROP INDEX IF EXISTS Idx_Job_hist ON #Job_History;
    CREATE NONCLUSTERED INDEX Idx_Job_Hist ON #Job_History (server_id, job_start);

    select
        t1.job_name, t2.job_start, t2.job_duration
    from
        #JOB_HISTORY t2
    inner join 
        #JOBS t1 on (t1.job_id = t2.job_id)
    where
        t2.server_id = @param_server_id
        and t2.job_start >= @param_from
        and t2.job_start <= @param_to;

(完全没有结论,因为我的系统不是你的系统......)结果:

他们的计划: enter image description here

您的计划: enter image description here

您的计划的成本总体上要高得多。

但是,这只是一个相当人为的练习来证明这一点 - 运行计划,答案是 - 这取决于。

(感谢借口玩这个,很有趣:)

答案 1 :(得分:1)

这里简短的回答是,你JOIN表的顺序并不重要。 SQL是您告诉服务器您想要什么的语言之一,而不是您希望它做什么(**)。 (AKA是一个所谓的declarative language)。

我们看到两个版本的查询的不同查询计划的原因是它们不完全相同。在第一个表中,要求server_id在两个表中都相同,而在第二个版本中则不再提及。 t1.server_id可以是任何东西。如果您重新添加此要求,您会注意到查询计划将是相同的,并且服务器将在“引擎盖”下为任一查询执行完全相同的操作。

仅供参考:在Les H的回答的基础上,我冒昧地检查了MSSQL在这里建议的索引类型,而不是出乎意料地提出

CREATE NONCLUSTERED INDEX idx_test
ON [dbo].[Job_History] ([server_id],[job_start])
INCLUDE ([job_id],[job_duration])

仅供参考:

  • 没有索引,每个查询运行大约需要1500毫秒
  • 创建索引需要大约20秒
  • 使用索引,每个查询大约需要200毫秒才能运行

(**:是的,我知道你可以通过HINTS'指导'在幕后发生的事情,但经验表明,当QO不再有意义时,那些应该只是最后的手段在大多数情况下,当统计数据是最新的并且数据布局不是极具异国情调时,查询优化工具非常聪明地找到了获取所需数据的最佳方式。)