SELECT DISTINCT TOP N
查询,这些查询似乎没有被SQL Server查询优化器优化。让我们从一个简单的例子开始:一个带有两个交替值的百万行表。我将使用GetNums函数生成数据:DROP TABLE IF EXISTS X_2_DISTINCT_VALUES;
CREATE TABLE X_2_DISTINCT_VALUES (PK INT IDENTITY (1, 1), VAL INT NOT NULL);
INSERT INTO X_2_DISTINCT_VALUES WITH (TABLOCK) (VAL)
SELECT N % 2
FROM dbo.GetNums(1000000);
UPDATE STATISTICS X_2_DISTINCT_VALUES WITH FULLSCAN;
对于以下查询:
SELECT DISTINCT TOP 2 VAL
FROM X_2_DISTINCT_VALUES
OPTION (MAXDOP 1);
SQL Server仅通过扫描表的第一个数据页就可以找到两个不同的值,但是它会扫描所有数据。 SQL Server为什么不扫描直到找到所需数量的不同值?
对于此问题,请使用以下测试数据,该数据包含1000万行,并在块中生成10个不同值:
DROP TABLE IF EXISTS X_10_DISTINCT_HEAP;
CREATE TABLE X_10_DISTINCT_HEAP (VAL VARCHAR(10) NOT NULL);
INSERT INTO X_10_DISTINCT_HEAP WITH (TABLOCK)
SELECT REPLICATE(CHAR(65 + (N / 100000 ) % 10 ), 10)
FROM dbo.GetNums(10000000);
UPDATE STATISTICS X_10_DISTINCT_HEAP WITH FULLSCAN;
带有聚集索引的表的答案也可以接受:
DROP TABLE IF EXISTS X_10_DISTINCT_CI;
CREATE TABLE X_10_DISTINCT_CI (PK INT IDENTITY (1, 1), VAL VARCHAR(10) NOT NULL, PRIMARY KEY (PK));
INSERT INTO X_10_DISTINCT_CI WITH (TABLOCK) (VAL)
SELECT REPLICATE(CHAR(65 + (N / 100000 ) % 10 ), 10)
FROM dbo.GetNums(10000000);
UPDATE STATISTICS X_10_DISTINCT_CI WITH FULLSCAN;
以下查询扫描所有10表格中的一百万行。我怎么能得到不能扫描整个桌子的东西?我正在使用SQL Server 2016 SP1。
SELECT DISTINCT TOP 10 VAL
FROM X_10_DISTINCT_HEAP
OPTION (MAXDOP 1);
#1 楼
在上面的查询中,看起来有三种不同的优化器规则可以执行DISTINCT
操作。以下查询引发错误,表明该列表是详尽无遗的:SELECT DISTINCT TOP 10 ID
FROM X_10_DISTINCT_HEAP
OPTION (MAXDOP 1, QUERYRULEOFF GbAggToSort, QUERYRULEOFF GbAggToHS, QUERYRULEOFF GbAggToStrm);
Msg 8622,级别16,状态1,行1
由于此查询中定义的提示,查询处理器无法生成查询计划。重新提交查询,但不指定任何提示,也无需使用SET FORCEPLAN。
GbAggToSort
将分组汇总(区别)实现为一种独特的排序方式。这是一个阻塞运算符,它将在产生任何行之前从输入读取所有数据。 GbAggToStrm
将group-by聚合实现为流聚合(在这种情况下,也需要输入排序)。这也是一个阻塞运算符。 GbAggToHS
实现为哈希匹配,这是我们从问题中的错误计划中看到的结果,但可以实现为哈希匹配(聚合)或哈希匹配(流程不同)。哈希匹配(流不同)运算符是解决此问题的一种方法,因为它不会阻塞。 SQL Server一旦找到足够多的不同值,便应该能够停止扫描。<<流/>逻辑>操作器将扫描输入,删除重复项。尽管Distinct运算符会在产生任何输出之前消耗所有输入,但是Flow Distinct运算符会返回从输入中获取的每一行(除非该行是重复的,在这种情况下将被丢弃)。
为什么问题中的查询使用哈希匹配(聚合)而不是哈希匹配(流不同)?随着表中不同值数量的变化,我希望哈希匹配(流不同)查询的成本会减少,因为对需要扫描到表的行数的估计应该减少。我希望哈希匹配(聚合)计划的成本会增加,因为需要构建的哈希表会变得更大。研究此问题的一种方法是创建计划指南。如果我创建了两个数据副本,但对其中一个应用了计划指南,则我应该能够针对同一数据并排比较哈希匹配(聚合)与哈希匹配(不同)。请注意,我无法通过禁用查询优化器规则来做到这一点,因为相同的规则适用于两个计划(
GbAggToHS
)。这是获取我所遵循的计划指南的一种方法:
DROP TABLE IF EXISTS X_PLAN_GUIDE_TARGET;
CREATE TABLE X_PLAN_GUIDE_TARGET (VAL VARCHAR(10) NOT NULL);
INSERT INTO X_PLAN_GUIDE_TARGET WITH (TABLOCK)
SELECT CAST(N % 10000 AS VARCHAR(10))
FROM dbo.GetNums(10000000);
UPDATE STATISTICS X_PLAN_GUIDE_TARGET WITH FULLSCAN;
-- run this query
SELECT DISTINCT TOP 10 VAL FROM X_PLAN_GUIDE_TARGET OPTION (MAXDOP 1)
获取计划手柄并将其用于创建计划指南:
-- plan handle is 0x060007009014BC025097E88F6C01000001000000000000000000000000000000000000000000000000000000
SELECT qs.plan_handle, st.text FROM
sys.dm_exec_query_stats AS qs
CROSS APPLY sys.dm_exec_sql_text(qs.sql_handle) AS st
WHERE st.text LIKE '%X[_]PLAN[_]GUIDE[_]TARGET%'
ORDER BY last_execution_time DESC;
EXEC sp_create_plan_guide_from_handle
'EVIL_PLAN_GUIDE',
0x060007009014BC025097E88F6C01000001000000000000000000000000000000000000000000000000000000;
计划指南仅适用于确切的查询文本,因此让我们从计划指南中将其复制回:
SELECT query_text
FROM sys.plan_guides
WHERE name = 'EVIL_PLAN_GUIDE';
重置数据:
TRUNCATE TABLE X_PLAN_GUIDE_TARGET;
INSERT INTO X_PLAN_GUIDE_TARGET WITH (TABLOCK)
SELECT REPLICATE(CHAR(65 + (N / 100000 ) % 10 ), 10)
FROM dbo.GetNums(10000000);
应用计划指南获取查询的查询计划:
SELECT DISTINCT TOP 10 VAL FROM X_PLAN_GUIDE_TARGET OPTION (MAXDOP 1)
它具有我们想要的测试数据哈希匹配(流不同)运算符。请注意,SQL Server希望从表中读取所有行,并且估计成本与具有哈希匹配项(聚合)的计划完全相同。我所做的测试表明,当计划的行目标大于或等于SQL Server从表中期望的不同值的数量时,这两个计划的成本是相同的,在这种情况下,可以直接从表中得出统计。不幸的是(对于我们的查询),当成本相同时,优化器会选择哈希匹配(聚合),而不是哈希匹配(流不同)。因此,我们比所需的计划少了0.0000001个魔术优化器单元。
解决此问题的一种方法是减少行目标。如果从优化器的角度来看行目标小于行的不同计数,则我们可能会得到哈希匹配(流不同)。这可以通过
OPTIMIZE FOR
查询提示来完成:DECLARE @j INT = 10;
SELECT DISTINCT TOP (@j) VAL
FROM X_10_DISTINCT_HEAP
OPTION (MAXDOP 1, OPTIMIZE FOR (@j = 1));
对于此查询,优化器将创建一个计划,就好像该查询只需要第一行,但是在执行查询时一样它会返回前10行。在我的计算机上,此查询从
X_10_DISTINCT_HEAP
扫描892800行,并在299毫秒内完成,具有250毫秒的CPU时间和2537逻辑读取。 请注意,如果统计信息仅报告一个不同的值(针对偏斜数据的抽样统计信息可能会发生这种情况),则此技术将无效。但是,在这种情况下,您的数据不太可能足够密集地打包以使用此类技术进行验证。扫描表中的所有数据可能不会造成很多损失,特别是如果可以并行完成的话。
解决此问题的另一种方法是通过夸大SQL Server期望从基表中获得的估计的不同值的数量。这比预期的要难。应用确定性函数可能无法增加结果的不同计数。如果查询优化器知道该数学事实(一些测试表明至少是出于我们的目的),则应用确定性函数(包括所有字符串函数)将不会增加不同行的估计数量。
许多不确定函数也不起作用,包括
NEWID()
和RAND()
的明显选择。但是,LAG()
可以解决此查询问题。查询优化器期望针对LAG
表达式期望1000万个不同的值,这将鼓励进行哈希匹配(流量不同)计划:SELECT DISTINCT TOP 10 LAG(VAL, 0) OVER (ORDER BY (SELECT NULL)) AS ID
FROM X_10_DISTINCT_HEAP
OPTION (MAXDOP 1);
在我的计算机上,此查询扫描892800行
X_10_DISTINCT_HEAP
并在1165 ms内完成,具有1109 ms的CPU时间和2537逻辑读取,因此LAG()
增加了相当多的相对开销。 @Paul White建议尝试对此查询进行批处理模式处理。在SQL Server 2016上,即使使用MAXDOP 1
也可以进行批处理模式处理。对行存储表进行批处理方式处理的一种方法是,按如下所示连接到空CCI:CREATE TABLE #X_DUMMY_CCI (ID INT NOT NULL);
CREATE CLUSTERED COLUMNSTORE INDEX X_DUMMY_CCI ON #X_DUMMY_CCI;
SELECT DISTINCT TOP 10 VAL
FROM
(
SELECT LAG(VAL, 1) OVER (ORDER BY (SELECT NULL)) AS VAL
FROM X_10_DISTINCT_HEAP
LEFT OUTER JOIN #X_DUMMY_CCI ON 1 = 0
) t
WHERE t.VAL IS NOT NULL
OPTION (MAXDOP 1);
该代码将导致此查询计划。
Paul指出,我必须更改查询以使用
LAG(..., 1)
,因为LAG(..., 0)
似乎不符合Window Aggregate优化的条件。此更改将经过时间减少到520 ms,将CPU时间减少到454 ms。请注意,
LAG()
方法不是最稳定的方法。如果Microsoft针对该功能更改了唯一性假设,则它可能不再起作用。与旧版CE的估算值不同。同样,针对堆的这种优化也不是一个好主意。如果重建表,则可能会遇到最坏的情况,即几乎所有行都需要从表中读取。针对具有唯一列的表(例如集群表)问题中的索引示例),我们有更好的选择。例如,我们可以使用始终返回空字符串的
SUBSTRING
表达式来欺骗优化器。 SQL Server认为SUBSTRING
不会更改不同值的数量,因此,如果将其应用于唯一列(例如PK),则估计不同行的数量为1000万。以下查询获取哈希匹配(流区分)运算符:SELECT DISTINCT TOP 10 VAL + SUBSTRING(CAST(PK AS VARCHAR(10)), 11, 1)
FROM X_10_DISTINCT_CI
OPTION (MAXDOP 1);
在我的计算机上,此查询扫描来自
X_10_DISTINCT_CI
的900000行,并在333 ms和297 ms CPU中完成时间和3011逻辑读取。总而言之,当
SELECT DISTINCT TOP N
> =估计的表中不同的行数时,查询优化器似乎假定将从表中读取所有行。散列匹配(聚合)运算符的成本可能与散列匹配(流不同)运算符的成本相同,但是优化器始终选择聚合运算符。当足够多的不同值位于表扫描开始处附近时,这可能导致不必要的逻辑读取。诱使优化器使用哈希匹配(流不同)运算符的两种方法是使用N
提示降低行目标,或使用唯一列上的OPTIMIZE FOR
或LAG()
增加估计的不同行数。#2 楼
您已经正确回答了自己的问题。我想补充一点,即最有效的方法实际上是扫描整个表-如果可以将其组织为列存储“堆”:
CREATE CLUSTERED COLUMNSTORE INDEX CCSI
ON dbo.X_10_DISTINCT_HEAP;
简单查询:
SELECT DISTINCT TOP (10)
XDH.VAL
FROM dbo.X_10_DISTINCT_HEAP AS XDH
OPTION (MAXDOP 1);
然后给出:
Table 'X_10_DISTINCT_HEAP'. Scan count 1, logical reads 0, physical reads 0, read-ahead reads 0, lob logical reads 66, lob physical reads 0, lob read-ahead reads 0. Table 'X_10_DISTINCT_HEAP'. Segment reads 13, segment skipped 0. SQL Server Execution Times: CPU time = 0 ms, elapsed time = 11 ms.
当前不能以批处理模式执行哈希匹配(流区分)。由于从批处理到行处理的(不可见的)昂贵的过渡,使用此方法的方法要慢得多。例如:
SET ROWCOUNT 10;
SELECT DISTINCT
XDH.VAL
FROM dbo.X_10_DISTINCT_HEAP AS XDH
OPTION (FAST 1);
SET ROWCOUNT 0;
礼物:
Table 'X_10_DISTINCT_HEAP'. Scan count 1, logical reads 0, physical reads 0, read-ahead reads 0, lob logical reads 20, lob physical reads 0, lob read-ahead reads 0. Table 'X_10_DISTINCT_HEAP'. Segment reads 4, segment skipped 0. SQL Server Execution Times: CPU time = 640 ms, elapsed time = 680 ms.
这是比将表组织为行存储堆时要慢。
#3 楼
这是尝试使用递归CTE模拟重复的部分扫描(类似于但不同于跳过扫描)的尝试。这样做的目的是-避免对表进行排序和多次扫描,因为我们在(id)
上没有索引。 绕过一些递归CTE限制有一些技巧:
递归部分中不允许
TOP
。我们改用子查询和ROW_NUMBER()
。我们不能对常量部分有多个引用,也不能使用
LEFT JOIN
或从递归部分使用NOT IN (SELECT id FROM cte)
。要绕过,我们构建了一个VARCHAR
字符串,该字符串累积了所有id
值(类似于STRING_AGG
或hierarchyID),然后与LIKE
进行比较。对于堆(假设该列名为
id
)test-1 on rextester.com。正如测试所示,这不能避免多次扫描,但是在前几页中找到不同的值时执行OK。但是,如果值分布不均匀,则可能会对表的大部分进行多次扫描-这当然会导致性能下降。
WITH ct (id, found, list) AS
( SELECT TOP (1) id, 1, CAST('/' + id + '/' AS VARCHAR(MAX))
FROM x_large_table_2
UNION ALL
SELECT y.ID, ct.found + 1, CAST(ct.list + y.id + '/' AS VARCHAR(MAX))
FROM ct
CROSS APPLY
( SELECT x.id,
rn = ROW_NUMBER() OVER (ORDER BY (SELECT NULL))
FROM x_large_table_2 AS x
WHERE ct.list NOT LIKE '%/' + id + '/%'
) AS y
WHERE ct.found < 3 -- the TOP (n) parameter here
AND y.rn = 1
)
SELECT id FROM ct ;
,并且当表被聚集时(
unique_key
上的CI),在rextester.com上进行test-2。 这使用聚簇索引(
WHERE x.unique_key > ct.unique_key
)避免多次扫描:WITH ct (unique_key, id, found, list) AS
( SELECT TOP (1) unique_key, id, 1, CAST(CONCAT('/',id, '/') AS VARCHAR(MAX))
FROM x_large_table_2
UNION ALL
SELECT y.unique_key, y.ID, ct.found + 1,
CAST(CONCAT(ct.list, y.id, '/') AS VARCHAR(MAX))
FROM ct
CROSS APPLY
( SELECT x.unique_key, x.id,
rn = ROW_NUMBER() OVER (ORDER BY (SELECT NULL))
FROM x_large_table_2 AS x
WHERE x.unique_key > ct.unique_key
AND ct.list NOT LIKE '%/' + id + '/%'
) AS y
WHERE ct.found < 5 -- the TOP (n) parameter here
AND y.rn = 1
)
-- SELECT * FROM ct ; -- for debugging
SELECT id FROM ct ;
#4 楼
为了完整性,解决此问题的另一种方法是使用OUTER APPLY。我们可以为每个需要查找的不同值添加一个OUTER APPLY
运算符。这在概念上与ypercube的递归方法相似,但是有效地手工编写了递归。一个优点是,我们能够在派生表中使用TOP
而不是ROW_NUMBER()
解决方法。一个很大的缺点是查询文本随着N
的增加而变长。这是针对堆的查询的一种实现:
SELECT VAL
FROM (
SELECT t1.VAL VAL1, t2.VAL VAL2, t3.VAL VAL3, t4.VAL VAL4, t5.VAL VAL5, t6.VAL VAL6, t7.VAL VAL7, t8.VAL VAL8, t9.VAL VAL9, t10.VAL VAL10
FROM
(
SELECT TOP 1 VAL FROM X_10_DISTINCT_HEAP
) t1
OUTER APPLY
(
SELECT TOP 1 VAL FROM X_10_DISTINCT_HEAP t2 WHERE t2.VAL NOT IN (t1.VAL)
) t2
OUTER APPLY
(
SELECT TOP 1 VAL FROM X_10_DISTINCT_HEAP t3 WHERE t3.VAL NOT IN (t1.VAL, t2.VAL)
) t3
OUTER APPLY
(
SELECT TOP 1 VAL FROM X_10_DISTINCT_HEAP t4 WHERE t4.VAL NOT IN (t1.VAL, t2.VAL, t3.VAL)
) t4
OUTER APPLY
(
SELECT TOP 1 VAL FROM X_10_DISTINCT_HEAP t5 WHERE t5.VAL NOT IN (t1.VAL, t2.VAL, t3.VAL, t4.VAL)
) t5
OUTER APPLY
(
SELECT TOP 1 VAL FROM X_10_DISTINCT_HEAP t6 WHERE t6.VAL NOT IN (t1.VAL, t2.VAL, t3.VAL, t4.VAL, t5.VAL)
) t6
OUTER APPLY
(
SELECT TOP 1 VAL FROM X_10_DISTINCT_HEAP t7 WHERE t7.VAL NOT IN (t1.VAL, t2.VAL, t3.VAL, t4.VAL, t5.VAL, t6.VAL)
) t7
OUTER APPLY
(
SELECT TOP 1 VAL FROM X_10_DISTINCT_HEAP t8 WHERE t8.VAL NOT IN (t1.VAL, t2.VAL, t3.VAL, t4.VAL, t5.VAL, t6.VAL, t7.VAL)
) t8
OUTER APPLY
(
SELECT TOP 1 VAL FROM X_10_DISTINCT_HEAP t9 WHERE t9.VAL NOT IN (t1.VAL, t2.VAL, t3.VAL, t4.VAL, t5.VAL, t6.VAL, t7.VAL, t8.VAL)
) t9
OUTER APPLY
(
SELECT TOP 1 VAL FROM X_10_DISTINCT_HEAP t10 WHERE t10.VAL NOT IN (t1.VAL, t2.VAL, t3.VAL, t4.VAL, t5.VAL, t6.VAL, t7.VAL, t8.VAL, t9.VAL)
) t10
) t
UNPIVOT
(
VAL FOR VALS IN (VAL1, VAL2, VAL3, VAL4, VAL5, VAL6, VAL7, VAL8, VAL9, VAL10)
) AS upvt;
这里是上述查询的实际查询计划。在我的计算机上,此查询在713毫秒(625毫秒的CPU时间和12605逻辑读取)中完成。我们每10万行获得一个新的不同值,因此我希望该查询能够扫描约900000 * 10 * 0.5 = 4500000行。从理论上讲,此查询应从另一个答案中对该查询进行逻辑读取五倍:
DECLARE @j INT = 10;
SELECT DISTINCT TOP (@j) VAL
FROM X_10_DISTINCT_HEAP
OPTION (MAXDOP 1, OPTIMIZE FOR (@j = 1));
该查询进行了2537次逻辑读取。 2537 * 5 = 12685,非常接近12605。
对于具有聚集索引的表,我们可以做得更好。这是因为我们可以将最后一个集群键值传递到派生表中,以避免两次扫描相同的行。一种实现:
SELECT VAL
FROM (
SELECT t1.VAL VAL1, t2.VAL VAL2, t3.VAL VAL3, t4.VAL VAL4, t5.VAL VAL5, t6.VAL VAL6, t7.VAL VAL7, t8.VAL VAL8, t9.VAL VAL9, t10.VAL VAL10
FROM
(
SELECT TOP 1 PK, VAL FROM X_10_DISTINCT_CI
) t1
OUTER APPLY
(
SELECT TOP 1 PK, VAL FROM X_10_DISTINCT_CI t2 WHERE PK > t1.PK AND t2.VAL NOT IN (t1.VAL)
) t2
OUTER APPLY
(
SELECT TOP 1 PK, VAL FROM X_10_DISTINCT_CI t3 WHERE PK > t2.PK AND t3.VAL NOT IN (t1.VAL, t2.VAL)
) t3
OUTER APPLY
(
SELECT TOP 1 PK, VAL FROM X_10_DISTINCT_CI t4 WHERE PK > t3.PK AND t4.VAL NOT IN (t1.VAL, t2.VAL, t3.VAL)
) t4
OUTER APPLY
(
SELECT TOP 1 PK, VAL FROM X_10_DISTINCT_CI t5 WHERE PK > t4.PK AND t5.VAL NOT IN (t1.VAL, t2.VAL, t3.VAL, t4.VAL)
) t5
OUTER APPLY
(
SELECT TOP 1 PK, VAL FROM X_10_DISTINCT_CI t6 WHERE PK > t5.PK AND t6.VAL NOT IN (t1.VAL, t2.VAL, t3.VAL, t4.VAL, t5.VAL)
) t6
OUTER APPLY
(
SELECT TOP 1 PK, VAL FROM X_10_DISTINCT_CI t7 WHERE PK > t6.PK AND t7.VAL NOT IN (t1.VAL, t2.VAL, t3.VAL, t4.VAL, t5.VAL, t6.VAL)
) t7
OUTER APPLY
(
SELECT TOP 1 PK, VAL FROM X_10_DISTINCT_CI t8 WHERE PK > t7.PK AND t8.VAL NOT IN (t1.VAL, t2.VAL, t3.VAL, t4.VAL, t5.VAL, t6.VAL, t7.VAL)
) t8
OUTER APPLY
(
SELECT TOP 1 PK, VAL FROM X_10_DISTINCT_CI t9 WHERE PK > t8.PK AND t9.VAL NOT IN (t1.VAL, t2.VAL, t3.VAL, t4.VAL, t5.VAL, t6.VAL, t7.VAL, t8.VAL)
) t9
OUTER APPLY
(
SELECT TOP 1 PK, VAL FROM X_10_DISTINCT_CI t10 WHERE PK > t9.PK AND t10.VAL NOT IN (t1.VAL, t2.VAL, t3.VAL, t4.VAL, t5.VAL, t6.VAL, t7.VAL, t8.VAL, t9.VAL)
) t10
) t
UNPIVOT
(
VAL FOR VALS IN (VAL1, VAL2, VAL3, VAL4, VAL5, VAL6, VAL7, VAL8, VAL9, VAL10)
) AS upvt;
这是上述查询的实际查询计划。在我的计算机上,此查询在154毫秒内完成,具有140毫秒的CPU时间和3203逻辑读取。这似乎比针对聚集索引表的
OPTIMIZE FOR
查询运行得快一些。我没想到,所以我尝试更仔细地评估性能。我的方法是在没有结果集的情况下运行每个查询十次,并查看sys.dm_exec_sessions
和sys.dm_exec_session_wait_stats
的总数。会话56是APPLY
查询,会话63是OPTIMIZE FOR
查询。sys.dm_exec_sessions
的输出:╔════════════╦══════════╦════════════════════╦═══════════════╗
║ session_id ║ cpu_time ║ total_elapsed_time ║ logical_reads ║
╠════════════╬══════════╬════════════════════╬═══════════════╣
║ 56 ║ 1360 ║ 1373 ║ 32030 ║
║ 63 ║ 2094 ║ 2091 ║ 30400 ║
╚════════════╩══════════╩════════════════════╩═══════════════╝
似乎有明显的优势。在
APPLY
查询的cpu_time和elapsed_time中。sys.dm_exec_session_wait_stats
的输出:╔════════════╦════════════════════════════════╦═════════════════════╦══════════════╦══════════════════╦═════════════════════╗
║ session_id ║ wait_type ║ waiting_tasks_count ║ wait_time_ms ║ max_wait_time_ms ║ signal_wait_time_ms ║
╠════════════╬════════════════════════════════╬═════════════════════╬══════════════╬══════════════════╬═════════════════════╣
║ 56 ║ SOS_SCHEDULER_YIELD ║ 340 ║ 0 ║ 0 ║ 0 ║
║ 56 ║ MEMORY_ALLOCATION_EXT ║ 38 ║ 0 ║ 0 ║ 0 ║
║ 63 ║ SOS_SCHEDULER_YIELD ║ 518 ║ 0 ║ 0 ║ 0 ║
║ 63 ║ MEMORY_ALLOCATION_EXT ║ 98 ║ 0 ║ 0 ║ 0 ║
║ 63 ║ RESERVED_MEMORY_ALLOCATION_EXT ║ 400 ║ 0 ║ 0 ║ 0 ║
╚════════════╩════════════════════════════════╩═════════════════════╩══════════════╩══════════════════╩═════════════════════╝
OPTIMIZE FOR
查询具有附加的等待类型RESERVED_MEMORY_ALLOCATION_EXT。我不完全知道这意味着什么。它可能只是散列匹配(流量不同)运算符中开销的度量。无论如何,CPU时间相差70毫秒也许都不值得担心。#5 楼
我想您对为什么这样有答案这可能是解决它的一种方式
我知道它看起来很乱,但是执行计划说前2名仅占成本的84%
SELECT distinct top (2) [enumID]
FROM [ENRONbbb].[dbo].[docSVenum1]
declare @table table (enumID tinyint);
declare @enumID tinyint;
set @enumID = (select top (1) [enumID] from [docSVenum1]);
insert into @table values (@enumID);
set @enumID = (select top (1) [enumID] from [docSVenum1] where [enumID] not in (select enumID from @table));
insert into @table values (@enumID);
set @enumID = (select top (1) [enumID] from [docSVenum1] where [enumID] not in (select enumID from @table));
insert into @table values (@enumID);
set @enumID = (select top (1) [enumID] from [docSVenum1] where [enumID] not in (select enumID from @table));
insert into @table values (@enumID);
set @enumID = (select top (1) [enumID] from [docSVenum1] where [enumID] not in (select enumID from @table));
insert into @table values (@enumID);
set @enumID = (select top (1) [enumID] from [docSVenum1] where [enumID] not in (select enumID from @table));
insert into @table values (@enumID);
set @enumID = (select top (1) [enumID] from [docSVenum1] where [enumID] not in (select enumID from @table));
insert into @table values (@enumID);
set @enumID = (select top (1) [enumID] from [docSVenum1] where [enumID] not in (select enumID from @table));
insert into @table values (@enumID);
set @enumID = (select top (1) [enumID] from [docSVenum1] where [enumID] not in (select enumID from @table));
insert into @table values (@enumID);
set @enumID = (select top (1) [enumID] from [docSVenum1] where [enumID] not in (select enumID from @table));
insert into @table values (@enumID);
select enumID from @table;
评论
这段代码在我的机器上花了5秒钟。看起来,对表变量的联接增加了很多开销。在最终查询中,表变量被扫描892800次。该查询花费了1359毫秒的CPU时间和1374毫秒的经过时间。绝对超出我的预期。向主表变量添加主键似乎有所帮助,尽管我不确定为什么。可能还有其他可能的优化。
– Joe Obbish
17年2月21日在6:02
评论
该解决方案存在一个相当细微的性能问题。找到第N个值后,最终在表上进行了额外的查找。因此,如果前10个值有10个不同的值,它将寻找第11个不存在的值。最后,您需要进行额外的全面扫描,并且实际上总共有1000万个ROW_NUMBER()计算。我这里有一个解决方法,可以将机器上的查询速度提高20倍。你怎么看? brentozar.com/pastetheplan/?id=SkDhAmFKe
– Joe Obbish
17年2月21日在3:07