是否可以自动通知我SQL Server中的长时间阻塞?


8

大约每周一次,我必须解决SQL Server 2005数据库上的阻塞链,这是由Access 2003前端的长期读取锁定引起的。每当用户打开某个表单时,该锁便被取出;当用户完成滚动或关闭该表单时,该锁便被释放。由于我们的许多用户打开此表单作为参考,因此这些锁会保留一段时间。对表的任何更新都会导致阻塞,并且突然之间没有人可以从该表中进行选择,因为他们都在等待第一个锁。这对我们来说是个大问题,因为许多应用程序都依赖于此数据。我知道这种锁定行为是Access与链接表一起工作的一部分。

我一直在通过Activity Monitor解决问题,方法是在发现问题时杀死SELECT进程中的Head Blocker。这是一个问题,不仅是因为我花了一些时间手动进行操作,而且还因为它是被动的。到我听说时,对于许多人来说已经是一个问题。

我想知道是否有一种自动方法来检查这些持久的封锁链,并通过电子邮件发送或自动解决问题。逻辑看起来很简单(“如果匹配此SELECT查询的任何进程被阻塞超过一分钟,请通知我/杀死它”),但我不知道如何使用SQL Server来实现。

对于它的价值,我认为正确的解决方案是修复或重写应用程序。但是,由于部门的政治原因,在接下来的几个月中这不是一个选择,因此我正在寻找一个权宜之计。


Answers:


9

您是否考虑过使用快照隔离?在数据库中启用read_committed_snapshot将导致所有读取(选择)均为无锁:

alter database [...] set read_committed_snapshot on;

没有应用程序更改。快照下的某些语义会发生变化,您的应用程序可能会做出怪异的反应,但这是例外,并非常规。绝大多数应用程序没有发现任何区别,它们只是获得了免费的性能提升。

无论如何,我还是要回答最初的问题:如何检测(并可能杀死)长时间运行的查询。实际上,引擎已经为您做到了。超过阈值时会引发一个事件:阻塞的流程报告事件类。该阈值是通过阻止的过程阈值Option配置的。任何跟踪事件都可以转换为事件通知,并且事件通知可以激活过程。连接点,您将获得按需激活的代码,当引擎检测到查询已超过执行时间阈值时,该代码就会运行。没有轮询,没有监控。请注意,通知是异步的,在您处理它时,查询可能已经完成,因此必须考虑在内。

这是一个例子:

use msdb;
go

create queue blocked_process_report_queue;
go

create service blocked_process_report_service
    on queue blocked_process_report_queue
    ([http://schemas.microsoft.com/SQL/Notifications/PostEventNotification]);

create event notification blocked_process_report_notification
    on server
    for BLOCKED_PROCESS_REPORT
    to service N'blocked_process_report_service',
          N'current database';
go  

sp_configure 'show advanced options', 1 ;
GO
RECONFIGURE ;
GO
sp_configure 'blocked process threshold', 20 ;
GO
RECONFIGURE ;

现在在一个新查询中设置一个WAITFOR期望的通知:

use msdb;
waitfor(
   receive cast(message_body as xml), * 
   from  blocked_process_report_queue);

继续并造成一些阻塞。我使用了一个创建表但没有提交的过程,并尝试从另一个查询窗口中选择表。在20秒内(我在上面配置的阈值),我收到了阻止报告:

<blocked-process-report>
  <blocked-process>
    <process id="process1b5a24ca8" ...>
      <executionStack>
        <frame line="1" stmtstart="-1"... />
      </executionStack>
      <inputbuf>
          select * from t   </inputbuf>
    </process>
  </blocked-process>
  <blocking-process>
    <process status="sleeping" spid="51" ...>
      <executionStack />
      <inputbuf>
         begin transaction
         create table t (a int)   </inputbuf>
    </process>
  </blocking-process>
</blocked-process-report>

我将把这个过程包装成一个自动化过程的任务留给读者。是的,队列/服务/激活的过程必须位于中[msdb]


我还没有,但是我一定会继续阅读下去!我应该寻找什么样的怪异?如果通常可以提高性能,是否有原因默认情况下未启用快照隔离?
勇士鲍勃

在提供的链接中追踪该链接,通读链接,并了解它如何适用于您的情况
swasheck 2012年

3
我建议阅读“ 将不同结果与RCSI和已提交读进行比较”以及最后的链接。如果您有多语句UDF,则特别需要担心。涉及READ_COMMITTED_SNAPSHOT下的UDF的读取似乎不一致。最终,您需要测试。但同样,大多数情况下没有明显的效果。
Remus

1
我同意,该应用程序没有可见效果。在数据库系统中,您需要关注tempdb。read_committed_snapshot有更多的负载。
Grant Fritchey 2012年

1
@AlexKuznetsov:RCSI的部署方式揭示了它的本质:只需一次更改就可以部署到DB,并且它静默将已提交的内容映射到每个语句的快照。这一切对我来说就是“拼命尝试修复无法更改的损坏应用程序”。OP目前正在考虑每N分钟杀死一次阻止进程。在这种情况下,对RCSI进行测试驱动似乎很合理。我从经验中知道这RCSI案件的数量有助于和它破事发生问题时,远远超过了案件。
Remus Rusanu 2012年

5

您可以构建自己的监视工具,也可以寻求可以为您提供解决方案的第三方解决方案。如果您有兴趣构建自己的数据库,则取决于您使用的SQL Server版本。如果是2005年,则可以使用Blocked Process Report跟踪事件。如果您运行的是2008或更高版本,建议您使用等效的扩展事件blocked_process_report。Jonathan Kehayias 对如何使用它有很好的记录

如果您正在查看第三者产品,则Red Gate软件的SQL Monitor已阻止了进程并内置了长期运行的进程警报。


3

尽管这不能解决如何将问题通知您的问题,但是此过程将向您展示如何查询以查看是否存在阻塞。如果您输入正确的参数,它也会为您生成kill命令。

希望这能给您一些想法。

IF (object_id('Who') IS NOT NULL)
BEGIN
  print 'Dropping procedure: Who'
  drop procedure Who
END
print 'Creating procedure: Who'
GO
CREATE PROCEDURE Who
(
  @db varchar(100) = '%',
  @kill char(1) = 'N',
  @tran char(1) = 'N'
)
as

/*  This proc should be rewritten to take advantage of:
  select * from sys.dm_exec_connections
  select * from sys.dm_exec_sessions
  select * from sys.dm_exec_requests
*/



---------------------------------------------------------------------------------------------------
-- Date Created: July 17, 2007
-- Author:       Bill McEvoy
-- Description:  This procedure gives a summary report and a detailed report of the logged-in
--               processes.  This procedure is a derivative of sp_who3 which I wrote in 2002.
--               
---------------------------------------------------------------------------------------------------
-- Date Revised: 
-- Author:       
-- Reason:       
---------------------------------------------------------------------------------------------------
set nocount on

---------------------------------------------------------------------
-- Validate input parameters                                       --
---------------------------------------------------------------------


---------------------------------------------------------------------
-- M A I N   P R O C E S S I N G                                   --
---------------------------------------------------------------------
--                                                                 --
-- Generate login summary report                                   --
--                                                                 --
--                                                                 --
---------------------------------------------------------------------


---------------------------------------------------------------------
-- Generate login summary report                                   --
---------------------------------------------------------------------

select 'loginame'   = convert(char(30),loginame),
       'connection' = count(*),
       'phys_io'    = str(sum(physical_io),10),
--       'cpu'        = sum(cpu),
       'cpu(mm:ss)' = str((sum(cpu)/1000/60),12) + ':' + case 
                                            when left((str(((sum(cpu)/1000) % 60),2)),1) = ' ' then stuff(str(((sum(cpu)/1000) %60),2),1,1,'0') 
                                            else str(((sum(cpu)/1000) %60),2)
                                         end,
       'wait_time'  = str(sum(waittime),12),
       'Total Memory(MB)' = convert(decimal(12,2),sum(convert(float,memusage) * 8192.0 / 1024.0 / 1024.0))
from master.dbo.sysprocesses
where db_name(dbid) like @db
  and not (loginame = 'sa' and program_name = '' and db_name(dbid) = 'master')
group by loginame
order by 3 desc



---------------------------------------------------------------------
-- Generate detailed activity report                               --
---------------------------------------------------------------------

select 'loginame'     = left(loginame, 30),
       'hostname'     = left(hostname,25),
       'database'     = left(db_name(dbid),25),
       'spid'         = str(spid,4,0),
       'block'        = str(blocked,5,0),
       'phys_io'      = str(physical_io,10,0),
       'cpu(mm:ss)'   = str((cpu/1000/60),10) + ':' + case when left((str(((cpu/1000) % 60),2)),1) = ' ' then stuff(str(((cpu/1000) % 60),2),1,1,'0') else str(((cpu/1000) % 60),2) END,
       'mem(MB)'      = str((convert(float,memusage) * 8192.0 / 1024.0 / 1024.0),8,2),
       'program_name' = left(program_name,50),
       'command'      = cmd,
       'lastwaittype' = left(lastwaittype,20),
       'login_time'   = convert(char(19),login_time,120),
       'last_batch'   = convert(char(19),last_batch,120),
       'status'       = left(nt_username,20)
  from master..sysprocesses
where db_name(dbid) like @db
  and not (loginame = 'sa' and program_name = '' and db_name(dbid) = 'master')
order by 5,4


---------------------------------------------------------------------
-- Generate KILL commands                                          --
---------------------------------------------------------------------

IF (upper(@Kill) = 'Y')
BEGIN
  select 'kill' + ' ' + str(spid,4,0)
    from master..sysprocesses
  where db_name(dbid) like @db
    and not (loginame = 'sa' and program_name = '' and db_name(dbid) = 'master')
  order by spid
END



---------------------------------------------------------------------
-- Report on open transactions                                     --
---------------------------------------------------------------------

IF (UPPER(@Tran) = 'Y')
BEGIN

  -- Create the temporary table to accept the results.
  IF (object_id('tempdb..#Transactions') is NOT NULL)
    DROP TABLE #Transactions
  CREATE TABLE #Transactions
  (
    DatabaseName    varchar(30),
    TransactionName varchar(25),
    Details         sql_variant 
  )

  -- Execute the command, putting the results in the table.
  exec sp_msforeachdb '
  INSERT INTO #Transactions (TransactionName, Details)
     EXEC (''DBCC OPENTRAN([?]) WITH TABLERESULTS, NO_INFOMSGS'');
  update #Transactions 
     set DatabaseName = ''[?]''
   where DatabaseName is NULL'

  -- Display the results.
  SELECT * FROM #Transactions order by transactionname
END


go
IF (object_id('Who') IS NOT NULL)
  print 'Procedure created'
ELSE
  print 'Procedure NOT created'
GO


exec who @tran=Y

在让他更好地研究阻塞问题之前,您正在给他锤子。我想您最好更改条件以仅终止MSACCESS会话:D。
玛丽安2012年

我只是想说明如何开始调查...虽然这是一个古老的过程...可能在2012
datagod

2

我建议阅读以下MSDN 论坛主题。这是由于对SQL Server数据库的访问引起的锁定。建议主要是使用NOLOCK提示通过查询访问表,以免造成任何锁定问题。NOLOCK并不是最佳解决方案,因为它可能导致其他问题,但可以减少大多数锁定问题。

更好的解决方案是实现Remus的想法,即在数据库上设置快照隔离。或仅对发现导致阻塞的某些连接实施快照隔离级别。

为了适当地监视您的服务器是否存在阻塞问题,我建议:

  • 建立一个服务器端跟踪,它将监视超过x秒的阻塞问题(我说5个就足够了);
  • 每天保存较高的痕迹,以便您至少有最近30天的历史记录以查看趋势和模式;
  • 每小时工作一次,研究当日的跟踪文件,并通过电子邮件将任何有趣的阻止情况发送给您;

如果您希望对此问题做出积极响应,而不是每小时都有工作来监视跟踪,请使其每分钟运行一次并杀死所有领先的阻塞Access会话。


0

遵循@Remus Rusanu的出色回答,我已经完成了读者的任务,将事件连接到存储过程。

在我的情况下,sp会将阻塞事件的xml写入表中,但是您可以在该位置随意执行任何操作。

因此,遵循Remus的代码queueservicenotification从上方简单复制/粘贴创建,和。添加sp_configure选项,您就基本设置好了。

剩下要做的唯一一件事就是

  • 创建不带参数的SP。
  • 为SP创建表以将数据写入其中(例如,您的SP可能有所不同)
  • 激活SP上的 queue

激活SP后,事件将立即开始流入您的表中。

我发现,如果SP出现错误,队列会立即自动停用。在这种情况下,您需要进入Server Studio并在队列条目的上下文菜单中再次激活它([msdb]->Service Broker->Warteschlangen德语版本)。

我花了很多时间才能完成此工作并在文档中找到正确的位置,因此我认为这对其他人也有帮助。我正在使用SQLServer 2005。

创建不带参数的SP

CREATE PROCEDURE sp_addrecord
AS
  DECLARE @RecvReqDlgHandle UNIQUEIDENTIFIER;
  DECLARE @RecvReqMsg XML;
  DECLARE @RecvReqMsgName sysname;

  WHILE (1=1)
  BEGIN

    BEGIN TRANSACTION;

    WAITFOR
    ( RECEIVE TOP(1)
        @RecvReqDlgHandle = conversation_handle,
        @RecvReqMsg = message_body,
        @RecvReqMsgName = message_type_name
      FROM blocked_process_report_queue
    ), TIMEOUT 5000;

    IF (@@ROWCOUNT = 0)
    BEGIN
      ROLLBACK TRANSACTION;
      BREAK;
    END

    IF @RecvReqMsgName = 
        N'http://schemas.microsoft.com/SQL/Notifications/EventNotification'
    BEGIN
        print 'Insert SQL to pdix_lock_events'
        INSERT INTO pdix_lock_events
               VALUES(null, cast( @RecvReqMsg as xml));

    END
    ELSE IF @RecvReqMsgName =
        N'http://schemas.microsoft.com/SQL/ServiceBroker/EndDialog'
    BEGIN
       END CONVERSATION @RecvReqDlgHandle;
    END
    ELSE IF @RecvReqMsgName =
        N'http://schemas.microsoft.com/SQL/ServiceBroker/Error'
    BEGIN
       END CONVERSATION @RecvReqDlgHandle;
    END

    COMMIT TRANSACTION;

  END
GO

创建pdix_lock_events

USE [msdb]
GO

/****** Object:  Table [dbo].[pdix_lock_events]    Script Date: 05/06/2015 17:48:36 ******/
SET ANSI_NULLS ON
GO

SET QUOTED_IDENTIFIER ON
GO

CREATE TABLE [dbo].[pdix_lock_events](
    [locktime] [timestamp] NULL,
    [lockevent] [xml] NOT NULL
) ON [PRIMARY]

GO

激活SP上的 queue

alter queue blocked_process_report_queue with activation( 
    procedure_name=sp_addrecord, 
    max_queue_readers = 1, 
    status = on, 
    execute as 'dbo');  
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.