我在存储过程上遇到了很大的麻烦。因为当我检查基准测试结果时,我意识到“ MatchxxxReferencesByIds”的平均LastElapsedTimeInSecond为“ 240.25”毫秒。您可以检查我的存储过程吗?我需要您的帮助来改善我的水平。
ALTER PROCEDURE [Common].[MatchxxxReferencesByIds]
(@refxxxIds VARCHAR(MAX),
@refxxxType NVARCHAR(250))
BEGIN
SET NOCOUNT ON;
BEGIN TRAN
DECLARE @fake_tbl TABLE (xxxid NVARCHAR(50))
INSERT INTO @fake_tbl
SELECT LTRIM(RTRIM(split.a.value('.', 'NVARCHAR(MAX)'))) AS fqdn
FROM
(SELECT
CAST ('<M>' + REPLACE(@refxxxIds, ',', '</M><M>') + '</M>' AS XML) AS data
) AS a
CROSS APPLY
data.nodes ('/M') AS split(a)
SELECT [p].[ReferencedxxxId]
FROM [Common].[xxxReference] AS [p]
WHERE ([p].[IsDeleted] = 0)
AND (([p].[ReferencedxxxType] COLLATE Turkish_CI_AS = @refxxxType COLLATE Turkish_CI_AS )
AND [p].[ReferencedxxxId] COLLATE Turkish_CI_AS IN (SELECT ft.xxxid COLLATE Turkish_CI_AS FROM @fake_tbl ft))
COMMIT;
END;
答案 0 :(得分:4)
一个人只能在不知道表的架构,索引和数据大小的情况下进行假设。
硬编码归类可以防止使用在ReferencedEntityId
列上的任何索引来防止查询优化器。字段名称和示例数据'423423,423423,423432,23423'
仍建议这是一个数字列(int?bigint?)。不需要排序规则,变量的列类型应该与表的类型匹配。
最后,a.value
可以直接返回int
或bigint
,这意味着拆分查询可以重写为:
declare @refEntityIds nvarchar(max)='423423,423423,423432,23423';
DECLARE @fake_tbl TABLE (entityid bigint PRIMARY KEY, INDEX IX_TBL(Entityid))
INSERT INTO @fake_tbl
SELECT split.a.value('.', 'bigint') AS fqdn
FROM
(SELECT
CAST ('<M>' + REPLACE(@refEntityIds, ',', '</M><M>') + '</M>' AS XML) AS data
) AS a
CROSS APPLY
data.nodes ('/M') AS split(a)
输入数据包含一些重复项,因此entityid
不能是主键。
之后,查询可以更改为:
SELECT [p].[ReferencedEntityId]
FROM [Common].[EntityReference] AS [p]
WHERE [p].[IsDeleted] = 0
AND [p].[ReferencedEntityType] COLLATE Turkish_CI_AS = @refEntityType COLLATE Turkish_CI_AS
AND [p].[ReferencedEntityId] IN (SELECT ft.entityid FROM @fake_tbl ft)
下一个问题是硬编码排序规则。除非它与列的实际排序规则匹配,否则它会阻止服务器使用覆盖该列的任何索引。如何解决此问题取决于实际的数据统计。也许列的排序规则必须更改,或者用ReferencedEntityId
过滤后的行太少,以至于没有好处。
最后,IsDeleted
无法建立索引。它是{/ {1}}列,其值为1/0,或者是另一个仍包含0/1的数字列。查询优化器不会使用在选择行方面如此糟糕的索引,因为它实际上更快来仅扫描其他条件返回的行。
一般规则是将最有选择性的索引列放在第一位。数据库将所有列组合在一起以创建一个“键”值,并从中构造一个B +树索引。密钥的选择性越高,需要扫描的索引节点越少。
bit
仍可以在filtered index中用于仅索引未删除的列。这使查询优化器可以消除搜索中不需要的列。结果索引也将更小,这意味着相同数量的IO操作将在内存中加载更多索引页并允许更快的查找。
所有这些都意味着IsDeleted
应该具有这样的索引。
EntityReference
如果排序规则不匹配,则CREATE NONCLUSTERED INDEX IX_EntityReference_ReferenceEntityID
ON Common.EntityReference (ReferenceEntityId, ReferenceEntityType)
WHERE IsDeleted =0;
将不会用于搜索。如果这是最常见的情况,我们可以从索引中删除ReferenceEntityType
并将其放在ReferenceEntityType
子句中。尽管该字段仍可用于过滤而不必从实际表中加载数据,但它不会成为索引键的一部分:
INCLUDE
当然,如果是最常见的情况,则应改为更改列的排序规则
答案 1 :(得分:2)
我认为您不需要TRAN。您只是将逗号分隔的值“切碎”到@variable表中。并进行选择。 TRAN不需要这里。
尝试exists
SELECT [p].[ReferencedEntityId]
FROM [Common].[EntityReference] AS [p]
WHERE ([p].[IsDeleted] = 0)
AND (([p].[ReferencedEntityType] COLLATE Turkish_CI_AS = @refEntityType COLLATE Turkish_CI_AS )
AND EXISTS (SELECT 1 FROM @fake_tbl ft WHERE ft.entityid COLLATE Turkish_CI_AS = [p].[ReferencedEntityId] COLLATE Turkish_CI_AS )
3。
请参见https://www.sqlshack.com/efficient-creation-parsing-delimited-strings/
以不同方式解析定界字符串。
文章引用:
Microsoft的内置功能提供了方便的解决方案 并表现良好。它并不比XML快,但显然 的编写方式提供了易于优化的执行计划。 逻辑读取也更高。虽然我们看不到 涵盖并确切了解Microsoft如何实现此功能,我们 最不方便的功能是拆分字符串 SQL Server附带。请注意,分隔符已传入此 函数的大小必须为1。换句话说,您不能使用 带有多字符定界符的STRING_SPLIT,例如“”,“”。
答案 2 :(得分:2)
根据存储过程的执行计划,使它执行缓慢的原因是您要使用XML的部分。
让我们重新思考解决方案:
我创建了一个这样的表:
CREATE TABLE [Common].[EntityReference]
(
IsDeleted BIT,
ReferencedEntityType VARCHAR(100),
ReferencedEntityId VARCHAR(10)
);
GO
并这样操作(将1M条记录插入其中):
DECLARE @i INT = 1000000;
DECLARE @isDeleted BIT,
@ReferencedEntityType VARCHAR(100),
@ReferencedEntityId VARCHAR(10);
WHILE @i > 0
BEGIN
SET @isDeleted =(SELECT @i % 2);
SET @ReferencedEntityType = 'TEST' + CASE WHEN @i % 2 = 0 THEN '' ELSE CAST(@i % 2 AS VARCHAR(100)) END;
SET @ReferencedEntityId = CAST(@i AS VARCHAR(10));
INSERT INTO [Common].[EntityReference]
(
IsDeleted,
ReferencedEntityType,
ReferencedEntityId
)
VALUES (@isDeleted, @ReferencedEntityType, @ReferencedEntityId);
SET @i = @i - 1;
END;
让我们分析您的代码:
您有一个用逗号分隔的输入(@refEntityIds
),您想将其分割,然后针对这些值运行查询。 (您的PC中SP的子树成本大约为376)要这样做,您可以采用不同的方法:
1。将表变量传递给包含refEntityIds的存储过程
2。利用STRING_SPLIT函数分割字符串 让我们看一下示例查询:
INSERT INTO @fake_tbl
SELECT value
FROM STRING_SPLIT(@refEntityIds, ',');
使用此功能,您的代码将获得显着的性能提升。(子树成本:6.19,不包含以下索引)但是此功能在SQL Server 2008中不可用!
您可以使用此函数的替代项(请参见:https://stackoverflow.com/a/54926996/1666800),并将查询更改为此函数(子树成本仍然约为6.19):
INSERT INTO @fake_tbl
SELECT value FROM dbo.[fn_split_string_to_column](@refEntityIds,',')
在这种情况下,您将再次看到显着的性能改进。
您还可以在[Common].[EntityReference]
表上创建非聚集索引,该索引也有一点点的性能提升。但是请先考虑创建索引,然后再创建索引,这可能会对您的DML操作产生负面影响:
CREATE NONCLUSTERED INDEX [Index Name] ON [Common].[EntityReference]
(
[IsDeleted] ASC
)
INCLUDE ([ReferencedEntityType],[ReferencedEntityId])
如果我没有此索引(假设我已用我的替换拆分解决方案),则子树成本为:6.19,当我添加上述索引时,子树成本降至4.70,最后当我更改索引时索引到下一个,子树代价是5.16
CREATE NONCLUSTERED INDEX [Index Name] ON [Common].[EntityReference]
(
[ReferencedEntityType] ASC,
[ReferencedEntityId] ASC
)
INCLUDE ([IsDeleted])
由于@PanagiotisKanavos,以下索引的性能甚至比上述索引更好(子树成本:3.95):
CREATE NONCLUSTERED INDEX IX_EntityReference_ReferenceEntityID
ON Common.EntityReference (ReferencedEntityId)
INCLUDE(ReferencedEntityType)
WHERE IsDeleted =0;
还请注意,对本地表变量使用事务几乎没有效果,可能您可以忽略它。
答案 3 :(得分:2)
如果[p]。[ReferencedEntityId]将为整数,则无需应用COLLATE子句。您可以直接应用IN条件。
[p].[ReferencedEntityId] IN (SELECT ft.entityid AS FROM @fake_tbl ft))