Можно ли автоматически получать уведомления о длительной блокировке на сервере SQL?


8

Примерно раз в неделю мне приходится разрешать цепочку блокировок в базе данных SQL Server 2005, вызванную долговременной блокировкой чтения из интерфейса Access 2003. Блокировка снимается всякий раз, когда пользователь открывает определенную форму, и снимается, когда пользователь завершает прокрутку формы или закрывает ее. Поскольку многие из наших пользователей открывают эту форму в качестве ссылки, эти блокировки остаются на некоторое время. Любое обновление таблицы вызывает блокировку, и внезапно никто не может выбрать из этой таблицы, так как все они ожидают первой блокировки. Для нас это большая проблема, так как многие приложения полагаются на эти данные. Я понимаю, что это поведение блокировки является частью того, как Access работает со связанными таблицами.

Я решал проблему из Activity Monitor, убивая тот процесс SELECT, который блокирует головы всякий раз, когда я узнаю об этом. Это проблема не только потому, что мне требуется время, чтобы сделать это вручную, но и потому, что она реагирует. К тому времени, когда я слышу об этом, это уже стало проблемой для многих людей.

Я хотел бы знать, существует ли автоматический способ проверки этих длительных цепочек блокировок, и либо он будет отправлен по электронной почте, либо проблема будет решена автоматически. Логика кажется достаточно простой («если какой-либо процесс, соответствующий этому запросу SELECT, блокировался более минуты, сообщите мне / убейте его»), но я не знаю, как реализовать это с SQL Server.

Для чего это стоит, я думаю, что правильное решение состоит в том, чтобы исправить или переписать приложение. Тем не менее, из-за политики департаментов, это не вариант в течение следующих нескольких месяцев, поэтому я ищу временную пробел.


Ответы:


9

Рассматривали ли вы использование изоляции моментальных снимков ? Включение read_committed_snapshot в базе данных приведет к тому, что все операции чтения (выбора) будут заблокированы:

alter database [...] set read_committed_snapshot on;

Нет изменений приложения. Некоторые семантики меняются под снимком, и ваше приложение может реагировать странно, но это исключение, а не норма. Подавляющее большинство приложений не замечают никакой разницы, они просто получают бесплатное повышение производительности.

В любом случае, я хочу ответить и на оригинальный вопрос: как обнаружить (и, возможно, убить) длительный запрос. На самом деле, двигатель уже делает это для вас. При превышении порога возникает событие: Класс события отчета о заблокированных процессах . Порог настраивается через опцию заблокированного процесса . Любое событие трассировки можно превратить в Уведомление о событии, а уведомления о событиях могут активировать процедуры . Соедините точки, и вы получите активированный код по требованию, который запускается, когда механизм обнаруживает запрос, который превысил порог времени выполнения. Нет опроса, нет мониторинга. Обратите внимание, что уведомление является асинхроннымк тому времени, когда вы его обрабатываете, запрос может быть завершен, и это необходимо учитывать.

Вот пример:

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


Я не знаю, но я определенно собираюсь прочитать об этом! Какую странность я должен искать? Если это обычно повышение производительности, есть ли причина, по которой изоляция моментальных снимков не включена по умолчанию?
Воин Боб

в погоне за ссылкой внутри предоставленной ссылки, прочитайте это и посмотрите, как это применимо к вашей ситуации
swasheck

3
Я рекомендую прочитать Сравнение различных результатов с RCSI и Read Committed и ссылки в конце. Особые опасения оправданы, если у вас есть UDF с несколькими утверждениями, например. Чтения с участием UDF в READ_COMMITTED_SNAPSHOT могут показаться непоследовательными . В конечном итоге вам нужно проверить. Но опять же, в большинстве случаев нет видимых эффектов.
Ремус Русану

1
Нет видимых эффектов на приложение, я согласен. В системе баз данных вы захотите следить за tempdb. Там есть большая нагрузка от read_committed_snapshot.
Грант Фричей

1
@AlexKuznetsov: Верный способ развертывания RCSI раскрывает его природу: он развертывается одним изменением в БД и тихо отображает зафиксированное чтение для моментального снимка для каждого оператора. Все это говорит мне о «отчаянной попытке починить сломанное приложение, которое нельзя изменить». ОП в настоящее время рассматривает процессы блокировки убийства каждые N минут . В таком случае мне кажется разумным дать RCSI тест-драйв. По своему опыту я знаю, что число случаев, когда RCSI помогает, а не ломает вещи, значительно перевешивает случаи, когда возникают проблемы.
Ремус Русану

5

Вы можете создать свой собственный инструмент мониторинга или обратиться к стороннему решению, которое может предоставить его вам. Если вы заинтересованы в создании собственного, это зависит от того, с какой версией SQL Server вы работаете. Если это 2005, вы можете использовать событие трассировки Отчета о заблокированных процессах . Если вы работаете в версии 2008 или выше, я бы предложил использовать эквивалентное расширенное событие block_process_report. Джонатан Кеяйас хорошо написал о том, как его использовать.

Если вы просматриваете сторонние продукты, в программном обеспечении Red Gate SQL Monitor заблокированы процессы и встроены длительные оповещения о процессах.


3

Хотя это не относится к тому, как уведомить вас о проблеме, эта процедура покажет вам, как сделать запрос, чтобы увидеть, существует ли блокировка. Он также будет генерировать команды уничтожения для вас, если вы передадите правильный параметр.

Надеюсь, что это дает вам некоторые идеи.

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

Я просто пытался показать, как начать расследование ... это старый процесс, хотя ... скорее всего, не будет работать в 2012 году
дата данных

2

Я бы предложил прочитать следующую тему на форуме MSDN . Речь идет о блокировке, вызванной доступом к базе данных SQL Server. Предполагается, что в основном доступ к таблицам осуществляется с помощью подсказки NOLOCK, чтобы не возникало проблем с блокировкой. NOLOCK - не лучшее решение, так как оно может вызвать другие проблемы, но уменьшит большинство проблем с блокировкой.

Лучшим решением было бы реализовать идею Ремуса, настроить изоляцию моментальных снимков в вашей базе данных. Или реализуйте уровень изоляции моментального снимка только для определенных соединений, которые, по вашему мнению, вызывают блокировку.

Для того, чтобы правильно контролировать ваш сервер на наличие проблем с блокировкой, я бы предложил:

  • создайте трассировку на стороне сервера, которая будет отслеживать проблемы блокирования дольше, чем x секунд (я бы сказал, 5 достаточно);
  • сохраняйте верхние трассы каждый день, чтобы иметь историю как минимум за последние 30 дней, чтобы увидеть тренды и паттерны;
  • иметь почасовую работу, которая изучает файл трассировки текущих дней и отправлять вам по электронной почте любые интересные ситуации блокировки;

Если вы хотите упреждающего ответа на эту проблему, вместо того, чтобы каждый час выполнять мониторинг трассировок, заставляйте его запускаться каждую минуту и ​​убивайте любой ведущий блокирующий сеанс доступа.


0

Следуя прекрасному ответу @Remus Rusanu, я выполнил задачу читателя, чтобы связать событие с хранимой процедурой.

В моем случае sp запишет xml события блокировки в таблицу, но вы можете делать все, что захотите, в этой позиции.

Итак, следуйте коду Remus и создайте queue, the serviceи the 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');  
Используя наш сайт, вы подтверждаете, что прочитали и поняли нашу Политику в отношении файлов cookie и Политику конфиденциальности.
Licensed under cc by-sa 3.0 with attribution required.