sri*_*amn 8 join sql-server optimization execution-plan
我有一张表,其中包含家庭的详细信息,而另一个表则包含与这些家庭相关的所有人员的详细信息。对于家庭表,我使用其中的两列定义了一个主键 - [tempId,n]。对于 person 表,我有一个使用其 3 列定义的主键[tempId,n,sporder]
使用主键上的聚集索引所指示的排序,我为每个家庭[HHID]和每个人的[PERID]记录生成了一个唯一 ID (下面的代码片段用于生成 PERID]:
ALTER TABLE dbo.persons
ADD PERID INT IDENTITY
CONSTRAINT [UQ dbo.persons HHID] UNIQUE;
Run Code Online (Sandbox Code Playgroud)
现在,我的下一步是将每个人与相应的家庭相关联,即;将 a 映射[PERID]到 a [HHID]。两个表之间的人行横道基于两列[tempId,n]。为此,我有以下内部连接语句。
UPDATE t1
SET t1.HHID = t2.HHID
FROM dbo.persons AS t1
INNER JOIN dbo.households AS t2
ON t1.tempId = t2.tempId AND t1.n = t2.n;
Run Code Online (Sandbox Code Playgroud)
我总共有1928783个户籍记录和5239842个人记录。执行时间目前非常高。
现在,我的问题:
我已将SQL Server 2008 为整个脚本生成的执行计划上传到 SQLPerformance.com
Pau*_*ite 20
我很确定表定义接近于此:
CREATE TABLE dbo.households
(
tempId integer NOT NULL,
n integer NOT NULL,
HHID integer IDENTITY NOT NULL,
CONSTRAINT [UQ dbo.households HHID]
UNIQUE NONCLUSTERED (HHID),
CONSTRAINT [PK dbo.households tempId, n]
PRIMARY KEY CLUSTERED (tempId, n)
);
CREATE TABLE dbo.persons
(
tempId integer NOT NULL,
sporder integer NOT NULL,
n integer NOT NULL,
PERID integer IDENTITY NOT NULL,
HHID integer NOT NULL,
CONSTRAINT [UQ dbo.persons HHID]
UNIQUE NONCLUSTERED (PERID),
CONSTRAINT [PK dbo.persons tempId, n, sporder]
PRIMARY KEY CLUSTERED (tempId, n, sporder)
);
Run Code Online (Sandbox Code Playgroud)
我没有这些表或您的数据的统计信息,但以下内容至少会正确设置表基数(页数是猜测):
UPDATE STATISTICS dbo.persons
WITH
ROWCOUNT = 5239842,
PAGECOUNT = 100000;
UPDATE STATISTICS dbo.households
WITH
ROWCOUNT = 1928783,
PAGECOUNT = 25000;
Run Code Online (Sandbox Code Playgroud)
您现在的查询是:
UPDATE P
SET HHID = H.HHID
FROM dbo.households AS H
JOIN dbo.persons AS P
ON P.tempId = H.tempId
AND P.n = H.n;
Run Code Online (Sandbox Code Playgroud)
这产生了相当低效的计划:

该计划的主要问题是散列连接和排序。两者都需要内存授权(散列连接需要构建一个散列表,并且在排序过程中排序需要空间来存储行)。计划资源管理器显示此查询被授予 765 MB:

这是用于一个查询的大量服务器内存!更重要的是,根据行数和大小估计,在执行开始之前,此内存授予是固定的。
如果在执行时内存不足,至少一些哈希和/或排序数据将写入物理tempdb磁盘。这被称为“溢出”,它可能是一个非常缓慢的操作。您可以使用 Profiler 事件Hash Warnings和Sort Warnings跟踪这些溢出(在 SQL Server 2008 中)。
哈希表的构建输入的估计非常好:

排序输入的估计不太准确:

您必须使用 Profiler 进行检查,但我怀疑在这种情况下排序会溢出到tempdb。哈希表也有可能溢出,但这不太明确。
请注意,为该查询保留的内存在哈希表和排序之间分配,因为它们是并发运行的。Memory Fractions 计划属性显示每个操作预期使用的内存授予的相对量。
排序由查询优化器引入,以确保行以聚集键顺序到达聚集索引更新运算符。这促进了对表的顺序访问,这通常比随机访问更有效。
散列连接是一个不太明显的选择,因为它的输入大小相似(无论如何都是近似值)。当一个输入(构建哈希表的那个)相对较小时,哈希连接是最好的。
在这种情况下,优化器的成本模型确定散列连接是三个选项(散列、合并、嵌套循环)中成本较低的一个。
成本模型并不总是正确的。它往往会高估并行合并连接的成本,尤其是随着线程数量的增加。我们可以使用查询提示强制合并连接:
UPDATE P
SET HHID = H.HHID
FROM dbo.households AS H
JOIN dbo.persons AS P
ON P.tempId = H.tempId
AND P.n = H.n
OPTION (MERGE JOIN);
Run Code Online (Sandbox Code Playgroud)
这会产生一个不需要那么多内存的计划(因为合并连接不需要哈希表):

有问题的排序仍然存在,因为合并连接仅保留其连接键(tempId,n)的顺序,但聚集键是(tempId,n,sporder)。您可能会发现合并连接计划的性能并不比散列连接计划好。
我们也可以尝试嵌套循环连接:
UPDATE P
SET HHID = H.HHID
FROM dbo.households AS H
JOIN dbo.persons AS P
ON P.tempId = H.tempId
AND P.n = H.n
OPTION (LOOP JOIN);
Run Code Online (Sandbox Code Playgroud)
此查询的计划是:

这个查询计划被优化器的成本模型认为是最糟糕的,但它确实有一些非常理想的特性。首先,嵌套循环连接不需要内存授权。其次,它可以保留Persons表中的键顺序,因此不需要显式排序。您可能会发现此计划执行得相对较好,甚至可能已经足够好。
嵌套循环计划的最大缺点是它在单个线程上运行。这个查询很可能受益于并行性,但优化器认为在这里这样做没有任何好处。这也不一定正确。不幸的是,没有内置的查询提示来获取并行计划,但有一种未公开的方式:
UPDATE t1
SET t1.HHID = t2.HHID
FROM dbo.persons AS t1
INNER JOIN dbo.households AS t2
ON t1.tempId = t2.tempId AND t1.n = t2.n
OPTION (LOOP JOIN, QUERYTRACEON 8649);
Run Code Online (Sandbox Code Playgroud)
使用QUERYTRACEON提示启用跟踪标志 8649会生成以下计划:

现在我们有一个避免排序的计划,不需要额外的连接内存,并有效地使用并行性。您应该会发现此查询的性能比替代查询要好得多。
我的文章Forcing a Parallel Query Execution Plan 中有关并行性的更多信息:
| 归档时间: |
|
| 查看次数: |
13092 次 |
| 最近记录: |