1

Sessions consommatrices dans tempdb

twitterlinkedinmail

Je sais ce que vous allez dire: on l’a déjà vu partout, il y a déjà un million d’articles sur le sujet, quel intérêt pour une société qui se défend justement de poster sur des sujets originaux, etc…

Je vous répondrai que le problème n’est pas trivial. Tout le monde utilise les vues DMV pour récupérer les infos de la session qui occupe l’espace dans tempdb, notamment le texte SQL de la requête:
sys.dm_db_task_space_usage: pour voir l’espace utilisé par session.
sys.dm_exec_requests ou sys.dm_exec_query_stats + cross apply sys.dm_exec_sql_text(sql_handle): pour plus d’infos sur les sessions (stats + texte des requêtes).

Seulement voilà: les sessions dont le plan a été purgé du cache ne sont plus visibles dans les DMV sys.dm_exec… donc la jointure entre sys.dm_db_task_space_usage et sys.dm_exec… ne renverra pas d’infos pour des sessions qui utilisent pourtant bel et bien de l’espace dans tempdb.

Donc il faut avoir recours au bon vieux dbcc inputbuffer pour récupérer tout ça.

1) D’abord, on va créer les tables de recueil.

1.1 DBCCINPUTBUFFER va nous permettre de récupérer le contenu du dbcc inputbuffer par session:

CREATE TABLE tempdb.guest.DBCCINPUTBUFFER
(
EventType varchar(50),
Parameters int,
SQLText varchar(8000)
)

1.2 DBCCSHOWFILESTATS va nous permettre de récupérer l’espace total et utilisé dans tempdb:

CREATE TABLE tempdb.guest.DBCCSHOWFILESTATS
(
[fileid] [int], [filegroup] [int],
[totalextents] [int], [usedextents] [int],
[name] [varchar] (255), [filename] [varchar] (255)
)

1.3 monitorTempdb sera notre table finale d’historique.

CREATE TABLE tempdb.guest.monitorTempdb
(
curdate datetime,
spaceintempdb bigint,
spaceusedintempdb bigint,
spid int,
sqltext varchar(8000),
internal_objects_alloc_MB bigint,
internal_objects_dealloc_MB bigint,
user_objects_alloc_MB bigint,
user_objects_dealloc_MB bigint,
program_name varchar(100),
login_time datetime,
hostname varchar(20),
login_name varchar(100)
)

2) Par session récupérer les infos et le texte de la requête: on va placer les sessions consommatrices ainsi que les espaces consommés dans un curseur, puis boucler pour chaque session_id et récupérer plus d’infos (program_name, login, etc… depuis sys.dm_exec_sessions, la seule DMV qui affiche toutes les sessions actives ou non) et le dernier texte SQL généré:

use tempdb
go
declare
	@spid int,
	@sqltext varchar(8000),
	@internal_objects_alloc_page_count bigint,
	@internal_objects_dealloc_page_count bigint,
	@user_objects_alloc_page_count bigint,
	@user_objects_dealloc_page_count bigint,
	@program_name varchar(100),
	@login_time datetime,
	@hostname varchar(20),
	@login_name varchar(100),
	@SQLQUERY varchar(100),
	@spaceintempdb bigint,
	@spaceusedintempdb  bigint

declare cr_sqltext CURSOR READ_ONLY for
select session_id,internal_objects_alloc_page_count,internal_objects_dealloc_page_count,
user_objects_alloc_page_count, user_objects_dealloc_page_count
from sys.dm_db_task_space_usage where internal_objects_alloc_page_count > 12000 

open cr_sqltext
fetch next from cr_sqltext into
	@spid,
	@internal_objects_alloc_page_count,
	@internal_objects_dealloc_page_count,
	@user_objects_alloc_page_count,
	@user_objects_dealloc_page_count

while @@fetch_status = 0
begin
	select
		@program_name = program_name,
		@login_time = login_time,
		@hostname = host_name,
		@login_name = login_name
	from sys.dm_exec_sessions where session_id = @spid

	select @SQLQUERY = 'dbcc inputbuffer('+convert(char(3),@spid)+')'

	insert into tempdb.guest.DBCCINPUTBUFFER exec (@SQLQUERY)
	select @sqltext = SQLText from tempdb.guest.DBCCINPUTBUFFER 

	insert into tempdb.guest.DBCCSHOWFILESTATS exec ('DBCC showfilestats with NO_INFOMSGS')
	select
		@spaceintempdb = sum(totalextents)/16,
		@spaceusedintempdb = sum(usedextents)/16
	from tempdb.guest.DBCCSHOWFILESTATS

	delete from tempdb.guest.DBCCINPUTBUFFER
	delete from tempdb.guest.DBCCSHOWFILESTATS

	insert tempdb.guest.monitorTempdb values
	(getdate(),
	@spaceintempdb,
	@spaceusedintempdb ,
	@spid, 	@sqltext,
	@internal_objects_alloc_page_count*8192/1048576 ,
	@internal_objects_dealloc_page_count*8192/1048576 ,
	@user_objects_alloc_page_count*8192/1048576 ,
	@user_objects_dealloc_page_count*8192/1048576 ,
	@program_name ,
	@login_time ,
	@hostname ,
	@login_name)

fetch next from cr_sqltext into
	@spid,
	@internal_objects_alloc_page_count,
	@internal_objects_dealloc_page_count,
	@user_objects_alloc_page_count,
	@user_objects_dealloc_page_count

end
close cr_sqltext
deallocate cr_sqltext
go

Cette dernière partie peut être mise dans un job planifié toutes les minutes par exemple. C’est ainsi que j’ai découvert que sp_MSget_repl_commands et sp_MSdistribution_cleanup (procs de répli) peuvent être extrêmement consommatrices lorsque la file des requêtes à répliquer est très importante.

A+ [ David B. ]

Continuez votre lecture sur le blog :

twitterlinkedinmail

David Baffaleuf

Un commentaire

  1. super ce petit bout de code !!!! Petite remarque : je place mes tables hors tempdb. Pour les qq fois utilisées dans mon cas, la tempdb se remplissait extrêmement vite, rollback du traitement impossbible, SQL Server s’arrête tout seul…. perte des tables tempo évidemment 🙂

Laisser un commentaire

Votre adresse e-mail ne sera pas publiée. Les champs obligatoires sont indiqués avec *

Ce site utilise Akismet pour réduire les indésirables. En savoir plus sur comment les données de vos commentaires sont utilisées.