SQLサーバーでの長時間のブロックを自動的に通知できますか?


8

週に1回程度、Access 2003フロントエンドからの長期にわたる読み取りロックが原因で、SQL Server 2005データベースのブロッキングチェーンを解決する必要があります。ロックは、ユーザーが特定のフォームを開くたびに解除され、ユーザーがフォームのスクロールを終了するか、フォームを閉じると解放されます。多くのユーザーがこのフォームを参照として開いているので、これらのロックはしばらく保持されます。テーブルを更新するとブロッキングが発生し、最初のロックを待機しているため、突然このテーブルから誰も選択できなくなります。多くのアプリがこのデータに依存しているため、これは私たちにとって非常に問題です。このロック動作は、リンクテーブルでのAccessの動作の一部であることを理解しています。

私はアクティビティモニターから問題を解決してきました。ヘッドブロッカーであるSELECTプロセスを見つけたら、そのプロセスを強制終了します。これは、手動で行うのに時間がかかるだけでなく、反応的であるためにも問題です。私がそれを聞いたときまでに、それは多くの人にとってすでに問題になっています。

これらの長期にわたるブロッキングチェーンを自動的にチェックする方法があり、メールで送信されるか、問題が自動的に解決されるかどうかを知りたいです。ロジックは単純明快です(「このSELECTクエリに一致するプロセスが1分以上ブロックされている場合は、通知/強制終了する」)が、SQL Serverでこれを実装する方法がわかりません。

価値があることについては、適切な解決策はアプリを修正または書き直すことだと思います。ただし、部局の政治上の理由により、これは今後数か月間は選択肢になりません。そのため、私は一時的なギャップを探しています。


回答:


9

スナップショット分離の使用を検討しましたか?データベースでread_committed_snapshotを有効にすると、すべての読み取り(選択)がロックされなくなります。

alter database [...] set read_committed_snapshot on;

アプリケーションの変更はありません。一部のセマンティクスはスナップショットの下で変化し、アプリケーション奇妙に反応する可能性がありますが、それは例外ではありません。アプリケーションの大部分は違いに気付かず、無料でパフォーマンスが向上します。

とにかく、私は元の質問にも答えたいと思います:長時間実行されているクエリを検出する方法(そして場合によっては殺す方法)。実際、エンジンはすでにそれを行っています。しきい値を超えたときに発生するイベント:Blocked Process Report Event Classがあります。しきい値は、ブロックされたプロセスのしきい値オプションで構成されます。トレースイベントはすべてイベント通知に変換でき、イベント通知はプロシージャアクティブ化できます。ドットを接続すると、エンジンが実行時間のしきい値を超えたクエリを検出したときに実行されるオンデマンドのアクティブ化されたコードがあります。ポーリングなし、監視なし。ただし、通知は非同期です。、それを処理するときまでに、クエリが完了している可能性があるため、それを考慮する必要があります。

次に例を示します。

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]


まだしていませんが、間違いなく読んでいきます!どんな奇妙さを探したらいいですか?それが一般にパフォーマンスの向上である場合、スナップショット分離がデフォルトで有効になっていない理由はありますか?
ウォリアーボブ

提供されたリンク内でリンクを追跡し、これを読んで、それがあなたの状況にどのように当てはまるかを確認してください
スワシェック2012年

3
最後に、RCSIとのさまざまな結果の比較とコミットの読み取りとリンクを読むことをお勧めします。複数ステートメントのUDFがある場合は、特別な心配が必要です。READ_COMMITTED_SNAPSHOTの下のUDFを含む読み取りは、一貫性がないように見える場合があります。最終的にはテストする必要があります。しかし、繰り返しになりますが、ほとんどの場合、目に見える影響はありません。
Remus Rusanu

1
アプリに目に見える影響はない、と私は同意します。データベースシステムでは、tempdbを監視する必要があります。read_committed_snapshotからの負荷がさらにあります。
Grant Fritchey

1
@AlexKuznetsov:RCSIの展開方法はその性質を明らかにしています。DBへの単一の変更によって展開され、各ステートメントの読み取りコミットされたスナップショットに暗黙的にマッピングされます。これはすべて、「変更できない壊れたアプリを修正するための必死の試み」を私に読みます。OPは現在、N分ごとにブロックプロセスを強制終了することを検討しています。そのような場合、RCSIにテストドライブを提供することは、私にはかなり理にかなっているようです。経験から、RCSIが役立ち、問題を解決しないケースの数は、問題が発生した場合のケースをはるかに上回ります。
Remus Rusanu

5

独自の監視ツールを構築するか、それを提供できるサードパーティのソリューションを検討することができます。独自のビルドに興味がある場合は、使用しているSQL Serverのバージョンによって異なります。2005の場合は、ブロックされたプロセスレポートのトレースイベントを使用できます。2008以降を実行している場合は、同等の拡張イベントblocked_process_reportを使用することをお勧めします。Jonathan Kehayiasが、その使い方についてよく書いています。

サードパーティ製品をご覧になっている場合は、Red GateソフトウェアのSQLモニターがプロセスをブロックし、実行時間の長いプロセスアラートが組み込まれています。


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。
Marian

私は調査を開始する方法を示すためだけにしようとしていた...それは古い
プロシージャ

2

次のMSDN フォーラムトピックを読むことをお勧めします。これは、SQL Serverデータベースへのアクセスによって引き起こされるロックに関するものです。提案は主に、NOLOCKヒントを使用してクエリによってテーブルにアクセスすることです。これにより、ロックの問題が発生しなくなります。NOLOCKは他の問題を引き起こす可能性があるため、最善の解決策ではありませんが、ロックの問題のほとんどは軽減されます。

より良い解決策は、データベースにスナップショット分離を設定するRemusのアイデアを実装することです。または、ブロックの原因となっていることが判明した特定の接続に対してのみ、スナップショット分離レベルを実装します。

サーバーでブロッキングの問題を適切に監視するために、次のことをお勧めします。

  • x秒より長いブロッキングの問題を監視するサーバー側のトレースを作成します(5で十分です)。
  • 上部のトレースを毎日保存して、少なくとも過去30日間の履歴を得て、傾向とパターンを確認します。
  • 今日のトレースファイルを調査し、興味深いブロッキング状況をメールで送信する1時間ごとのジョブがあります。

この問題へのプロアクティブな応答が必要な場合は、トレースを監視するジョブを1時間ごとに行うのではなく、ジョブを1分ごとに実行して、先行するブロッキングAccessセッションを強制終了します。


0

@Remus Rusanuのすばらしい答えに続き、私はイベントをストアドプロシージャに接続するという読者のタスクを実行しました。

私の場合、spはブロッキングイベントのxmlをテーブルに書き込みますが、その位置で好きなことを自由に実行できます。

だから、レムスコードに従い、作成queueserviceおよびnotification上から貼り付ける/単純なコピーで。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');  
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.