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
有数百万行
现在考虑以下两个查询:
查询ASELECT *
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 OnTimeStamp>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