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