SQL Server查询间歇性性能问题
最近,我们在SQL Server(2016)上遇到了一个特定查询的性能问题。我看到的问题是,性能问题令人难以置信地不一致,我不确定如何改进 该表详细说明:SQL Server查询间歇性性能问题,sql,sql-server,sql-server-2016,Sql,Sql Server,Sql Server 2016,最近,我们在SQL Server(2016)上遇到了一个特定查询的性能问题。我看到的问题是,性能问题令人难以置信地不一致,我不确定如何改进 该表详细说明: CREATE TABLE ContactRecord ( ContactSeq BIGINT NOT NULL , ApplicationCd VARCHAR(2) NOT NULL , StartDt DATETIME2 NOT NULL , EndDt DATETIME2 , EndStateCd VARCHAR(3) ,
CREATE TABLE ContactRecord
(
ContactSeq BIGINT NOT NULL
, ApplicationCd VARCHAR(2) NOT NULL
, StartDt DATETIME2 NOT NULL
, EndDt DATETIME2
, EndStateCd VARCHAR(3)
, UserId VARCHAR(10)
, UserTypeCd VARCHAR(2)
, LineId VARCHAR(3)
, CallingLineId VARCHAR(20)
, DialledLineId VARCHAR(20)
, ChannelCd VARCHAR(2)
, SubChannelCd VARCHAR(2)
, ServicingAgentCd VARCHAR(7)
, EucCopyTimestamp VARCHAR(30)
, PRIMARY KEY (ContactSeq)
, FOREIGN KEY (ApplicationCd) REFERENCES ApplicationType(ApplicationCd)
, FOREIGN KEY (EndStateCd) REFERENCES EndStateType(EndStateCd)
, FOREIGN KEY (UserTypeCd) REFERENCES UserType(UserTypeCd)
)
CREATE TABLE TransactionRecord
(
TransactionSeq BIGINT NOT NULL
, ContactSeq BIGINT NOT NULL
, TransactionTypeCd VARCHAR(3) NOT NULL
, TransactionDt DATETIME2 NOT NULL
, PolicyId VARCHAR(10)
, ProductId VARCHAR(7)
, EucCopyTimestamp VARCHAR(30)
, Detail VARCHAR(1000)
, PRIMARY KEY (TransactionSeq)
, FOREIGN KEY (ContactSeq) REFERENCES ContactRecord(ContactSeq)
, FOREIGN KEY (TransactionTypeCd) REFERENCES TransactionType(TransactionTypeCd)
)
当前记录计数:
2000万ContactRecord
9000万交易记录
select
UserId,
max(StartDt) as LastLoginDate
from
ContactRecord
where
ContactSeq in
(
select
ContactSeq
from
TransactionRecord
where
ContactSeq in
(
select
ContactSeq
from
ContactRecord
where
UserId in
(
'1234567890',
'1234567891' -- Etc.
)
)
and TransactionRecord.TransactionTypeCd not in
(
'122'
)
)
and ApplicationCd not in
(
'1',
'4',
'5'
)
group by
UserId;
现在查询不是很好,可以使用连接进行改进,但是它基本上可以工作。
我遇到的问题是,我们的数据作业需要输入大约7100个用户ID。然后将其分成500人一组。对于每500个,它们将用于此查询中的in
子句中。在子句中的中,此查询的前14次执行包含500项,执行得很好。结果将在大约15-20秒内返回
问题在于最后一次执行此查询时剩余的100个给定值。它似乎永远不会完成。它只是挂着。在我们的数据作业中,它在10分钟后超时。我不知道为什么。我不是SQL Server的专家,所以我不确定如何调试它。我独立执行了每个子查询,然后用返回的数据替换了子查询的内容。对每个子查询执行此操作效果良好
这里非常感谢您的任何帮助,因为我不知道如何在大量参数下如此一致地工作,但仅在一小部分参数下不工作
编辑
我这里有三个执行计划的例子。请注意,每一个测试都是在测试服务器上执行的,并且几乎都是立即执行的,因为这个测试的数据非常少
这是500个参数的执行计划,在生产中执行良好,大约在15-20秒内返回:
这是119个参数的执行计划,在10分钟后数据作业中超时:
这是5个参数的执行计划,执行良好。此查询未在数据作业中显式执行,只是为了进行比较:
在所有情况下,SSMS均发出以下警告:
/*
Missing Index Details from SQLQuery2.sql
The Query Processor estimates that implementing the following index could improve the query cost by 26.3459%.
*/
/*
USE [CloasIvr]
GO
CREATE NONCLUSTERED INDEX [<Name of Missing Index, sysname,>]
ON [dbo].[TransactionRecord] ([TransactionTypeCd])
INCLUDE ([ContactSeq])
GO
*/
/*
SQLQuery2.sql中缺少索引详细信息
查询处理器估计,实现以下索引可以将查询成本提高26.3459%。
*/
/*
使用[CloasIvr]
去
创建非聚集索引[]
在[dbo].[TransactionRecord]([TransactionTypeCd])
包括([ContactSeq])
去
*/
这是这个问题的根本原因吗?如果看不到发生了什么,就很难确切地知道发生了什么-尤其是那些失败的。“良好”运行的执行计划可能会有所帮助,但我们只是猜测在糟糕的运行中会出现什么问题
我最初的猜测(类似于我的评论)是,对它的预期的估计是非常错误的,它创建了一个非常糟糕的计划
尤其是TransactionRecord表,其detail
列为1000个字符,可能会出现大量嵌套循环的大问题
索引
我建议的第一件事是索引——特别是要a)只包含这些数据所需的数据子集,b)以有用的方式对它们进行排序
我认为以下两个索引似乎会有所帮助
CREATE INDEX IX_ContactRecord_User ON ContactRecord
(UserId, ContactSeq)
INCLUDE (ApplicationCD, Startdt);
CREATE INDEX IX_TransactionRecord_ContactSeq ON TransactionRecord
(ContactSeq, TransactionTypeCd);
这些都是“覆盖索引”,也都是以有帮助的方式进行排序的。
或者,您可以用稍微修改过的版本替换第一个版本(在ContactSeq上首先排序),但我认为上面的版本会更有用
CREATE INDEX IX_ContactRecord_User2 ON ContactRecord
(ContactSeq)
INCLUDE (ApplicationCD, Startdt, UserId);
另外,关于TransactionRecord上的索引-如果这是唯一一个使用该索引的查询,您可以通过创建以下索引来改进它
CREATE INDEX IX_TransactionRecord_ContactSeq_Filtered ON TransactionRecord
(ContactSeq, TransactionTypeCd)
WHERE (TransactionTypeCD <> '122');
然后将查询的中间部分替换为
where
ContactSeq in
(
select
ContactSeq
from
ContactRecord CR
INNER JOIN #Users U ON CR.UserID = U.UserID
)
and TransactionRecord.TransactionTypeCd not in
(
'122'
)
简化查询
我尝试过简化查询,结果是:
select CR.UserId,
max(CR.StartDt) as LastLoginDate
from ContactRecord CR
INNER JOIN TransactionRecord TR ON CR.ContactSeq = TR.ContactSeq
where TR.TransactionTypeCd not in ('122')
AND CR.ApplicationCd not in ('1', '4', '5')
AND CR.UserId in ('1234567890', '1234567891') -- etc
group by UserId;
或者(使用临时表)
简化查询的一个优点是,它还可以帮助SQLServer获得良好的估计;这反过来有助于它获得良好的执行计划
当然,您需要测试上述返回的记录在您的情况下是否完全相同-我没有要测试的数据集,因此我无法100%确定这些简化版本是否与原始版本匹配。下载Adam Machanic的“Whoisactive”,并使用它对运行缓慢的查询进行故障排除,或者,您可以获取其中一个“良好”运行的执行计划和另一个较慢运行的执行计划,并将它们上载到“粘贴计划”并更新您的问题。imo很可能,对于较小的数字,它会根据基数估计值(行数估计值)创建不同的执行计划-然后,较小插入的部分计划包括一些低效的内容,例如嵌套循环,或对其中一个表进行多次完整扫描。我建议1轮500,1轮只有5或10(因为100挂起),两者都设置统计时间,IO打开代码>和显示。此外,如果您需要有关如何改进的帮助,您可以更改什么?是否允许您更改查询、使用临时表和/或向表添加索引?@seanb我将使用实际执行计划重试。在这里用曲线球攻击你的理论;较小列表中的确切项目数为120。这个挂起了,我已经放了20分钟没有结果了。若我从列表中删除13项,它几乎会立即返回(12 less是同一个故事,但任何13都会立即返回。你会期望这样吗?就改进而言,理论上任何事情都可以做,但我感兴趣的是找到导致这种情况似乎永远不会返回的原因。你是否为外键列添加了索引?特别是在将它们与in
和not in条件:与主键约束不同,创建外键约束不会自动创建
select CR.UserId,
max(CR.StartDt) as LastLoginDate
from ContactRecord CR
INNER JOIN TransactionRecord TR ON CR.ContactSeq = TR.ContactSeq
where TR.TransactionTypeCd not in ('122')
AND CR.ApplicationCd not in ('1', '4', '5')
AND CR.UserId in ('1234567890', '1234567891') -- etc
group by UserId;
select CR.UserId,
max(CR.StartDt) as LastLoginDate
from ContactRecord CR
INNER JOIN #Users U ON CR.UserID = U.UserID
INNER JOIN TransactionRecord TR ON CR.ContactSeq = TR.ContactSeq
where TR.TransactionTypeCd not in ('122')
AND CR.ApplicationCd not in ('1', '4', '5')
group by UserId;