Kann ich automatisch über eine längere Blockierung in SQL Server informiert werden?

8

Ungefähr einmal pro Woche muss ich eine Blockierungskette in einer SQL Server 2005-Datenbank auflösen, die durch eine langlebige Lesesperre von einem Access 2003-Frontend verursacht wird. Die Sperre wird aufgehoben, wenn ein Benutzer ein bestimmtes Formular öffnet, und wird freigegeben, sobald der Benutzer das Bildlauf durch das Formular abgeschlossen oder es geschlossen hat. Da viele unserer Benutzer dieses Formular als Referenz öffnen, bleiben diese Sperren eine Weile bestehen. Jede Aktualisierung der Tabelle führt zur Blockierung, und plötzlich kann niemand mehr aus dieser Tabelle auswählen, da alle auf die erste Sperre warten. Dies ist für uns ein ziemliches Problem, da viele Apps auf diesen Daten basieren. Ich verstehe, dass dieses Sperrverhalten Teil der Funktionsweise von Access mit verknüpften Tabellen ist.

Ich habe das Problem mit Activity Monitor gelöst, indem ich den SELECT-Prozess, der der Head Blocker ist, beendet habe, wenn ich davon erfahre. Dies ist nicht nur deshalb ein Problem, weil ich Zeit brauche, um es manuell zu machen, sondern auch, weil es reaktiv ist. Als ich davon höre, war es für viele Menschen bereits ein Problem.

Ich würde gerne wissen, ob es eine automatische Möglichkeit gibt, nach diesen langlebigen Blockierungsketten zu suchen und entweder per E-Mail benachrichtigt zu werden oder das Problem automatisch zu lösen. Die Logik scheint recht einfach zu sein ("Wenn ein Prozess, der mit dieser SELECT-Abfrage übereinstimmt, länger als eine Minute blockiert wurde, benachrichtigen Sie mich / beenden Sie ihn"), aber ich weiß nicht, wie ich dies mit SQL Server implementieren soll.

Ich denke, die richtige Lösung besteht darin, die App zu reparieren oder neu zu schreiben. Aufgrund der Abteilungspolitik ist dies jedoch für die nächsten Monate keine Option, daher suche ich nach einer Notlösung.

Krieger Bob
quelle

Antworten:

9

Haben Sie überlegt, die Snapshot-Isolation zu verwenden ? Durch Aktivieren von read_committed_snapshot in der Datenbank werden alle Lesevorgänge (Auswahlen) gesperrt:

alter database [...] set read_committed_snapshot on;

Keine Anwendungsänderungen. Einige Semantiken ändern sich unter Snapshot und Ihre Anwendung reagiert möglicherweise komisch, aber das ist die Ausnahme, nicht die Norm. Die überwiegende Mehrheit der Anwendungen bemerkt keinen Unterschied, sie erhalten lediglich eine kostenlose Leistungssteigerung.

Wie auch immer, ich möchte auch die ursprüngliche Frage beantworten: Wie man eine lange laufende Abfrage erkennt (und möglicherweise tötet). Eigentlich macht der Motor das schon für Sie. Beim Überschreiten eines Schwellenwerts wird ein Ereignis ausgelöst : Ereignisklasse "Blockierter Prozessbericht" . Der Schwellenwert wird über die Option Blockierter Prozessschwellenwert konfiguriert . Jedes Ablaufverfolgungsereignis kann in eine Ereignisbenachrichtigung umgewandelt werden, und Ereignisbenachrichtigungen können Prozeduren aktivieren . Verbinden Sie die Punkte, und Sie haben bei Bedarf aktivierten Code, der ausgeführt wird, wenn die Engine eine Abfrage erkennt, die einen Ausführungszeitschwellenwert überschritten hat. Keine Abfrage, keine Überwachung. Beachten Sie jedoch, dass die Benachrichtigung asynchron istZum Zeitpunkt der Verarbeitung ist die Abfrage möglicherweise abgeschlossen, sodass dies berücksichtigt werden muss.

Hier ist ein Beispiel:

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 ;

Richten Sie nun in einer neuen Abfrage eine WAITFORerwartete Benachrichtigung ein:

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

Und mach weiter und verursache eine Blockade. Ich habe einen Prozess verwendet, der eine Tabelle erstellt und nicht festgeschrieben hat, und in einem anderen Abfragefenster habe ich versucht, aus der Tabelle auszuwählen. In 20 Sekunden (mein konfigurierter Schwellenwert oben) erhielt ich den Sperrbericht:

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

Ich überlasse es dem Leser, dies als Übung in einen automatisierten Prozess zu packen. Und ja, die Warteschlange / der Dienst / die aktivierte Prozedur muss sich in befinden [msdb].

Remus Rusanu
quelle
Ich habe es nicht getan, aber ich werde es auf jeden Fall nachlesen! Nach was für einer Verrücktheit sollte ich suchen? Wenn es sich im Allgemeinen um eine Leistungssteigerung handelt, gibt es einen Grund, warum die Snapshot-Isolation nicht standardmäßig aktiviert ist?
Krieger Bob
den Link innerhalb des angegebenen Link zu jagen, durch Lesen dieses und sehen , wie es auf Ihre Situation zutrifft
swasheck
3
Ich empfehle das Lesen verschiedener Ergebnisse mit RCSI & Read Committed und die Links am Ende. Besondere Sorgen sind angebracht, wenn Sie UDFs mit mehreren Anweisungen haben, z. Lesevorgänge mit UDFs unter READ_COMMITTED_SNAPSHOT scheinen inkonsistent zu sein . Letztendlich müssen Sie testen. Aber auch in den meisten Fällen gibt es keine sichtbaren Auswirkungen.
Remus Rusanu
1
Keine sichtbaren Auswirkungen auf die App, stimme ich zu. Im Datenbanksystem möchten Sie die Tempdb im Auge behalten. Es gibt dort mehr Last von read_committed_snapshot.
Grant Fritchey
1
@AlexKuznetsov: Die Art und Weise, wie RCSI bereitgestellt wird, zeigt seine Natur: Wird durch eine einzige Änderung an der Datenbank bereitgestellt und ordnet für jede Anweisung stillschweigend Lese- Commit -Snapshots zu. Dies alles lautet für mich "verzweifelter Versuch, eine kaputte App zu reparieren, die nicht geändert werden kann". Das OP erwägt derzeit , Blockierungsprozesse alle N Minuten abzubrechen . In diesem Fall erscheint es mir durchaus vernünftig, RCSI eine Probefahrt zu geben. Ich weiß aus Erfahrung, dass die Anzahl der Fälle, in denen RCSI hilft und die Dinge nicht kaputt macht, die Fälle, in denen Probleme auftreten, bei weitem überwiegt.
Remus Rusanu
5

Sie können entweder Ihr eigenes Überwachungstool erstellen oder nach einer Lösung eines Drittanbieters suchen, die eine für Sie bereitstellt. Wenn Sie daran interessiert sind, Ihre eigene Version zu erstellen, hängt dies davon ab, mit welcher Version von SQL Server Sie arbeiten. Wenn es 2005 ist, können Sie das Trace-Ereignis Blocked Process Report verwenden . Wenn Sie 2008 oder höher ausführen, würde ich empfehlen, das entsprechende erweiterte Ereignis blocked_process_report zu verwenden. Jonathan Kehayias hat eine gute Beschreibung, wie man es benutzt.

Wenn Sie sich Produkte von Drittanbietern ansehen, hat der SQL Monitor der Red Gate-Software Prozess- und lang laufende Prozesswarnungen integriert.

Grant Fritchey
quelle
3

Obwohl hier nicht beschrieben wird, wie Sie über das Problem informiert werden, zeigt Ihnen dieses Verfahren, wie Sie abfragen, ob eine Blockierung vorliegt. Es werden auch Kill-Befehle für Sie generiert, wenn Sie den richtigen Parameter übergeben.

Hoffe das gibt dir ein paar Ideen.

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
quelle
Du gibst ihm den Hammer, bevor du ihn die Blockierungsprobleme besser studieren lässt :-). Ich würde sagen, Sie sollten die Bedingung so ändern, dass nur MSACCESS-Sitzungen beendet werden: D.
Marian
Ich habe nur versucht zu zeigen, wie man anfängt zu untersuchen ... es ist allerdings ein alter Prozess ... wird wahrscheinlich 2012 nicht funktionieren
Datum
2

Ich würde vorschlagen, das folgende Thema des MSDN- Forums zu lesen . Es geht um das Sperren durch den Zugriff auf eine SQL Server-Datenbank. Der Vorschlag besteht hauptsächlich darin, über Abfragen mit dem NOLOCK-Hinweis auf die Tabellen zuzugreifen, damit keine Sperrprobleme auftreten. NOLOCK ist nicht die beste Lösung, da es andere Probleme verursachen kann, aber die meisten Ihrer Sperrprobleme reduziert.

Die bessere Lösung wäre, Remus 'Idee zu implementieren, die Snapshot-Isolation in Ihrer Datenbank einzurichten. Oder implementieren Sie die Snapshot-Isolationsstufe nur für bestimmte Verbindungen, die Blockierungen verursachen.

Um Ihren Server ordnungsgemäß auf Blockierungsprobleme zu überwachen, würde ich Folgendes vorschlagen:

  • Erstellen Sie einen serverseitigen Trace, der Blockierungsprobleme länger als x Sekunden überwacht (ich würde sagen, 5 ist gut genug).
  • Speichern Sie jeden Tag die oberen Spuren, damit Sie mindestens die letzten 30 Tage einen Verlauf haben, um Trends und Muster zu erkennen.
  • Sie haben einen stündlichen Job, der die Trace-Datei der aktuellen Tage untersucht und Ihnen interessante Blockierungssituationen per E-Mail sendet.

Wenn Sie eine proaktive Antwort auf dieses Problem wünschen, anstatt jede Stunde einen Job zum Überwachen der Traces zu haben, lassen Sie ihn jede Minute laufen und beenden Sie alle führenden blockierenden Access-Sitzungen.

Marian
quelle
0

Nach der hervorragenden Antwort von @Remus Rusanu habe ich die Aufgabe des Lesers übernommen, das Ereignis mit einer gespeicherten Prozedur zu verbinden.

In meinem Fall schreibt der sp die XML des blockierenden Ereignisses in eine Tabelle, aber Sie können an dieser Position tun, was Sie wollen.

Folgen Sie also Remus 'Code und erstellen Sie das queue, das serviceund das notificationmit einem einfachen Kopieren / Einfügen von oben. Fügen Sie die sp_configureOptionen hinzu und Sie sind im Grunde genommen eingestellt.

Das einzige was noch zu tun ist ist

  • Erstellen Sie einen SP ohne Argumente.
  • Erstellen Sie eine Tabelle, in die der SP die Daten schreiben kann (in meinem Beispiel kann Ihr SP variieren).
  • Aktivieren Sie den SP auf dem queue

Sobald Sie den SP aktiviert haben, fließen die Ereignisse in Ihre Tabelle.

Ich fand heraus, dass sich die Warteschlange sofort selbst deaktiviert, wenn der SP einen Fehler aufweist. In diesem Fall müssen Sie zu Server Studio gehen und es im Kontextmenü des Warteschlangeneintrags erneut aktivieren [msdb]->Service Broker->Warteschlangen.

Ich habe einige Zeit gebraucht, um dies zum Laufen zu bringen und die richtigen Stellen in der Dokumentation zu finden. Ich nehme an, dass dies auch für andere hilfreich ist. Ich verwende SQLServer 2005.

Erstellen Sie den SP ohne Argumente

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

Erstellen Sie die pdix_lock_eventsTabelle

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

Aktivieren Sie den SP auf dem queue

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