Blocker Script é uma stored procedure escrita por um engenheiro de suporte (MikeZ), sendo muito utilizado para coletar dados relacionados a bloqueios. Esse script está disponível através do artigo KB 271509.
How to monitor blocking in SQL Server 2005 and in SQL Server 2000 http://support.microsoft.com/kb/271509/en-us
Esse script tem anos de existência e seu código continua praticamente o mesmo. Um dos (poucos) problemas é a impossibilidade de identificar queries causando alto consumo de CPU, sendo necessário adotar uma ferramenta adicional como o SQL Profiler. Pensando nisso, fiz algumas alterações que auxiliam no diagnóstico de performance coletando menos informação. Já faz 5 anos que tenho usado essa versão modificada do Blocker script para avaliar a performance de servidores SQL Server.
1) Crie a stored procedure sp_blocker_pfe_auto usando o script abaixo.
USE MASTER GO IF EXISTS (SELECT * FROM sysobjects WHERE id = OBJECT_ID('dbo.fn_blocker_pfe_sql_text')) DROP FUNCTION dbo.fn_blocker_pfe_sql_text GO --------------------------------------------------------------------------------------- -- Procedure: dbo.fn_blocker_pfe_sql_text --------------------------------------------------------------------------------------- CREATE FUNCTION dbo.fn_blocker_pfe_sql_text(@sql_handle BINARY(20), @stmt_start INT, @stmt_end INT, @length INT) RETURNS VARCHAR(8000) AS BEGIN DECLARE @text VARCHAR(8000) DECLARE @len INT IF @stmt_end > 0 BEGIN SET @len = (@stmt_end - @stmt_start)/2 IF @len < @length SET @length = @len END SELECT @text = SUBSTRING(text, @stmt_start/2, @length) FROM ::fn_get_sql(@sql_handle) RETURN @text end GO IF EXISTS (SELECT * FROM sysobjects WHERE id = OBJECT_ID('dbo.sp_blocker_pfe80') ) DROP PROCEDURE dbo.sp_blocker_pfe80 GO --------------------------------------------------------------------------------------- -- Procedure: dbo.sp_blocker_pfe --------------------------------------------------------------------------------------- CREATE PROCEDURE dbo.sp_blocker_pfe80 ( @info bit = 0, @process bit = 0, @process2 bit = 0, @lock bit = 0, @waitstat bit = 0, @inputbuffer bit = 0, @sqlhandle bit = 0, @sqlhandle_collect bit = 0, @sqlhandle_flush bit = 0, @opentran bit = 0, @logspace bit = 0, @memstatus bit = 0, @perfinfo bit = 0, @trace bit = 0, @textsize int = 256 ) AS SET NOCOUNT ON SET LANGUAGE 'us_english' DECLARE @spid VARCHAR(6) DECLARE @blocked VARCHAR(6) DECLARE @time DATETIME DECLARE @time2 DATETIME DECLARE @dbname nVARCHAR(128) DECLARE @status SQL_VARIANT DECLARE @useraccess SQL_VARIANT DECLARE @perfobjname NVARCHAR(256) IF is_member('sysadmin')=0 BEGIN PRINT 'Must be a member of the sysadmin group in order to run this procedure' return END SET @time = getdate() DECLARE @probclients TABLE ( spid SMALLINT, ecid SMALLINT, blocked SMALLINT, waittype BINARY(2), dbid SMALLINT, sql_handle BINARY(20), stmt_start INT, stmt_end INT, PRIMARY KEY (spid, ecid)) INSERT @probclients SELECT spid, ecid, blocked, waittype, dbid, sql_handle, stmt_start, stmt_end FROM master.dbo.sysprocesses WHERE ( kpid<>0 OR waittype<>0x0000 OR open_tran<>0 OR spid IN (SELECT blocked FROM master.dbo.sysprocesses) ) AND spid>50 --------------------------------------------------------------------------------------- -- 8.2 Start time: --------------------------------------------------------------------------------------- SET @time2 = GETDATE() PRINT '' PRINT '8.3 Start time: ' + CONVERT(VARCHAR(26), @time, 121) + ' ' + CONVERT(VARCHAR(12), datediff(ms,@time,@time2)) --------------------------------------------------------------------------------------- -- Static Configuration --------------------------------------------------------------------------------------- IF @info = 1 BEGIN SET @time2 = GETDATE() PRINT '' PRINT 'MACHINE INFORMATION' PRINT '' PRINT 'ServerName: ' + @@SERVERNAME PRINT 'PhysicalName: ' + CAST(SERVERPROPERTY('ComputerNamePhysicalNetBIOS') AS VARCHAR) PRINT 'ProductVersion: ' + CAST(SERVERPROPERTY('ProductVersion') AS VARCHAR) PRINT 'ProductLevel: ' + CAST(SERVERPROPERTY('ProductLevel') AS VARCHAR) PRINT 'Edition: ' + CAST(SERVERPROPERTY('Edition') AS VARCHAR) PRINT 'ProcessId: ' + CAST(SERVERPROPERTY('ProcessId') AS VARCHAR) PRINT 'SessionId: ' + CAST(@@SPID AS VARCHAR) PRINT '' PRINT @@version PRINT '' PRINT 'EXEC xp_msver' PRINT '' EXEC xp_msver PRINT 'SELECT sysconfigures' PRINT '' SELECT value, comment FROM sysconfigures PRINT 'INFO ' + convert(VARCHAR(12), datediff(ms,@time2,getdate())) END --------------------------------------------------------------------------------------- -- Connections --------------------------------------------------------------------------------------- IF @process = 1 BEGIN SET @time2 = GETDATE() PRINT '' PRINT 'SYSPROCESSES' SELECT spid, status, blocked, open_tran, waitresource, waittype, waittime, cmd, lastwaittype, cpu, physical_io,memusage, last_batch=convert(VARCHAR(26), last_batch,121), login_time=convert(VARCHAR(26), login_time,121), net_address,net_library, dbid, ecid, kpid, hostname, hostprocess, loginame, program_name, nt_domain, nt_username, uid, sid, sql_handle, stmt_start, stmt_end FROM master.dbo.sysprocesses WHERE spid IN (SELECT spid FROM @probclients) PRINT 'ESP ' + convert(VARCHAR(12), datediff(ms,@time2,getdate())) END --------------------------------------------------------------------------------------- -- Connections2 --------------------------------------------------------------------------------------- IF @process2 = 1 BEGIN SET @time2 = GETDATE() PRINT '' PRINT 'SYSPROCESSES2' SELECT spid, status, blocked, open_tran, waitresource, waittype, waittime, cmd, lastwaittype, cpu, physical_io,memusage, last_batch=convert(VARCHAR(26), last_batch,121), login_time=convert(VARCHAR(26), login_time,121), net_address,net_library, dbid, ecid, kpid, hostname, hostprocess, loginame, program_name, nt_domain, nt_username, uid, sid, sql_handle, stmt_start, stmt_end FROM master.dbo.sysprocesses WHERE (kpid<>0 OR waittype<>0x0000 OR open_tran<>0) AND (spid>50) PRINT 'ESP2 ' + convert(VARCHAR(12), datediff(ms,@time2,getdate())) END --------------------------------------------------------------------------------------- -- SYSLOCKINFO --------------------------------------------------------------------------------------- IF @lock = 1 BEGIN SELECT @time2 = GETDATE() PRINT '' PRINT 'SYSLOCKINFO' SELECT CONVERT (smallint, req_spid) AS spid, rsc_dbid AS dbid, rsc_objid AS ObjId, rsc_indid AS IndId, SUBSTRING (v.name, 1, 4) AS Type, SUBSTRING (rsc_text, 1, 32) as Resource, SUBSTRING (u.name, 1, 8) AS Mode, SUBSTRING (x.name, 1, 5) AS Status FROM master.dbo.syslockinfo l inner join master.dbo.spt_values v on (l.rsc_type = v.number and v.type = 'LR') inner join master.dbo.spt_values x on (l.req_status = x.number and x.type = 'LS') inner join master.dbo.spt_values u on (l.req_mode + 1 = u.number and u.type = 'L') WHERE l.rsc_type = 5 PRINT 'ESL ' + convert(VARCHAR(12), datediff(ms,@time2,getdate())) END --------------------------------------------------------------------------------------- -- DBCC SQLPERF(WAITSTATS) --------------------------------------------------------------------------------------- IF @waitstat = 1 BEGIN SELECT @time2 = GETDATE() PRINT '' PRINT 'DBCC SQLPERF(WAITSTATS)' DBCC SQLPERF(WAITSTATS) PRINT 'DBCCWAIT ' + convert(VARCHAR(12), datediff(ms,@time2,getdate())) END --------------------------------------------------------------------------------------- -- DBCC INPUTBUFFER --------------------------------------------------------------------------------------- IF @inputbuffer = 1 BEGIN SELECT @time2 = GETDATE() PRINT '' PRINT 'DBCC INPUTBUFFER(*)' DECLARE ibuffer CURSOR FAST_FORWARD FOR SELECT DISTINCT CAST (spid AS VARCHAR(6)) AS spid FROM @probclients WHERE (spid <> @@spid) OPEN ibuffer FETCH NEXT FROM ibuffer INTO @spid WHILE (@@FETCH_STATUS <> -1) BEGIN PRINT '' PRINT 'DBCC INPUTBUFFER FOR SPID ' + @spid EXEC ('DBCC INPUTBUFFER (' + @spid + ')') FETCH NEXT FROM ibuffer INTO @spid END DEALLOCATE IBUFFER PRINT 'DBCCINPUTBUFFER(*) END ' + convert(VARCHAR(12), datediff(ms,@time2,getdate())) END --------------------------------------------------------------------------------------- -- SQLHANDLE --------------------------------------------------------------------------------------- IF @sqlhandle = 1 BEGIN SELECT @time2 = GETDATE() PRINT '' PRINT 'SQLHANDLE' SELECT sql_handle, stmt_start, stmt_end, total = count(*), text = cast(dbo.fn_blocker_pfe_sql_text(sql_handle, stmt_start, stmt_end, @textsize) as VARCHAR(1024)) FROM @probclients WHERE ecid = 0 and spid<>@@spid GROUP BY sql_handle, stmt_start, stmt_end ORDER BY count(*) DESC PRINT 'ESH HANDLE 0x0000000000000000000000000000000000000000 ' + convert(VARCHAR(12), datediff(ms,@time2,getdate())) END --------------------------------------------------------------------------------------- -- DBCC OPENTRAN --------------------------------------------------------------------------------------- IF @opentran = 1 BEGIN SELECT @time2 = GETDATE() PRINT '' PRINT 'DBCC OPENTRAN(*)' DECLARE ibuffer CURSOR FAST_FORWARD FOR SELECT DISTINCT CAST (dbid AS VARCHAR(6)) FROM @probclients WHERE dbid <> 0 UNION SELECT '2' OPEN ibuffer FETCH NEXT FROM ibuffer INTO @spid WHILE (@@FETCH_STATUS <> -1) BEGIN PRINT '' SET @dbname = DB_NAME(@spid) SET @status = DATABASEPROPERTYEX(@dbname,'Status') SET @useraccess = DATABASEPROPERTYEX(@dbname,'UserAccess') PRINT 'DBCC OPENTRAN FOR DBID ' + @spid + ' ['+ @dbname + ']' IF @status = N'ONLINE' and @useraccess = N'SINGLE_USER' PRINT 'Skipped: Status=ONLINE UserAccess=SINGLE_USER' ELSE DBCC OPENTRAN(@dbname) FETCH NEXT FROM ibuffer INTO @spid END DEALLOCATE ibuffer PRINT 'DBCCOPENTRAN(*) END ' + convert(VARCHAR(12), datediff(ms,@time2,getdate())) END --------------------------------------------------------------------------------------- -- DBCC MEMORYSTATUS --------------------------------------------------------------------------------------- IF @memstatus = 1 BEGIN SELECT @time2 = GETDATE() PRINT '' PRINT 'DBCC MEMORYSTATUS' DBCC MEMORYSTATUS PRINT 'MEMSTATUS ' + convert(VARCHAR(12), datediff(ms,@time2,getdate())) END --------------------------------------------------------------------------------------- -- DBCC SQLPERF(LOGSPACE) --------------------------------------------------------------------------------------- IF @logspace = 1 BEGIN SELECT @time2 = GETDATE() PRINT '' PRINT 'DBCC SQLPERF(LOGSPACE)' DBCC SQLPERF(LOGSPACE) PRINT 'DBCCLOG ' + convert(VARCHAR(12), datediff(ms,@time2,getdate())) END --------------------------------------------------------------------------------------- -- Sysperfinfo --------------------------------------------------------------------------------------- IF @perfinfo = 1 BEGIN SELECT @time2 = GETDATE() PRINT '' PRINT 'SYSPERFINFO' SELECT TOP 1 @perfobjname=LEFT(object_name, CHARINDEX(N':',object_name)) FROM sysperfinfo SELECT object_name, counter_name, cntr_value FROM sysperfinfo WHERE object_name IN (@perfobjname + N'Buffer Manager', @perfobjname + N'Databases', @perfobjname + N'General Statistics', @perfobjname + N'Memory Manager', @perfobjname + N'SQL Statistics') PRINT 'PERFINFO ' + convert(VARCHAR(12), datediff(ms,@time2,getdate())) END --------------------------------------------------------------------------------------- -- ::fn_trace_getinfo --------------------------------------------------------------------------------------- IF @trace = 1 BEGIN SELECT @time2 = GETDATE() PRINT '' PRINT 'TRACE_GETINFO' SELECT * FROM ::fn_trace_getinfo(0) PRINT 'TRCINF ' + convert(VARCHAR(12), datediff(ms,@time2,getdate())) END --------------------------------------------------------------------------------------- -- End time --------------------------------------------------------------------------------------- PRINT '' PRINT 'End time: ' + convert(varchar(26), getdate(), 121) GO IF EXISTS (SELECT * FROM sysobjects WHERE id = OBJECT_ID('dbo.sp_blocker_pfe_auto') ) DROP PROCEDURE dbo.sp_blocker_pfe_auto GO --------------------------------------------------------------------------------------- -- Procedure: dbo.sp_blocker_pfe_auto --------------------------------------------------------------------------------------- CREATE PROCEDURE dbo.sp_blocker_pfe_auto AS SET NOCOUNT ON EXEC dbo.sp_blocker_pfe80 @info = 1, @trace = 1 WHILE 1=1 BEGIN EXEC dbo.sp_blocker_pfe80 @process2 = 1, @inputbuffer = 1, @sqlhandle = 1 RAISERROR('',0,1) WITH NOWAIT WAITFOR DELAY '0:0:15' EXEC dbo.sp_blocker_pfe80 @process2 = 1, @inputbuffer = 1, @sqlhandle = 1 RAISERROR('',0,1) WITH NOWAIT WAITFOR DELAY '0:0:15' EXEC dbo.sp_blocker_pfe80 @process2 = 1, @inputbuffer = 1, @sqlhandle = 1 RAISERROR('',0,1) WITH NOWAIT WAITFOR DELAY '0:0:15' EXEC dbo.sp_blocker_pfe80 @process2 = 1, @inputbuffer = 1, @sqlhandle = 1, @waitstat = 1, @lock = 1, @opentran = 1 RAISERROR('',0,1) WITH NOWAIT WAITFOR DELAY '0:0:15' END
Esse script cria os seguintes scripts no banco de dados MASTER: fn_blocker_pfe_sql_text, sp_blocker_pfe80, sp_blocker_pfe_auto. Após criado esses objetos, não é necessário rodar o script novamente.
2) Execute a stored procedure sp_blocker_pfe_auto usando um usuário com privilégios de SysAdmin para coletar informações do servidor SQL. Esse script fica executando infinitamente e pode ser cancelado após obter as informações necessárias.
3) É possível coletar as informações do Blocker Script usando a linha de comando
osql -S SERVIDOR\INSTANCIA -o arquivo_saida.out -E -Q "sp_blocker_pfe_auto" -w2000
Atenção: Não esqueça de alterar o nome do servidor instância SQL Server (SERVIDOR\INSTANCIA) e do arquivo de saída.
No meu caso, a linha de comando ficou assim:
osql -S fcatae-11\katmai –o blocker2010-10-07.out -E -Q "sp_blocker_pfe_auto" -w2000
Após 30 minutos de coleta de dados, apertei Control-C para finalizar o script. Na minha máquina, foi gerado o arquivo blocker2010-10-07.out com 5MB. O tamanho do arquivo varia conforme a carga no servidor, mas é razoável estimar arquivos de 300MB para uma monitoração de 8 horas.
Em uma situação de bloqueio, identificamos que o SPID 56 está bloqueando a sessão 57. O recurso envolvido é a tabela identificada por 5:2105058535 (dbid:objid).
Essa semana estou reescrevendo a stored procedure para usar as novas funcionalidades do SQL 2008 e, em breve, disponibilizarei um tutorial sobre como analisar o arquivo de saída (falta tempo para fazer tudo isso). Essa é uma das principais ferramentas para diagnóstico de performance.
Links Relacionados
Gostaria de convidar a todos que deixem seus comentários sobre o assunto – sugestões, idéias, dicas, etc.
Pontos a serem melhorados:
- Requer SysAdmin para rodar a procedure. O ideal seria depender somente de permissões como VIEW SERVER STATE.
- Substituir a função fn_blocker_pfe_sql_text pela DMF sys.dm_exec_sql_text(@handle)
- Utilizar a sys.dm_exec_requests, que apresenta uma performance melhor que sys.sysprocesses no SQL2008
- A análise de transações abertas poderia ser melhorada com o auxílio da sys.dm_tran_database_transactions
- Não monitora as session_id < 50 - normalmente usadas pelo Service Broker - Activation Procs
Show de bola e realmente pelo menos um artiguinho destacando uns 4 a 5 pontos chaves de análise seria de grande ajuda. Algo como...."o que focar dentro de um arquivo de 100, 200 ou 300MB :)
abraços e boa dica.
Grande Fabricio, espero ansiosamente pela nova versão :) Show
Cara, essa eu não conhecia #fail , mas vou fazer uns testes e olhar o código e depois digo algo.
Já imaginei alguma integração disso com o Perfmon, mas devo estar querendo muito já, hehe!
Muito legal, não sabia que essa SP exisitia e já precisie desse tipo de monitoramento. Aposto que este artigo vai ajudar muita gente. Parabéns.
Boa Fabricio!!! Em uma investigação de performace e bloqueio são essenciais.!
Hoje eu a utilizo com algumas modificações...
Quando você adicionar o script aqui no Blog podemos dar algumas ideias e customizar em "colaboração" com todos. O que acha?
Pessoal, para os que estão "estudando" o uso da proc. como eu, segue um link que tem algumas outras informações.
blogs.technet.com/.../sql-server-performance-analysis-part-iv.aspx
Obrigado por todos comentários.
Grande idéia do Nilton, preciso escrever os artigos detalhados sobre os assuntos. Alguns cenários que o Blocker (modificado) pode ajudar:
- Bloqueios
- Alto consumo de CPU
- Contenção nas páginas PFS, SGAM, system tables do TempDB
- Contenção na recompilação de stored procedures
- Problemas de falta de thread
- Falta de memória no Workspace (resource semaphore)
Assim que possível, tentarei trazer exemplos reais de como analisar. Vlad - ótimo cross-link para o blog da Claudia, que mostra um exemplo de como analisar o blocker. Falando em Perfmon, existe uma análise complementar que pode ser feita pelos performance counters.
A idéia de compartilhar o script é ótima! Já estou pensando em fazer isso o mais rápido possível.
Agradeço muito pelas sugestões dadas. Obrigado!!
Atualizado o blog:
- Coloquei uma versão mais otimizada do script, que coleta menos informação e gera arquivos menores. Também inclui uma modificação recente que fiz para facilitar a coleta com os SQL2008.
UPDATE: Finalizei uma nova versão do Blocker Script. Rodei em dois ambientes de produção e o impacto foi baixo. O tamanho do arquivo ficou em torno de 300MB após uma monitoração de 6 horas.