Tho*_*mas 1 sql-server optimization execution-plan sql-server-2019
我正在从使用对象 ID 和属性类型作为聚集索引的属性表迁移一些存储“键值”样式的数据(我也尝试过作为非聚集索引):
\nCREATE TABLE [dbo].[#attrs](\n [DataMigrationEventObjectID] [int] NOT NULL,\n [AttributeType] [varchar](128) NOT NULL,\n [AttributeValue] [varchar](255) NULL\n) \nCREATE CLUSTERED INDEX pk ON #attrs ([DataMigrationEventObjectID],AttributeType);\n
Run Code Online (Sandbox Code Playgroud)\n我添加了属性值来选择值,因为数据库中的属性表有很多其他数据,我可以仅为此迁移事件选择它。使用我的测试数据集来填充此表的查询会插入约 3k 行,并且运行时间不到一秒(我的数据集中总共约有 50 个对象,每个对象都有多个属性)。
\n查询中表的连接如下所示,连接聚集索引:
\n INNER JOIN #attrs obj_gvn\n ON obj_gvn.DataMigrationEventObjectID = obj.DataMigrationEventObjectID\n AND obj_gvn.AttributeType = \'GivenName\'\n
Run Code Online (Sandbox Code Playgroud)\n通过对该临时表进行 14 个联接,查询将在几秒钟内完成。如果有 15 个连接,查询需要一分钟,如果有 16 个以上连接,则半小时后仍在运行。
\n我已经检查了所有联接是否存在意外条件,这会导致返回太多行,当它在 1 分钟内返回时,它只返回正确的行,所以我不认为存在意外的笛卡尔联接。设置 MAXDOP 值不会影响它,并且查询运行一分钟时返回的查询计划不会标记任何问题。
\n对于 SQL,我错过了什么,导致它在聚集索引上进行大量联接,理论上应该很快,而且记录数量如此之少?
\n我无法获得实际执行计划,因为查询未完成,并且因为它使用临时表,所以我无法获得其估计计划。我尝试将临时表捏造为数据库中的真实表并生成估计计划,但 2 分钟后该计划仍未生成,因此看起来延迟是在“创建计划”方面
\n粘贴查询的缩短版本的计划:brentozar.com/pastetheplan/ ?id=Hy76dd92i
\n我已经更新了数据库的统计数据以防万一,但它仍然没有生成计划。
\n我过去处理过越来越多有问题的连接查询,其中计划编译仍然是即时的。我觉得它在“生成计划”步骤失败这一事实一定意味着什么。
\n不幸的是,更新到最新的 CU 没有帮助。sp_whoisactive
仅显示 CPU 使用率不断攀升(屏幕截图),其他资源上没有任何看起来有问题的情况。
这是我的开发机器,因此只有一个 SQL 进程处于活动状态,即我正在运行的查询。没有其他任何东西,所以我认为必须是 SQL 尝试生成计划。
\n我怀疑如果我部署了这个,生产会运行得很好,但是\xe2\x80\x99s很奇怪有一个“主键杀死性能的大量连接”问题。我可能会考虑将开发服务器炸掉并从头开始。
\n具有大量联接的查询的编译时间可能变化很大。
原因之一是有 N 个!对 N 个表的内连接进行排序的方法。对于小N来说,这不是问题。对于较大的 N,即使优化器没有尝试对可能的计划空间进行详尽的搜索,这也可能是一个问题。
您可能已经注意到,您提供的执行计划不会按照您编写表的顺序访问表。例如,首先访问表DataMigrationEventObject (别名为“obj”)。没有明显的单个索引可用,因此优化器创建索引交集(使用两个非聚集索引)和键查找(获取列TargetObjectKey1)来物理实现查询的一小部分。
优化器也不限于考虑不同的连接顺序。它可能会探索不同的连接算法(散列、合并、嵌套循环、应用)、不同的索引策略、非连接运算符的放置和类型、并行性等。
如果到目前为止找到的最佳计划是合理的,优化器会采用启发式方法来避免花费太多时间来寻找更好的计划。给定一个相当简单的查询、一个合适的数据库逻辑设计和一个合适的物理实现(例如数据类型和索引),优化器通常会快速找到一个好的低成本计划。这似乎是你过去的经历。
这种安排仍然可能在多种方面出错。如果优化器选择的初始连接顺序与合理的低成本相距甚远,则需要完成大量转换工作才能到达终点。如果数据库没有良好的支持索引,优化器可能会花费大量时间来尝试找到合适的策略。
如果无法准确导出行计数和值分布信息(不限于过时的基表统计信息),则可能会错误地计算候选计划片段的成本。这很容易导致启发式阈值被突破,并且优化器花费过多的精力探索、实施和计算替代方案的成本。
发生这种情况时,有时唯一的解决方案是使用不同的语法、不同的算法(如枢轴建议中所示)来表达数据需求,或者将操作分解为更简单的单独步骤,并使用小型且索引良好的临时表作为保存地区。
一般来说,您的案例中的问题很可能是特定的数据分布,导致早期成本计算不准确并导致优化器工作量过多。
您的查询和数据库存在多个问题,使它们容易受到计划编译时间过长的影响。我不会列出所有这些,因为没有提供完整的架构,并且在提供的执行计划中找到的查询文本在关键点被截断。即使提供了所有必要的信息,答案也会太长。几点:
#attrs
未标记UNIQUE
。根据定义,键是唯一的。不向优化器提供这一关键保证意味着它无法知道对象 id 和类型上的相等谓词最多会返回一行。还有其他后果,太多了,无法列出,但作为一个例子,合并联接必须是多对多而不是一对多。这些事情对于勘探和成本计算很重要。#attrs
表有一个名为“clus”的重复索引。问题中提到了这一点,但这是向优化器提供的不明显的索引选择的一个示例,但没有任何好处。使聚集索引UNIQUE
成为适当的键。#attrs
有重复的谓词DataMigrationEventID = @MigrationEventID
。这是无害的,但是在与计算机打交道时,注意细节很重要(它们非常字面意思)。#theClients
使用创建临时表然后立即返回该表中的所有结果没有任何好处(只有成本)SELECT INTO
。如果使用得当,临时表是一个强大的调整工具。这不是此类用法的示例。对于任何想要在本地生成计划的人,我能够从问题和执行计划中推断出以下内容。可能存在一些遗漏和不准确之处。当然没有准确的统计数据,只有原始表基数:
表格
CREATE TABLE dbo.DataMigrationEventObject
(
DataMigrationEventObjectID integer NOT NULL PRIMARY KEY,
DataMigrationEventID integer NOT NULL,
DataMigrationObjectType varchar(255) NOT NULL,
TargetObjectKey1 integer NOT NULL,
TargetObjectKey2 integer NOT NULL,
TargetObjectKey3 integer NOT NULL,
IsCompleted bit NOT NULL,
IsDeleted bit NOT NULL,
INDEX DataMigrationEventID (DataMigrationEventID),
INDEX DataMigrationObjectType (DataMigrationObjectType)
);
UPDATE STATISTICS dbo.DataMigrationEventObject WITH ROWCOUNT = 8413, PAGECOUNT = 841;
CREATE TABLE dbo.DataMigrationEventObjectAttribute
(
DataMigrationEventObjectID integer NOT NULL PRIMARY KEY,
DataMigrationEventID integer NOT NULL,
AttributeType varchar(128) NOT NULL,
AttributeValue sql_variant NOT NULL,
IsDeleted bit NOT NULL,
);
UPDATE STATISTICS dbo.DataMigrationEventObjectAttribute WITH ROWCOUNT = 56900, PAGECOUNT = 56900;
CREATE TABLE dbo.#attrs
(
DataMigrationEventObjectID integer NOT NULL,
AttributeType varchar(128) NOT NULL,
AttributeValue varchar(255) NULL
);
CREATE CLUSTERED INDEX pk ON #attrs (DataMigrationEventObjectID, AttributeType);
DECLARE @MigrationEventID integer = 23;
INSERT INTO #attrs
SELECT
dmeo.DataMigrationEventObjectID,
a.AttributeType,
CAST(a.AttributeValue as varchar(255)) AttributeValue
FROM DataMigrationEventObject dmeo
JOIN DataMigrationEventObjectAttribute a
ON a.DataMigrationEventObjectID = dmeo.DataMigrationEventObjectID
WHERE
dmeo.DataMigrationEventID = @MigrationEventID
AND dmeo.DataMigrationObjectType IN ('BpClient','xxxxxxxClientMapping')
AND dmeo.DataMigrationEventID = @MigrationEventID;
CREATE INDEX clus ON #attrs (DataMigrationEventObjectID, AttributeType);
UPDATE STATISTICS #attrs WITH ROWCOUNT = 2173, PAGECOUNT = 21;
Run Code Online (Sandbox Code Playgroud)
询问
DECLARE @MigrationEventID integer = 23;
SELECT
dmeo.DataMigrationEventObjectID xxObjectID,
a_clientid.AttributeValue ClientID,
a_tnt.AttributeValue ClientExternalID,
a_tnt.AttributeValue TenantID,
int_att.AttributeValue ClientInternalID,
obj_gvn.AttributeValue GivenName,
obj_oth.AttributeValue OtherName,
obj_fam.AttributeValue FamilyName,
obj_pref.AttributeValue PreferredName,
obj_title.AttributeValue Title,
obj_dob.AttributeValue DateOfBirth,
obj_email.AttributeValue Email,
obj_hphone.AttributeValue HomePhone,
obj_wphone.AttributeValue WorkPhone,
obj_add1.AttributeValue Address1,
obj_add2.AttributeValue Address2,
obj_sub.AttributeValue Suburb,
obj_state.AttributeValue StateID,
obj_post.AttributeValue Postcode,
obj_sex.AttributeValue SexID
/*,
obj_del.AttributeValue IsDeleted,
obj_inact.AttributeValue IsInactive,
obj_pref_corr.AttributeValue PreferredCorrespondenceMethodID,
obj_eth.AttributeValue EthnicityID
obj_em_title.AttributeValue EmergencyContactTitle,
obj_em_surn.AttributeValue EmergencyContactSurname,
obj_em_gvn.AttributeValue EmergencyContactGivenName,
obj_em_add.AttributeValue EmergencyContactAddress,
obj_em_sub.AttributeValue EmergencyContactSuburb,
obj_em_post.AttributeValue EmergencyContactPostcode,
obj_em_phone.AttributeValue EmergencyContactPhone,
obj_em_phone2.AttributeValue EmergencyContactPhone2,
obj_em_rel.AttributeValue EmergencyContactRelationship,
obj_nok_title.AttributeValue NextOfKinContactTitle,
obj_nok_surn.AttributeValue NextOfKinContactSurname,
obj_nok_gvn.AttributeValue NextOfKinContactGivenName,
obj_nok_add.AttributeValue NextOfKinContactAddress,
obj_nok_sub.AttributeValue NextOfKinContactSuburb,
obj_nok_post.AttributeValue NextOfKinContactPostcode,
obj_nok_phone.AttributeValue NextOfKinContactPhone,
obj_nok_phone2.AttributeValue NextOfKinContactPhone2,
obj_nok_rel.AttributeValue NextOfKinContactRelationship*/
INTO #theClients
FROM DataMigrationEventObject dmeo
INNER JOIN #attrs a_clientid
ON a_clientid.DataMigrationEventObjectID = dmeo.DataMigrationEventObjectID
AND a_clientid.AttributeType = 'ClientID'
INNER JOIN #attrs a_clientextid
ON a_clientextid.DataMigrationEventObjectID = dmeo.DataMigrationEventObjectID
AND a_clientextid.AttributeType = 'ClientExternalID'
INNER JOIN #attrs a_tnt
ON a_tnt.DataMigrationEventObjectID = dmeo.DataMigrationEventObjectID
AND a_tnt.AttributeType = 'TenantID'
INNER JOIN (
#attrs int_att
INNER JOIN DataMigrationEventObject obj
ON obj.DataMigrationEventObjectID = int_att.DataMigrationEventObjectID
AND obj.DataMigrationObjectType = 'BpClient'
)
ON int_att.AttributeType = 'InternalID'
AND obj.DataMigrationEventID = dmeo.DataMigrationEventID
AND obj.TargetObjectKey1 = cast(a_clientextid.AttributeValue as int)
INNER JOIN #attrs obj_gvn
ON obj_gvn.DataMigrationEventObjectID = obj.DataMigrationEventObjectID
AND obj_gvn.AttributeType = 'GivenName'
INNER JOIN #attrs obj_oth
ON obj_oth.DataMigrationEventObjectID = obj.DataMigrationEventObjectID
AND obj_oth.AttributeType = 'OtherName'
INNER JOIN #attrs obj_fam
ON obj_fam.DataMigrationEventObjectID = obj.DataMigrationEventObjectID
AND obj_fam.AttributeType = 'FamilyName'
INNER JOIN #attrs obj_pref
ON obj_pref.DataMigrationEventObjectID = obj.DataMigrationEventObjectID
AND obj_pref.AttributeType = 'PreferredName'
INNER JOIN #attrs obj_title
ON obj_title.DataMigrationEventObjectID = obj.DataMigrationEventObjectID
AND obj_title.AttributeType = 'Title'
INNER JOIN #attrs obj_dob
ON obj_dob.DataMigrationEventObjectID = obj.DataMigrationEventObjectID
AND obj_dob.AttributeType = 'DateOfBirth'
INNER JOIN #attrs obj_email
ON obj_email.DataMigrationEventObjectID = obj.DataMigrationEventObjectID
AND obj_email.AttributeType = 'Email'
INNER JOIN #attrs obj_hphone
ON obj_hphone.DataMigrationEventObjectID = obj.DataMigrationEventObjectID
AND obj_hphone.AttributeType = 'HomePhone'
INNER JOIN #attrs obj_wphone
ON obj_wphone.DataMigrationEventObjectID = obj.DataMigrationEventObjectID
AND obj_wphone.AttributeType = 'WorkPhone'
INNER JOIN #attrs obj_add1
ON obj_add1.DataMigrationEventObjectID = obj.DataMigrationEventObjectID
AND obj_add1.AttributeType = 'Address1'
INNER JOIN #attrs obj_add2
ON obj_add2.DataMigrationEventObjectID = obj.DataMigrationEventObjectID
AND obj_add2.AttributeType = 'Address2'
INNER JOIN #attrs obj_sub
ON obj_sub.DataMigrationEventObjectID = obj.DataMigrationEventObjectID
AND obj_sub.AttributeType = 'Suburb'
INNER JOIN #attrs obj_state
ON obj_state.DataMigrationEventObjectID = obj.DataMigrationEventObjectID
AND obj_state.AttributeType = 'StateID'
INNER JOIN #attrs obj_post
ON obj_post.DataMigrationEventObjectID = obj.DataMigrationEventObjectID
AND obj_post.AttributeType = 'Postcode'
INNER JOIN #attrs obj_sex
ON obj_sex.DataMigrationEventObjectID = obj.DataMigrationEventObjectID
AND obj_sex.AttributeType = 'SexID'
--INNER JOIN #attrs obj_del
--ON obj_del.DataMigrationEventObjectID = obj.DataMigrationEventObjectID
--AND obj_del.AttributeType = 'IsDeleted'
--INNER JOIN #attrs obj_inact
--ON obj_inact.DataMigrationEventObjectID = obj.DataMigrationEventObjectID
--AND obj_inact.AttributeType = 'IsInactive'
--INNER JOIN #attrs obj_pref_corr
--ON obj_pref_corr.DataMigrationEventObjectID = obj.DataMigrationEventObjectID
--AND obj_pref_corr.AttributeType = 'PreferredCorrespondenceMethodID'
--INNER JOIN #attrs obj_eth
--ON obj_eth.DataMigrationEventObjectID = obj.DataMigrationEventObjectID
--AND obj_eth.AttributeType = 'EthnicityID'
WHERE
dmeo.DataMigrationEventID = @MigrationEventID
AND dmeo.IsDeleted = 0
AND dmeo.DataMigrationObjectType = 'xxxxxxxClientMapping';
Run Code Online (Sandbox Code Playgroud)
该演示不会在兼容性级别 130 下的 SQL Server 2019 实例上重现过多的编译时间,但考虑到缺乏准确的统计信息以及可能不同的硬件和实例配置(主要是内存),所有这些都会影响计划选择,这并不意外。
使用原始基数模型可以重现更长的编译时间(以及相当疯狂的计划)。将以下提示添加到最终查询中:
OPTION (MAXDOP 1, USE HINT ('FORCE_LEGACY_CARDINALITY_ESTIMATION'))
Run Code Online (Sandbox Code Playgroud)
我已经使用了MAXDOP 1
,因为你的实例是这样配置的。
归档时间: |
|
查看次数: |
250 次 |
最近记录: |