web-dev-qa-db-ja.com

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

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

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

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

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

8
Warrior Bob

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

alter database [...] set read_committed_snapshot on;

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

とにかく、私はoriginalの質問にも答えたいと思います:長時間実行されているクエリを検出する(そしておそらく殺す)方法です。実際、エンジンはすでにそれを行っています。しきい値を超えたときに発生するイベントがあります: ブロックされたプロセスレポートのイベントクラス 。しきい値は blocked process threshold Option で設定されます。トレースイベントは イベント通知 に変換でき、イベント通知は プロシージャをアクティブ化 に変換できます。ドットを接続すると、エンジンが実行時間のしきい値を超えたクエリを検出したときに実行されるオンデマンドのアクティブ化されたコードがあります。ポーリングなし、監視なし。通知はasynchronousであることに注意してください。処理するときまでに、クエリが完了している可能性があるため、それを考慮する必要があります。

次に例を示します。

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]になければなりません

9
Remus Rusanu

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

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

5
Grant Fritchey

これは、問題を通知する方法は扱いませんが、この手順では、ブロッキングが存在するかどうかを確認するためのクエリ方法を示します。また、正しいパラメーターを渡した場合は、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
3
datagod

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

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

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

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

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

2
Marian

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

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

したがって、Remusのコードに従い、上記の単純なコピー/貼り付けでqueueservicenotificationを作成します。 sp_configureオプションを追加すると、基本的に設定は完了です。

残された唯一のことは

  • 引数なしでSPを作成します。
  • SPにデータを書き込むためのテーブルを作成します(私の例では、SPは異なる場合があります)
  • queueでSPをアクティブにします

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');  
0
thst