When sp_depends is not dependable
A lot of DBAs including me used sp_depends several times to analyse stored procedures. I felt helpless when I understood, sp_depends is not always dependable. Scan through stored procedure script with 1000+ lines of code manually to find objects is practically impossible.
How do we analyse a stored procedure performance and healthcheck all underlying tables quickly when sp_depends is not dependable?
A quick look at the stored proc script gave more tables and views not listed in sp_depends output.
References
As a solution to this and mainly to run “UPDATE STATISTICS” for dependant objects as soon as we find a slow performing stored procedure, my manager Sundar gave me a task to build a script . The script had to follow below guidelines to achieve our goal.
- Scan the stored proc for all nested stored procedures to infinite level
- Find all actual dependent tables and views
- Find all indexes
- Generate scripts to check fragmentation for all identified indexes
- Generate “UPDATE STATISTICS” and “ALTER INDEX” statements for all fragmented indexes
- All by not using sp_depends
Finally, here is the script that works fine. It goes one step ahead after finding dependent objects, also it generates scripts to update statistics, check fragmentation and prints alter index statements.
/* --------------------------------------------------------------------------------------------------------------------------------------- Filename : FindIndexesUsedByStoredProc_TableViews_NestedSPs.sql Purpose : To troubleshoot a stored proc. Scans all dependent objects for indexes to check fragmentation Schedule : NONE Date : 08-May-2019 Author : Sundaresh Malikayil, Sherbaz Mohamed Version : 1 SQL Version : 2008, 2008 R2, 2012, 2014, 2016, 2017 Important --arks: INPUT : @spName VARIABLE : @spName PARENT : NONE CHILD : NONE NOTE : Set value for @spName Version History: 7-May-2019 Sundar Initial version 7-May-2019 Sherbaz Scan Views for Indexes 8-May-2019 Sherbaz Scan nested stored procs 8-May-2019 Sundar Identified a bug in sp_depends logic 8-May-2019 Sherbaz Replaced sp_depends logic with sql_expression_dependencies ------------------------------------------------------------------------------------------------------------------------------------------- Usage: set the stored proc name for variable @spName ------------------------------------------------------------------------------------------------------------------------------------------- */ SET NOCOUNT ON; SET ANSI_WARNINGS OFF DECLARE @spName SYSNAME, @recordcount int SET @spName = 'stored procname' -- Set stored proc name here to analyse IF OBJECT_ID('tempdb..#Fragmentation', 'U') IS NOT NULL /*Then it exists*/ DROP TABLE #Fragmentation IF OBJECT_ID('tempdb..#table', 'U') IS NOT NULL /*Then it exists*/ DROP TABLE #table IF OBJECT_ID('tempdb..#Filtedtable', 'U') IS NOT NULL /*Then it exists*/ DROP TABLE #Filtedtable IF OBJECT_ID('tempdb..#Procs', 'U') IS NOT NULL /*Then it exists*/ drop table #Procs CREATE TABLE #Fragmentation ( SerialId int identity, objectname sysname, indexname sysname, avgFragmentation_in_percent float ) CREATE TABLE #Filtedtable ( SerialID INT IDENTITY, objectname sysname null, objectype sysname null ) create table #Procs ( id int identity, objectname sysname null, scanned bit default 0 ) CREATE TABLE #table ( id int identity, objectname sysname null, objectype sysname null, scanned bit default 0 ) insert into #Procs(objectname) values (@spname) --select * from #Procs -- Scan for all nested SPs while (select count(objectname) from #Procs where scanned = 0) > 0 begin select top 1 @spname = objectname from #Procs where scanned = 0 insert into #Procs(objectname) SELECT referenced_entity_name FROM sys.sql_expression_dependencies AS sed INNER JOIN sys.objects AS o ON sed.referencing_id = o.object_id join sys.objects o2 on o2.object_id=object_id(referenced_entity_name) WHERE referencing_id = OBJECT_ID(@spname) and o2.type = 'P' and o.object_id is not null --select (s6.name+ '.' + o1.name) -- from sys.objects o1 -- ,master.dbo.spt_values v2 -- ,sysdepends d3 -- ,master.dbo.spt_values u4 -- ,master.dbo.spt_values w5 --11667 -- ,sys.schemas s6 -- where o1.object_id = d3.depid -- and o1.type = substring(v2.name,1,2) collate catalog_default and v2.type = 'O9T' -- and u4.type = 'B' and u4.number = d3.resultobj -- and w5.type = 'B' and w5.number = d3.readobj|d3.selall -- and d3.id = object_id(@spname) -- and o1.schema_id = s6.schema_id -- and (s6.name+ '.' + o1.name) not in (select objectname from #Procs) -- and deptype < 2 and substring(v2.name, 5, 66) = 'stored procedure' update #Procs set scanned = 1 where objectname = @spname end select * from #Procs update #Procs set scanned = 0 -- Scan for all tables and views while (select count(objectname) from #Procs where scanned = 0) > 0 begin select top 1 @spname = objectname from #Procs where scanned = 0 INSERT #TABLE (objectname,objectype) SELECT referenced_entity_name,o2.type FROM sys.sql_expression_dependencies AS sed INNER JOIN sys.objects AS o ON sed.referencing_id = o.object_id join sys.objects o2 on o2.object_id=object_id(referenced_entity_name) WHERE referencing_id = OBJECT_ID(@spname) and o2.type in ('U', 'V') and o.object_id is not null update #Procs set scanned = 1 where objectname = @spname end select * from #Table order by objectname INSERT #Filtedtable (objectname, objectype) SELECT DISTINCT objectname, objectype FROM #table select * from #Filtedtable select @recordcount=count(1) from #Filtedtable --This will get the list of all Indexes used by a given stored proc DECLARE @C INT, @objectname sysname, @objectype sysname, @cmdText varchar(8000) = NULL, @refDB varchar(500), @refSchema varchar(100), @viewName varchar(500) SET @C= 1 print '/*' WHILE @C <= @recordcount BEGIN SELECT @objectname=objectname, @objectype = objectype FROM #Filtedtable WHERE SerialID = @C print '' if @objectype = 'V' begin SELECT @refDB=referenced_database_name FROM sys.sql_expression_dependencies WHERE referencing_id = OBJECT_ID(@objectname); print 'use ' + @refDB end else begin print 'use ' + db_name() end print 'GO' print 'sp_spaceused ' + @objectname + ' ' print 'UPDATE STATISTICS ' + @objectname + ' ' print 'GO' print '' SET @C = @C + 1 END print '*/' --Find the fragmentation of the indexes SET @C = 1 print 'IF OBJECT_ID(''tempdb..#Fragmentation'', ''U'') IS NOT NULL /*Then it exists*/ drop table #Fragmentation CREATE TABLE #Fragmentation ( SerialId int identity, dbname sysname, objectname sysname, indexname sysname null, avgFragmentation_in_percent float, page_count bigint )' WHILE @C <= @recordcount BEGIN SELECT @objectname=objectname, @objectype=objectype FROM #Filtedtable WHERE SerialID = @C if @objectype ='V' begin SELECT @refDB=referenced_database_name, @refSchema = referenced_schema_name FROM sys.sql_expression_dependencies WHERE referencing_id = OBJECT_ID(@objectname); --set @viewName= print ' INSERT #Fragmentation (dbname, objectname,indexname,avgFragmentation_in_percent, page_count) SELECT '''+@refDB+''','''+@refSchema+'.'+@objectname+''',name, avg_fragmentation_in_percent, a.page_count FROM '+@refDB+'.sys.dm_db_index_physical_stats (DB_ID('''+@refDB+'''), object_id('''+@refDB+'.'+@refSchema+'.'+@objectname+'''), NULL, NULL, NULL) as a join '+@refDB+'.sys.indexes as b on a.object_id = b.object_id and a.index_id = b.index_id and b.type <> 0' end else begin print ' INSERT #Fragmentation (dbname, objectname,indexname,avgFragmentation_in_percent, page_count) SELECT db_name(),'''+@objectname+''',name, avg_fragmentation_in_percent, a.page_count FROM sys.dm_db_index_physical_stats (DB_ID(), object_id('''+@objectname+'''), NULL, NULL, NULL) as a join sys.indexes as b on a.object_id = b.object_id and a.index_id = b.index_id and b.type <> 0' end SET @C = @C + 1 END print ' --select ''USE ''+dbname+''; ALTER INDEX [''+indexname+''] ON [''+objectname+''] REBUILD WITH (MAXDOP = 4, FILLFACTOR = 80, ONLINE = ON);'', avgFragmentation_in_percent, page_count FROM #Fragmentation where avgFragmentation_in_percent > 10 select ''USE ''+dbname+''; ALTER INDEX [''+indexname+''] ON [''+objectname+''] REBUILD WITH (MAXDOP = 4, FILLFACTOR = 80, ONLINE = ON(WAIT_AT_LOW_PRIORITY (MAX_DURATION = 1 minutes, ABORT_AFTER_WAIT = SELF)));'', avgFragmentation_in_percent, page_count FROM #Fragmentation where avgFragmentation_in_percent > 10 ' select * from #Fragmentation