无阻塞,良好的执行计划,查询缓慢:为什么?

时间:2019-07-12 14:00:21

标签: sql-server

我有一个查询,有时需要几分钟才能完成。多个进程正在同时运行,但没有阻塞(我正在运行扩展事件会话,可以看到其他事务的阻塞,因此用于检查记录的事件的查询正在运行)。 从查询计划缓存来看,执行计划是一个很好的计划:在SSMS中运行它,它需要少于100个IO,并且没有表或索引扫描。

用户可能有不同的计划,但是如果我添加了在所有表上使用扫描的提示(有些表相当大),它仍然会在1秒钟左右返回。因此,最糟糕的执行计划仍然不会导致查询花费几分钟。

排除了阻塞和错误的执行计划,还有什么可以使查询变慢?

值得指出的一件事是,尽管代码未引用SQL Server,但SQL Server使用了我们创建的索引视图(我们正在使用SQL Server Enterprise)。该索引视图具有覆盖索引以支持查询,并且正在使用它-再次,执行计划非常好。最初的查询使用的是NOLOCK,我观察到索引视图的任何行或页面都没有锁(因此SQL Server尊重我们的锁定提示,即使它正在访问索引视图而不是基础表也是如此)。这是有道理的,否则我会期望看到阻塞。

我们在其他一些查询中使用索引视图,但是我们在SQL代码中引用了它们(并指定了NOLOCK,NOEXPAND)。我没有看到这些查询有任何问题,并且我不知道我们告诉优化器使用的索引视图与优化器本身决定使用的索引视图之间应该有任何区别,但是我看到的是暗示有。

有什么想法吗?我还有什么要看的吗?

这是查询:

execute sp_executesql 
N'SELECT DISTINCT p.policy_id
           , p.name_e AS policy_name_e
           , p.name_l AS policy_name_l
FROM       patient_visit_nl_view AS pv
INNER JOIN swe_cashier_transaction_nl_view AS ct ON ct.patient_visit_id = pv.patient_visit_id
           AND ct.split_date_time IS NOT NULL
INNER JOIN ar_invoice_nl_view AS ai ON ai.ar_invoice_id = ct.invoice_id
           AND ai.company_code = ''KOC''
           AND ai.transaction_status_rcd = ''TEMP''
INNER JOIN policy_nl_view p ON p.policy_id = ai.policy_id
WHERE      pv.patient_id = @pv__patient_id'
, N' @pv__patient_id uniqueidentifier'
, @pv__patient_id = '5D61EDF1-7542-11E8-BFCB-D89EF37315A2'

注意:后缀为_nl_view的视图是从具有NOLOCK的表中选择的(想法是我们将来可以在不影响业务层代码的情况下进行更改)。

您可以在此处查看查询计划:https://www.brentozar.com/pastetheplan/?id=HJI9Lj_WH

IO状态:

Table 'policy'. Scan count 0, logical reads 9, physical reads 0, read-ahead reads 0, lob logical reads 0, lob physical reads 0, lob read-ahead reads 0.
Table 'ar_invoice_cashier_transaction_visit_iview'. Scan count 1, logical reads 5, physical reads 0, read-ahead reads 0, lob logical reads 0, lob physical reads 0, lob read-ahead reads 0.

已采取的锁定(IS锁定所涉及的对象,仅此而已): locks taken

在索引视图的相关部分下方:

CREATE VIEW dbo.ar_invoice_cashier_transaction_visit_iview WITH SCHEMABINDING
AS
SELECT      ai.ar_invoice_id
        , ai.company_code
        , ai.policy_id
        , ai.transaction_status_rcd
        , ct.cashier_transaction_id
        , pv.patient_id
        -- more columns
FROM        dbo.ar_invoice AS ai
INNER JOIN  dbo.swe_cashier_transaction AS ct ON ct.invoice_id = ai.ar_invoice_id AND ct.split_date_time IS NOT NULL
INNER JOIN  dbo.patient_visit AS pv ON pv.patient_visit_id = ct.patient_visit_id

CREATE UNIQUE CLUSTERED INDEX XPKar_invoice_cashier_transaction_visit_iview ON dbo.ar_invoice_cashier_transaction_visit_iview (ar_invoice_id, cashier_transaction_id)

CREATE INDEX XIE4ar_invoice_cashier_transaction_visit_iview ON dbo.ar_invoice_cashier_transaction_visit_iview (patient_id, transaction_status_rcd, company_code) INCLUDE (policy_id)

到目前为止很好。

但是每隔几天(而不是一天中的同一时间),事情就会变成梨形,查询需要几分钟,实际上却超时(提供程序的命令超时设置为10分钟)。发生这种情况时,不会发生阻塞。我有一个扩展的事件会话,这是我的查询

DECLARE @event_xml xml;

SELECT      @event_xml = CONVERT(xml, target_data)
FROM        sys.dm_xe_sessions AS s
INNER JOIN  sys.dm_xe_session_targets AS t ON s.address = t.event_session_address
WHERE       s.name = 'Blocking over 10 seconds'

SELECT  DATEADD(hour, DATEDIFF(hour, GETUTCDATE(), GETDATE()), R.c.value('@timestamp', 'datetime')) AS time_stamp
    , R.c.value('(data[@name="blocked_process"]/value[1]/blocked-process-report[1]/blocked-process[1]/process)[1]/@spid', 'int') AS blocked_spid
    , R.c.value('(data[@name="blocked_process"]/value[1]/blocked-process-report[1]/blocked-process[1]/process[1]/inputbuf)[1]', 'varchar(max)') AS blocked_inputbuf
    , R.c.value('(data[@name="blocked_process"]/value[1]/blocked-process-report[1]/blocked-process[1]/process[1]/@waitresource)[1]', 'varchar(max)') AS wait_resource
    , R.c.value('(data[@name="blocked_process"]/value[1]/blocked-process-report[1]/blocking-process[1]/process)[1]/@spid', 'int') AS blocking_spid
    , R.c.value('(data[@name="blocked_process"]/value[1]/blocked-process-report[1]/blocking-process[1]/process[1]/inputbuf)[1]', 'varchar(max)') AS blocking_inputbuf
    , R.c.query('.')
FROM    @event_xml.nodes('/RingBufferTarget/event') AS R(c)
ORDER BY R.c.value('@timestamp', 'datetime') DESC

此查询返回了其他阻塞情况,因此我认为这是正确的。在出现问题(超时)时,没有涉及以上查询或任何其他查询的阻塞情况。

由于没有阻塞,因此我正在研究错误的查询计划的可能性。我没有在缓存中找到一个坏计划(在我被授予远程访问权限之前,我已经建议对表进行sp_recompile编译),所以我试图考虑最糟糕的情况:扫描每个表。应用相关选项,以下是此查询的IO统计信息:

Table 'patient_visit'. Scan count 1, logical reads 4559, physical reads 0, read-ahead reads 7, lob logical reads 0, lob physical reads 0, lob read-ahead reads 0.
Table 'Workfile'. Scan count 0, logical reads 0, physical reads 0, read-ahead reads 0, lob logical reads 0, lob physical reads 0, lob read-ahead reads 0.
Table 'Worktable'. Scan count 0, logical reads 0, physical reads 0, read-ahead reads 0, lob logical reads 0, lob physical reads 0, lob read-ahead reads 0.
Table 'swe_cashier_transaction'. Scan count 9, logical reads 24840, physical reads 0, read-ahead reads 23660, lob logical reads 0, lob physical reads 0, lob read-ahead reads 0.
Table 'ar_invoice'. Scan count 9, logical reads 21247, physical reads 0, read-ahead reads 7074, lob logical reads 0, lob physical reads 0, lob read-ahead reads 0.
Table 'policy'. Scan count 9, logical reads 271, physical reads 0, read-ahead reads 0, lob logical reads 0, lob physical reads 0, lob read-ahead reads 0.
Table 'Worktable'. Scan count 0, logical reads 0, physical reads 0, read-ahead reads 0, lob logical reads 0, lob physical reads 0, lob read-ahead reads 0.

这是执行计划:https://www.brentozar.com/pastetheplan/?id=rJr29s_br

客户拥有一个强大的SQL Server 2012盒子,大量内核(maxdop设置为8),大量内存。早餐会吃掉这个糟糕的查询(大约需要350毫秒)。

为完整起见,以下是涉及的表的行数:

  • ar_invoice:2363527
  • swe_cashier_transaction:2946514
  • Patient_visit:654976
  • 政策:1038
  • ar_invoice_cashier_transaction_visit_iview:1999609

我还针对返回最多行的Patient_id和不存在(即0行)的Patient_id运行了查询。我使用recompile选项运行它们:在两种情况下,优化器都选择了相同的(好的)执行计划。

所以回到问题:没有阻塞,查询计划似乎是好的(即使坏了,查询需要10分钟的时间也不会很坏),那么什么原因会导致这种情况呢? ?

唯一有点不寻常的是,尽管SQL并未从索引视图中进行选择,但优化器还是使用它-这是或应该是一件好事。我知道企业版声称它可以做到这一点,但这是我第一次在野外看到它(尽管我看到了很多相反的情况:在SQL中引用索引视图,但是优化器从视图的基础表)。我很想相信这是相关的。

2 个答案:

答案 0 :(得分:0)

在不了解您的设置的情况下,我将检查其他几件事:

  • 框上的总体CPU和内存使用率是多少,会导致资源争用
  • 如果您的存储位于SAN而非本地存储上,则存储端是否存在争用(如果您对来自不同系统的同一磁盘阵列进行大量读写,则可能会发生这种情况)

答案 1 :(得分:0)

放慢查询速度可能还涉及其他几个因素。我个人并不真正相信SQL Server的优化技术。通常,我建议您对查询进行优化,以使优化器不必进行繁琐的工作,例如在主表上使用Exists / In而不是加入并执行distinct / {{1 }},

group

而不是像上面那样运行查询

select  distinct ia.AttributeCode, ia.AttributeDescription
from    ItemsTable as i 
        inner join ItemAttributesTable as ia on i.AttributeCode = ia.AttributeCode
where   i.Manufacturer = @paramMfr
        and i.MfrYear between @paramYearStart and @paramYearYend

我并不是真正的索引专家,但对于上述情况,我认为select ia.AttributeCode, ia.AttributeDescription from ItemAttributesTable as ia where ia.AttributeCode in ( select i.AttributeCode from ItemsTable as i where i.Manufacturer = @paramMfr and i.MfrYear between @paramYearStart and @paramYearYend ) 中只有1个索引就足够了

可以通过删除视图并直接使用表来进行另一种优化,因为视图也可以在其他表上进行联接,而这在这里并不需要。

总而言之,重点是,当查询优化器找出最佳方案时,可能会遇到超时(称为优化器超时限制)的情况,在这种情况下,它可能会选择制定一个在那个特定时间并不十分好的计划,这就是为什么要使用计划缓存的原因。这就是我建议在此重点关注优化查询的原因,而不是着眼于其超时的原因。

也请检查https://blogs.msdn.microsoft.com/psssql/2018/10/19/understanding-optimizer-timeout-and-how-complex-queries-can-be-affected-in-sql-server/

更新1:

建议:

  1. 使用ItemsTable / Exists,即使您看到与当前查询相同的执行计划,这仍将帮助优化程序几乎始终使用正确的计划
  2. 尝试消除视图并直接使用表格,减少选择列的数量。
  3. 确保已根据给定的参数定义了正确的索引
  4. 尝试将查询分为更小的部分,例如在临时表中选择过滤后的数据,然后使用临时表获取其余详细信息
  5. 尝试谷歌搜索“应用程序中的超时不在SSMS中”,并查看不同的hacks

查询超时的常见原因:

  1. 未定义索引
  2. 提取太多数据
  3. 当您尝试从一个或多个表中读取数据时,它们上有锁。
  4. 参数类型和字段类型的区别,例如,列为varchar而参数类型为nvarchar
  5. 参数嗅探