Sql server SQL Server:小表上的内部联接相对于子句中的子查询的性能

Sql server SQL Server:小表上的内部联接相对于子句中的子查询的性能,sql-server,Sql Server,假设我有以下两张表: CREATE TABLE [dbo].[ActionTable] ( [ActionID] [int] IDENTITY(1, 1) NOT FOR REPLICATION NOT NULL ,[ActionName] [varchar](80) NOT NULL ,[Description] [varchar](120) NOT NULL ,CONSTRAINT [PK_ActionTable] PRIMARY KEY CLUSTERED

假设我有以下两张表:

CREATE TABLE [dbo].[ActionTable] 
(
    [ActionID] [int] IDENTITY(1, 1) NOT FOR REPLICATION NOT NULL
    ,[ActionName] [varchar](80) NOT NULL
    ,[Description] [varchar](120) NOT NULL
    ,CONSTRAINT [PK_ActionTable] PRIMARY KEY CLUSTERED ([ActionID] ASC)
    ,CONSTRAINT [IX_ActionName] UNIQUE NONCLUSTERED ([ActionName] ASC)
    )
GO

CREATE TABLE [dbo].[BigTimeSeriesTable] 
(
    [ID] [bigint] IDENTITY(1, 1) NOT FOR REPLICATION NOT NULL
    ,[TimeStamp] [datetime] NOT NULL
    ,[ActionID] [int] NOT NULL
    ,[Details] [varchar](max) NULL
    ,CONSTRAINT [PK_BigTimeSeriesTable] PRIMARY KEY NONCLUSTERED ([ID] ASC)
    )
GO

ALTER TABLE [dbo].[BigTimeSeriesTable]
    WITH CHECK ADD CONSTRAINT [FK_BigTimeSeriesTable_ActionTable] FOREIGN KEY ([ActionID]) REFERENCES [dbo].[ActionTable]([ActionID])
GO

CREATE CLUSTERED INDEX [IX_BigTimeSeriesTable] ON [dbo].[BigTimeSeriesTable] ([TimeStamp] ASC)
GO

CREATE NONCLUSTERED INDEX [IX_BigTimeSeriesTable_ActionID] ON [dbo].[BigTimeSeriesTable] ([ActionID] ASC)
GO
ActionTable
有1000行,
bigtimerseriestable
有数百万行

现在考虑以下两个查询:

查询A

SELECT *
FROM BigTimeSeriesTable
WHERE TimeStamp > DATEADD(DAY, -3, GETDATE())
    AND ActionID IN (
        SELECT ActionID
        FROM ActionTable
        WHERE ActionName LIKE '%action%'
        )

查询B

SELECT bts.*
FROM BigTimeSeriesTable bts
INNER JOIN ActionTable act ON act.ActionID = bts.ActionID
WHERE bts.TimeStamp > DATEADD(DAY, -3, GETDATE())
    AND act.ActionName LIKE '%action%'

问题:为什么查询A的性能比查询B好(有时好10倍)?查询优化器不应该认识到这两个查询完全相同吗?是否有任何方法可以提供提示来提高内部联接的性能


更新:我将连接更改为
内部合并连接
,性能大大提高。看见有趣的是,当我在我试图运行的实际查询中尝试合并联接时(我不能在这里显示,机密),它完全弄乱了查询优化器,查询速度非常慢,而不仅仅是相对缓慢。

对于内部联接,筛选和联接之间没有区别

[

但是这里你的代码创建了不同的情况

查询A:您正在筛选1000条记录

查询B:首先加入数百万行,然后过滤1000条记录


因此,查询A比查询B花费的时间少

您提供的执行计划都具有完全相同的基本策略

参加

ActionTable
进行搜索,查找
ActionName
以“generate”开头的行,在
ActionName上有一个剩余谓词,如“%action%”
。然后使用7个匹配行构建哈希表

在探测端有一个seek On
TimeStamp>Scalar操作符(dateadd(day,(-3),getdate())
并根据哈希表测试匹配的行,以查看这些行是否应该联接

有两个主要差异可以解释为什么版本中的
执行得更快

在里面

  • 版本中的
    正在并行执行。有4个并发线程在执行查询,而不仅仅是一个线程
    
  • 与并行性相关,此计划有一个。它能够使用此位图提前消除行。在内部联接计划中,25959124行被传递到哈希联接的探测端,在半联接计划中,seek仍然读取2590万行,但只有313行被传递给联接进行评估。其余的行被提前消除by在搜索中应用位图
  • 为什么
    内部联接
    版本不能并行执行,这一点尚不清楚。您可以尝试添加提示
    选项(使用提示('ENABLE_parallel_PLAN_PREFERENCE'))
    ,以查看您现在是否得到一个并行执行且包含位图过滤器的计划

    如果您能够更改索引,那么考虑到查询仅返回7个不同操作的309行,您可能会发现将
    IX\u bigtimeseristable\u ActionID
    替换为带前导列的覆盖索引
    [ActionID],[TimeStamp]
    然后获得一个包含7次搜索的嵌套循环计划比当前查询的性能要好得多

    CREATE NONCLUSTERED INDEX [IX_BigTimeSeriesTable_ActionID_TimeStamp]
      ON [dbo].[BigTimeSeriesTable] ([ActionID], [TimeStamp])
      INCLUDE ([Details], [ID])
    
    希望有了这个索引,您现有的查询就可以使用它,并且您将看到7个查找,每个查找平均返回44行,以读取并返回所需的确切总数309行。如果没有,您可以尝试下面的方法

    SELECT CA.*
    FROM ActionTable A
    CROSS APPLY 
    (
    SELECT *
    FROM BigTimeSeriesTable B
    WHERE B.ActionID = A.ActionID AND B.TimeStamp > DATEADD(DAY, -3, GETDATE())
    ) CA
    WHERE A.ActionName LIKE '%action%'
    

    我成功地使用了索引提示:
    WITH(index(IX\u BigTimeSeriesTable\u ActionID))

    然而,随着查询的变化,即使是很小的变化,这也会完全削弱优化器获得最佳查询的能力


    因此,如果你想“物化”为了迫使子查询更早地执行,到2020年2月为止,最好使用临时表。

    查看这些查询的执行计划。如果您知道in更快,那么为什么要使用联接?如果内部查询只返回几个结果,比将两个表联接在一起要快。@a_horse_,没有名字,我很抱歉ady加入这两个,以便在我的查询中返回ActionName。问题不清楚,但我试图保持示例的干净。你能发布示例执行计划吗?考虑到行数,很难创建复制。什么是“先做”的是由查询计划决定的。不一定是由您编写的查询决定的。完全有可能(尽管不太可能)查询A在
    中的
    之后运行
    ,其中ActionName如“%action%”
    。这个问题令人沮丧的一部分是,ActionID上已经有一个索引。我正试图“具体化”子查询以强制使用该索引。我应该注意到,没有一个执行计划包含该索引,而让并行性工作是一件麻烦事。这里有两种解决方案,临时表或索引提示。到目前为止,索引提示工作得很好。感谢您的回答,这非常有帮助。@MarkO-如果索引只要有一个键列
    ActionID
    ,SQL Server仍然需要读取一个操作的所有匹配行,包括那些超过3天的行,并在
    TimeStamp
    上使用剩余谓词来丢弃不需要的行。最佳索引将有
    ActionID,TimeStamp
    键列,以允许精确地读取正确的行seekedForcing IX_BigTimeSeriesTable_ActionID生成此特定查询(以及其他类似查询)只需几分之一秒,即使有一个联接,并且大表有2亿多行。不幸的是,在不久的将来更新索引是不可行的,所以这将不得不这样做。还要注意,表是在
    时间戳上聚集的,所以丢弃该谓词上不需要的行不是什么大问题。@MarkO-啊,在这种情况下,实际上是这样的已经隐式地拥有理想索引。对于非唯一的非聚集索引,聚集索引键被附加到键列中-因此它已经是
    [ActionID],[TimeStamp]
    ,即使只声明为
    ActionI