Dapatkah saya secara otomatis diberitahu tentang pemblokiran yang berkepanjangan di SQL server?

8

Sekitar sekali seminggu saya harus menyelesaikan rantai pemblokiran pada database SQL Server 2005, yang disebabkan oleh kunci baca yang berumur panjang dari front-end Access 2003. Kunci diambil ketika pengguna membuka formulir tertentu dan dilepaskan setelah pengguna selesai menggulir formulir atau menutupnya. Karena banyak pengguna kami membuka formulir ini sebagai referensi, kunci ini tetap ada untuk sementara waktu. Pembaruan apa pun ke tabel menyebabkan pemblokiran, dan tiba-tiba tidak ada yang dapat memilih dari tabel ini karena mereka semua menunggu di kunci pertama. Ini merupakan masalah bagi kami, karena banyak aplikasi mengandalkan data ini. Saya mengerti bahwa perilaku penguncian ini adalah bagian dari cara Access bekerja dengan tabel tertaut.

Saya telah memecahkan masalah dari Activity Monitor, dengan membunuh proses SELECT mana pun yang merupakan Pemblokir Utama setiap kali saya mengetahuinya. Ini adalah masalah tidak hanya karena saya butuh waktu untuk melakukannya secara manual, tetapi juga karena itu reaktif. Pada saat saya mendengarnya, itu sudah menjadi masalah bagi banyak orang.

Saya ingin tahu apakah ada cara otomatis untuk memeriksa rantai pemblokiran yang tahan lama ini, dan apakah akan diemail atau memiliki masalah diselesaikan secara otomatis. Logikanya tampak cukup jelas ("jika ada proses yang cocok dengan kueri SELECT ini telah memblokir lebih dari satu menit, beri tahu saya / bunuh saja") tetapi saya tidak tahu bagaimana menerapkannya dengan SQL Server.

Untuk apa nilainya, saya pikir solusi yang tepat adalah memperbaiki atau menulis ulang aplikasi. Namun, karena politik departemen, ini bukan pilihan untuk beberapa bulan ke depan, jadi saya mencari penghenti sementara.

Prajurit Bob
sumber

Jawaban:

9

Sudahkah Anda mempertimbangkan untuk menggunakan isolasi snapshot ? Mengaktifkan read_committed_snapshot dalam database akan menyebabkan semua pembacaan (pilihan) bebas dari kunci:

alter database [...] set read_committed_snapshot on;

Tidak ada perubahan aplikasi. Beberapa semantik berubah di bawah snapshot dan aplikasi Anda mungkin bereaksi aneh, tapi itu pengecualian bukan norma. Sebagian besar aplikasi tidak melihat perbedaan, mereka hanya mendapatkan peningkatan kinerja gratis.

Bagaimanapun, saya juga menjawab pertanyaan awal : bagaimana mendeteksi (dan mungkin membunuh) permintaan yang sudah berjalan lama. Sebenarnya, mesin sudah melakukan itu untuk Anda. Ada peristiwa yang dimunculkan ketika ambang dilewatkan: Proses yang Diblokir Laporan Acara . Ambang dikonfigurasi melalui Opsi ambang proses yang diblokir . Setiap acara jejak dapat diubah menjadi Pemberitahuan Acara dan pemberitahuan acara dapat mengaktifkan prosedur . Hubungkan titik-titik dan Anda memiliki kode diaktifkan berdasarkan permintaan yang berjalan ketika mesin mendeteksi kueri yang telah melewati ambang waktu eksekusi. Tidak ada polling, tidak ada pemantauan. Perhatikan bahwa notifikasi tidak sinkron, pada saat Anda memprosesnya, kueri mungkin telah selesai sehingga harus diperhitungkan.

Berikut ini sebuah contoh:

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 ;

Sekarang di kueri baru siapkan WAITFORpemberitahuan menunggu:

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

Dan lanjutkan dan menyebabkan beberapa penyumbatan. Saya menggunakan proses yang membuat tabel dan tidak melakukan, dan dari jendela kueri lain saya mencoba memilih dari tabel. Dalam 20 detik (ambang konfigurasi saya di atas) saya mendapat laporan pemblokiran:

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

Saya akan meninggalkan tugas membungkus ini menjadi proses otomatis sebagai latihan untuk pembaca. Dan ya, prosedur antrian / layanan / diaktifkan harus dalam [msdb].

Remus Rusanu
sumber
Saya belum, tapi saya pasti akan membacanya! Keanehan macam apa yang harus saya cari? Jika ini umumnya meningkatkan kinerja, apakah ada alasan isolasi snapshot tidak diaktifkan secara default?
Prajurit Bob
mengejar tautan di dalam tautan yang disediakan, baca ini dan lihat bagaimana itu berlaku untuk situasi Anda
swasheck
3
Saya sarankan membaca Membandingkan Hasil Berbeda dengan RCSI & Baca Komit dan tautan di akhir. Kekhawatiran khusus dijamin jika Anda memiliki UDF multi-pernyataan, misalnya. Membaca yang melibatkan UDF di bawah READ_COMMITTED_SNAPSHOT mungkin tampak tidak konsisten . Pada akhirnya Anda perlu menguji. Tetapi sekali lagi, sebagian besar kasus tidak ada efek yang terlihat.
Remus Rusanu
1
Tidak ada efek yang terlihat pada aplikasi, saya setuju. Dalam sistem basis data, Anda ingin mengawasi tempdb. Ada lebih banyak memuat di sana dari read_committed_snapshot.
Grant Fritchey
1
@AlexKuznetsov: Cara verry RCSI digunakan menunjukkan sifatnya: digunakan oleh satu perubahan tunggal ke DB dan secara diam - diam memetakan read-commit ke snapshot untuk setiap pernyataan. Ini semua berbunyi bagi saya 'upaya putus asa untuk memperbaiki aplikasi yang rusak yang tidak dapat diubah'. OP saat ini mempertimbangkan untuk membunuh proses pemblokiran setiap N menit . Memberikan RCSI test drive sepertinya cukup masuk akal bagi saya dalam kasus seperti itu. Saya tahu dari pengalaman bahwa jumlah kasus yang RCSI bantu dan tidak memecahkan banyak hal melebihi kasus ketika masalah terjadi.
Remus Rusanu
5

Anda dapat membuat alat pemantauan sendiri atau mencari solusi pihak ketiga yang dapat menyediakannya untuk Anda. Jika Anda tertarik untuk membangun sendiri, itu tergantung pada versi SQL Server yang Anda gunakan. Jika tahun 2005, Anda dapat menggunakan acara pelacakan Laporan Proses Diblokir . Jika Anda menjalankan 2008 atau di atas, saya sarankan menggunakan acara diperpanjang yang setara, diblokir_process_report. Jonathan Kehayias memiliki tulisan yang bagus tentang cara menggunakannya.

Jika Anda melihat produk pihak ke-3, Monitor SQL perangkat lunak Red Gate telah memblokir proses dan peringatan proses yang telah berjalan lama.

Berikan Fritchey
sumber
3

Meskipun ini tidak membahas cara memberi tahu Anda tentang masalah ini, prosedur ini akan menunjukkan kepada Anda bagaimana cara menanyakan apakah ada pemblokiran. Ini juga akan menghasilkan perintah kill untuk Anda, jika Anda memasukkan parameter yang benar.

Semoga ini memberi Anda beberapa ide.

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
datagod
sumber
Anda memberinya palu sebelum membiarkannya mempelajari lebih baik masalah pemblokiran :-). Saya akan mengatakan Anda sebaiknya mengubah kondisi untuk hanya membunuh sesi MSACCESS: D.
Marian
Saya hanya mencoba menunjukkan bagaimana memulai investigasi ... ini adalah proc lama ... sepertinya tidak akan berfungsi pada 2012
datagod
2

Saya sarankan membaca topik forum MSDN berikut . Ini tentang penguncian yang disebabkan oleh Akses ke SQL Server db. Saran utamanya adalah mengakses tabel dengan kueri menggunakan petunjuk NOLOCK, sehingga tidak akan menyebabkan masalah penguncian. NOLOCK bukan solusi terbaik, karena dapat menyebabkan masalah lain, tetapi akan mengurangi sebagian besar masalah penguncian Anda.

Solusi yang lebih baik adalah dengan mengimplementasikan ide Remus, mengatur isolasi snapshot pada database Anda. Atau terapkan tingkat isolasi snapshot hanya untuk koneksi tertentu yang Anda temukan menyebabkan pemblokiran.

Agar dapat memonitor server Anda dengan benar untuk memblokir masalah, saya sarankan:

  • membangun jejak sisi server yang akan memantau masalah pemblokiran lebih dari x detik (saya akan mengatakan 5 cukup baik);
  • simpan jejak atas setiap hari sehingga Anda memiliki riwayat selama setidaknya 30 hari terakhir untuk melihat tren dan pola;
  • memiliki pekerjaan per jam yang mempelajari file jejak hari ini dan email kepada Anda setiap situasi pemblokiran yang menarik;

Jika Anda menginginkan respons proaktif terhadap masalah ini, alih-alih memiliki pekerjaan setiap jam untuk memantau jejak, buatlah itu berjalan setiap menit dan matikan sesi Akses pemblokiran terkemuka apa pun.

Marian
sumber
0

Mengikuti jawaban luar biasa @Remus Rusanu, saya telah melakukan tugas pembaca untuk menghubungkan acara ke prosedur yang tersimpan.

Dalam kasus saya sp akan menulis xml acara pemblokiran ke tabel, tetapi Anda bebas melakukan apa pun yang Anda inginkan pada posisi itu.

Jadi, ikuti kode Remus dan buat queue, dengan servicedan notificationdengan copy / paste sederhana dari atas. Tambahkan sp_configureopsi dan Anda pada dasarnya sudah diatur.

Satu-satunya hal yang harus dilakukan adalah

  • Buat SP tanpa argumen.
  • Buat tabel untuk SP untuk menuliskan data (misalnya, SP Anda mungkin beragam)
  • Aktifkan SP di queue

Segera setelah Anda mengaktifkan SP, acara akan mulai mengalir ke meja Anda.

Saya menemukan bahwa antrian segera menonaktifkan dirinya sendiri, jika SP memiliki kesalahan. Dalam hal ini, Anda harus pergi ke Server Studio dan mengaktifkannya lagi di menu konteks entri antrian ([msdb]->Service Broker->Warteschlangen dalam versi Jerman).

Butuh beberapa waktu untuk menyelesaikannya dan menemukan tempat yang tepat dalam dokumentasi, jadi saya kira ini juga bermanfaat bagi orang lain. Saya menggunakan SQLServer 2005.

Buat SP tanpa argumen

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

Buat pdix_lock_eventstabel

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

Aktifkan SP di queue

alter queue blocked_process_report_queue with activation( 
    procedure_name=sp_addrecord, 
    max_queue_readers = 1, 
    status = on, 
    execute as 'dbo');  
thst
sumber