Puis-je être automatiquement averti d'un blocage prolongé dans SQL Server?


8

Environ une fois par semaine, je dois résoudre une chaîne de blocage sur une base de données SQL Server 2005, causée par un verrou de lecture de longue durée à partir d'un frontal Access 2003. Le verrou est retiré chaque fois qu'un utilisateur ouvre un certain formulaire et est libéré une fois que l'utilisateur a fini de parcourir le formulaire ou de le fermer. Étant donné que beaucoup de nos utilisateurs ouvrent ce formulaire comme référence, ces verrous restent en place pendant un certain temps. Toute mise à jour de la table provoque le blocage, et soudainement, personne ne peut sélectionner dans cette table car ils attendent tous le premier verrou. C'est tout à fait un problème pour nous, car de nombreuses applications s'appuient sur ces données. Je comprends que ce comportement de verrouillage fait partie de la façon dont Access fonctionne avec les tables liées.

J'ai résolu le problème à partir du moniteur d'activité, en tuant le processus SELECT qui est le bloqueur de tête chaque fois que je le découvre. C'est un problème non seulement parce qu'il me faut du temps pour le faire manuellement, mais aussi parce qu'il est réactif. Au moment où j'en entends parler, cela a déjà été un problème pour beaucoup de gens.

Je voudrais savoir s'il existe un moyen automatique de vérifier ces chaînes de blocage durables et de recevoir un e-mail ou de résoudre le problème automatiquement. La logique semble assez simple ("si un processus correspondant à cette requête SELECT bloque depuis plus d'une minute, informez-moi / tuez-le") mais je ne sais pas comment l'implémenter avec SQL Server.

Pour ce que ça vaut, je pense que la bonne solution est de réparer ou de réécrire l'application. Cependant, en raison de la politique ministérielle, ce n'est pas une option pour les prochains mois, donc je cherche un bouche-trou.


Réponses:


9

Avez-vous envisagé d'utiliser l' isolement d'instantané ? L'activation de read_committed_snapshot dans la base de données entraînera le verrouillage de toutes les lectures (sélections):

alter database [...] set read_committed_snapshot on;

Aucune modification d'application. Certaines sémantiques changent sous l'instantané et votre application peut réagir bizarrement, mais ce n'est pas l'exception, mais la norme. La grande majorité des applications ne remarquent aucune différence, elles bénéficient simplement d'une amélioration gratuite des performances.

Quoi qu'il en soit, j'ai pensé à répondre également à la question d' origine : comment détecter (et éventuellement tuer) une requête de longue durée. En fait, le moteur le fait déjà pour vous. Un événement est déclenché lorsqu'un seuil est dépassé: Classe d'événements de rapport de processus bloqué . Le seuil est configuré via l' option de seuil de processus bloqué . Tout événement de trace peut être transformé en notification d'événement et les notifications d'événement peuvent activer des procédures . Connectez les points et vous disposez d'un code activé à la demande qui s'exécute lorsque le moteur détecte une requête qui a franchi un seuil de temps d'exécution. Pas de sondage, pas de surveillance. Notez cependant que la notification est asynchrone, au moment où vous le traitez, il se peut que la requête soit terminée, ce qui doit être pris en compte.

Voici un exemple:

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 ;

Maintenant, dans une nouvelle requête, configurez une WAITFORattente de notification:

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

Et allez-y et provoquez un blocage. J'ai utilisé un processus qui a créé une table et n'a pas validé, et à partir d'une autre fenêtre de requête, j'ai essayé de sélectionner dans la table. En 20 secondes (mon seuil configuré ci-dessus), j'ai reçu le rapport de blocage:

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

Je laisserai au lecteur le soin d'en faire un processus automatisé. Et oui, la file d'attente / service / procédure activée doit être en [msdb].


Je ne l'ai pas fait, mais je vais certainement le lire! Quel genre de bizarrerie devrais-je rechercher? S'il s'agit généralement d'une amélioration des performances, y a-t-il une raison pour laquelle l'isolement de l'instantané n'est pas activé par défaut?
Warrior Bob

poursuivant le lien à l'intérieur du lien fourni, lisez -le et voyez comment cela s'applique à votre situation
swasheck

3
Je recommande de lire Comparaison de différents résultats avec RCSI & Read Committed et les liens à la fin. Des inquiétudes particulières sont justifiées si vous avez des FDU à états multiples, par exemple. Les lectures impliquant des FDU sous READ_COMMITTED_SNAPSHOT peuvent sembler incohérentes . En fin de compte, vous devez tester. Mais encore une fois, la majorité des cas, il n'y a pas d'effets visibles.
Remus Rusanu

1
Aucun effet visible sur l'application, je suis d'accord. Dans le système de base de données, vous voudrez garder un œil sur le tempdb. Il y a plus de charge à partir de read_committed_snapshot.
Grant Fritchey

1
@AlexKuznetsov: La façon dont le RCSI est déployé révèle sa nature: il est déployé par un seul changement dans la base de données et il mappe silencieusement la lecture validée à l'instantané pour chaque instruction. Tout cela me dit «une tentative désespérée de réparer une application cassée qui ne peut pas être modifiée». L'OP envisage actuellement de tuer les processus de blocage toutes les N minutes . Donner à RCSI un essai routier me semble tout à fait raisonnable dans ce cas. Je sais par expérience que le nombre de cas que RCSI aide et ne brise pas les choses l'emporte largement sur les cas où des problèmes surviennent.
Remus Rusanu

5

Vous pouvez soit créer votre propre outil de surveillance, soit rechercher une solution tierce qui peut vous en fournir un. Si vous souhaitez créer le vôtre, cela dépend de la version de SQL Server avec laquelle vous travaillez. S'il s'agit de 2005, vous pouvez utiliser l' événement de trace Rapport de processus bloqué . Si vous utilisez 2008 ou une version ultérieure, je vous suggère d'utiliser l'événement étendu équivalent, block_process_report. Jonathan Kehayias a une bonne écriture sur la façon de l'utiliser.

Si vous regardez des produits tiers, le moniteur SQL du logiciel Red Gate a bloqué les alertes de processus et de processus de longue durée.


3

Bien que cela ne traite pas de la façon de vous informer du problème, cette procédure vous montrera comment interroger pour voir si un blocage existe. Il générera également des commandes kill pour vous, si vous passez le paramètre correct.

J'espère que cela vous donnera quelques idées.

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

Vous lui donnez le marteau avant de le laisser étudier mieux les problèmes de blocage :-). Je dirais que vous feriez mieux de changer la condition pour ne tuer que les sessions MSACCESS: D.
Marian

J'essayais juste de montrer comment commencer à enquêter ... mais c'est un vieux proc ... probablement ne fonctionnera pas en 2012
datagod

2

Je suggère de lire le sujet de forum MSDN suivant . Il s'agit du verrouillage provoqué par l'accès à une base de données SQL Server. La suggestion est principalement d'accéder aux tables par des requêtes à l'aide de l'indicateur NOLOCK, afin que cela ne cause aucun problème de verrouillage. NOLOCK n'est pas la meilleure solution, car il peut causer d'autres problèmes, mais réduira la plupart de vos problèmes de verrouillage.

La meilleure solution serait d'implémenter l'idée de Remus, de configurer l'isolement des instantanés sur votre base de données. Ou implémentez le niveau d'isolement d'instantané uniquement pour certaines connexions qui provoquent un blocage.

Afin de surveiller correctement votre serveur pour les problèmes de blocage, je suggère:

  • créer une trace côté serveur qui surveillera les problèmes de blocage pendant plus de x secondes (je dirais que 5 est suffisant);
  • enregistrez les traces supérieures tous les jours afin d'avoir un historique d'au moins les 30 derniers jours pour voir les tendances et les modèles;
  • avoir un travail horaire qui étudie le fichier de trace du jour en cours et vous envoyer par e-mail toutes les situations de blocage intéressantes;

Si vous souhaitez une réponse proactive à ce problème, au lieu d'avoir un travail toutes les heures pour surveiller les traces, faites-le s'exécuter toutes les minutes et supprimez toute session d'accès bloquante principale.


0

Suite à l'excellente réponse de @Remus Rusanu, j'ai effectué la tâche du lecteur pour connecter l'événement à une procédure stockée.

Dans mon cas, le sp écrira le xml de l'événement de blocage dans une table, mais vous êtes libre de faire ce que vous voulez à cette position.

Alors, suivez le code de Remus et créez le queue, le serviceet le notificationavec un simple copier / coller d'en haut. Ajoutez les sp_configureoptions et vous êtes fondamentalement défini.

Il ne reste plus qu'à faire

  • Créez un SP sans arguments.
  • Créez une table pour le SP dans laquelle écrire les données (pour mon exemple, votre SP peut varier)
  • Activez le SP sur le queue

Dès que vous avez activé le SP, les événements commencent à affluer dans votre table.

J'ai découvert que la file d'attente se désactive immédiatement si le SP a une erreur. Dans ce cas, vous devez aller dans Server Studio et le réactiver dans le menu contextuel de l'entrée de file d'attente ( [msdb]->Service Broker->Warteschlangenen version allemande).

Il m'a fallu un certain temps pour que cela fonctionne et pour trouver les bons endroits dans la documentation, donc je suppose que cela est également utile pour les autres. J'utilise SQLServer 2005.

Créez le SP sans arguments

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

Créer la pdix_lock_eventstable

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

Activez le SP sur le queue

alter queue blocked_process_report_queue with activation( 
    procedure_name=sp_addrecord, 
    max_queue_readers = 1, 
    status = on, 
    execute as 'dbo');  
En utilisant notre site, vous reconnaissez avoir lu et compris notre politique liée aux cookies et notre politique de confidentialité.
Licensed under cc by-sa 3.0 with attribution required.