bee*_*eks 7 sql-server-2005 sql-server optimization
我构建了以下 SQL Server 查询,但它遇到了SQL Server 2005 中的反半连接缺陷,导致基数估计不准确(1 - 呃!)并永远运行。由于它是一个长期的生产 SQL Server,我不能轻易建议升级版本,因此我不能在这个特定查询上强制使用 traceflag 4199 提示。
我很难重构WHERE AND NOT IN (SELECT)
. 有人可以帮忙吗?我确保尝试使用基于集群密钥对的最佳连接。
SELECT TOP 5000 d.doc2_id
,d.direction_cd
,a.address_type_cd
,d.external_identification
,s.hash_value
,d.publishdate
,d.sender_address_id AS [D2 Sender_Address_id]
,a.address_id AS [A Address_ID]
,d.message_size
,d.subject
,emi.employee_id
FROM assentor.emcsdbuser.doc2 d(NOLOCK)
INNER JOIN assentor.emcsdbuser.employee_msg_index emi(NOLOCK)
ON d.processdate = emi.processdate
AND d.doc2_id = emi.doc2_id
INNER LOOP JOIN assentor.emcsdbuser.doc2_address a(NOLOCK)
ON emi.doc2_id = a.doc2_id
AND emi.address_type_cd = a.address_type_cd
AND emi.address_id = a.address_id
INNER JOIN sis.dbo.sis s(NOLOCK) ON d.external_identification = s.external_identification
WHERE d.publishdate > '2008-01-01'
**AND d.doc2_id NOT IN (
SELECT doc2_id
FROM assentor.emcsdbuser.doc2_address d2a(NOLOCK)
WHERE d.doc2_id = d2a.doc2_id
AND d2a.address_type_cd = 'FRM'
)**
OPTION (FAST 10)
Run Code Online (Sandbox Code Playgroud)
请注意,该Employee_MSG_Index
表是 500m 行,doc2
是 1.5b 行,SIS
是 ~500m 行。
任何帮助,将不胜感激!
Pau*_*ite 13
由于它是一个长期的生产 SQL Server,我不能轻易建议升级版本
从2005 年到 2012 年,反半连接基数估计错误可在所有版本的 SQL Server 上重现。所有都需要跟踪标志 4199 才能启用修复,因此如果不激活 4199,升级将无法解决您的问题(当然,从 2005 升级还有许多其他很好的理由)。
...因此我无法在此特定查询上强制使用 traceflag 4199 提示。
如果它只是一个特定的查询受到影响,您可以使用OPTION (QUERYTRACEON 4199)
为该查询启用跟踪标志。此查询提示已记录并支持与 4199 一起使用,并且从 SQL Server 2005 Service Pack 2 开始适用。
此提示有效地运行DBCC TRACEON (4199)
并DBCC TRACEOFF (4199)
围绕查询,因此需要sysadmin权限。如果这是一个问题,请使用计划指南添加提示。
您还应该考虑使用 4199 启用instance-wide测试整个系统。计划回归是可能的,但总的来说,您可能会发现此标志启用的各种优化器修复非常值得。所有未来影响计划的查询处理器修复都需要激活此标志。
正如ypercube's answer 中提到的,该错误需要两个或更多连接列来显示(在许多细节中)。该冗余您的NOT IN
条款会使优化看到两列的比较(虽然逻辑上只有一个),从而暴露错误。
删除这种冗余将“解决”这个特定查询的问题,尽管其他确实有多个连接谓词的查询仍然容易受到攻击。
为了说明这一点,这里有一个基于问题中链接的 CSS 博客文章的示例(但有一个完整的脚本!):
CREATE TABLE dbo.tst_TAB1
(
c1 integer NOT NULL,
c2 integer NOT NULL,
c3 integer NOT NULL
);
CREATE TABLE dbo.tst_TAB2
(
c1 integer NOT NULL,
c2 integer NOT NULL,
c3 integer NOT NULL
);
CREATE INDEX i ON dbo.tst_TAB1 (c1, c2);
CREATE INDEX i ON dbo.tst_TAB2 (c1, c2);
Run Code Online (Sandbox Code Playgroud)
样本数据:
INSERT dbo.tst_TAB1
(c1, c2, c3)
SELECT
number, number, number
FROM master.dbo.spt_values
WHERE
[type] = N'P'
AND number BETWEEN 1 AND 2047;
INSERT dbo.tst_TAB2 (c1, c2, c3)
VALUES (1, 1, 1);
Run Code Online (Sandbox Code Playgroud)
使用NOT IN
冗余谓词测试查询:
SELECT
T1.c1
FROM tst_TAB1 AS t1
WHERE
t1.c1 NOT IN
(
SELECT
t2.c1
FROM tst_TAB2 AS t2
-- This is redundant!
WHERE
t2.c1 = t1.c1
);
Run Code Online (Sandbox Code Playgroud)
估计的执行计划显示了反半连接后 1 行的估计值:
旁注:事实上,这是另一个(罕见的)错误的一个例子。编写WHERE
子句 ast1.c1 = t2.c1
而不是t2.c1 = t1.c1
允许优化器看到这两个连接谓词实际上是相同的,并且错误不会表现出来。
相同的查询OPTION (QUERYTRACEON 4199)
:
SELECT
T1.c1
FROM tst_TAB1 AS t1
WHERE
t1.c1 NOT IN
(
SELECT
t2.c1
FROM tst_TAB2 AS t2
WHERE
t2.c1 = t1.c1
)
OPTION (QUERYTRACEON 4199);
Run Code Online (Sandbox Code Playgroud)
估计的执行计划现在显示估计为2046 行,这是完全正确的:
我们还可以删除冗余谓词:
SELECT
T1.c1
FROM tst_TAB1 AS t1
WHERE
t1.c1 NOT IN
(
SELECT
t2.c1
FROM tst_TAB2 AS t2
);
Run Code Online (Sandbox Code Playgroud)
执行计划碰巧使用了一个额外的不相关的优化(Stream Aggregate),但重要的一点是加入后估计是正确的,而不必启用 4199:
可以使用NOT IN
语法在多个列上表达反半连接。这些情况将需要 4199。例如,下一个查询连接c1
和c2
:
SELECT
T1.c1
FROM tst_TAB1 AS t1
WHERE
t1.c1 NOT IN
(
SELECT
t2.c1
FROM tst_TAB2 AS t2
WHERE
t2.c2 = t1.c2
);
Run Code Online (Sandbox Code Playgroud)
执行计划显示了错误的 1 行估计:
使用 4199,问题得到解决:
SELECT
T1.c1
FROM tst_TAB1 AS t1
WHERE
t1.c1 NOT IN
(
SELECT
t2.c1
FROM tst_TAB2 AS t2
WHERE
t2.c2 = t1.c2
)
OPTION (QUERYTRACEON 4199);
Run Code Online (Sandbox Code Playgroud)
NOT IN
最好避免以这种方式使用,尤其是由于联机丛书中提到的原因:
与这个问题NOT IN
,并NULLs
已写了很多次。有许多可用的替代语法,其中NOT EXISTS
是我个人的偏好。请注意,更改语法不会避免基数估计错误:
SELECT
T1.c1
FROM dbo.tst_TAB1 AS t1
WHERE
NOT EXISTS
(
SELECT 1
FROM dbo.tst_TAB2 AS t2
WHERE
t2.c1 = t1.c1
AND t2.c2 = t1.c2
);
Run Code Online (Sandbox Code Playgroud)
两列反半连接产生 1 行估计,需要 4199 来修复它。执行计划和之前看到的完全一样,这里不再赘述。该NOT EXISTS
语法确实避免了NULLs
与问题NOT IN
。
我同意 ypercube 的其他观察。
NOLOCK
在查询中的每个表上撒上提示是一种糟糕的代码味道。如果查询可以真正容忍READ UNCOMMITTED
事务语义,则明确设置隔离级别。
TOP
没有ORDER BY
是糟糕代码的另一个迹象。TOP
需要一个ORDER BY
条款来定义什么TOP
意思。永远不要依赖观察到的行为,使用明确的顶级ORDER BY
来获得保证。
INNER LOOP JOIN
和通常的连接提示,意味着FORCE ORDER
查询提示。这严重限制了优化器的自由,并且通常会被误解和误用。切勿使用您不完全理解的提示。
您提供的链接说该错误仅影响多于一列的连接:
请注意,只有在连接中涉及多个连接列时才会遇到此问题,如上例所示。
而且我不明白你为什么这样写NOT IN
(d.doc2_id = d2a.doc2_id
在子查询中添加条件。)它是多余的(除非连接的列可以为空 - 是吗?),所以你可以写NOT IN
为:
AND d.doc2_id NOT IN (
SELECT d2a.doc2_id
FROM assentor.emcsdbuser.doc2_address d2a
WHERE d2a.address_type_cd = 'FRM'
)
Run Code Online (Sandbox Code Playgroud)
或与NOT EXISTS
:
AND NOT EXISTS (
SELECT 1
FROM assentor.emcsdbuser.doc2_address d2a
WHERE d.doc2_id = d2a.doc2_id
AND d2a.address_type_cd = 'FRM'
)
Run Code Online (Sandbox Code Playgroud)
尝试两者并检查基数估计问题是否已解决。
其他注意事项:
address_type_cd
吗?NOLOCK
?TOP
没有ORDER BY
每次执行可能会给你不同的结果。 归档时间: |
|
查看次数: |
1454 次 |
最近记录: |