Skip to content

Commit 474bdc9

Browse files
Merge pull request #801 from erikdarlingdata/fix/index-cleanup-azure-usage-stats-early-exit
sp_IndexCleanup: fix silent early exit on Azure SQL DB/Hyperscale when index usage stats are empty
2 parents a50bfaa + af06e20 commit 474bdc9

1 file changed

Lines changed: 34 additions & 9 deletions

File tree

sp_IndexCleanup/sp_IndexCleanup.sql

Lines changed: 34 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1674,7 +1674,28 @@ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
16741674
ps.object_id
16751675
HAVING
16761676
SUM(ps.row_count) >= @min_rows
1677-
)
1677+
)';
1678+
1679+
/*
1680+
Only filter on index usage when the caller actually asked for a read or write
1681+
minimum. sys.dm_db_index_usage_stats has no row for an index that hasn't been
1682+
touched since usage stats were last reset, and on Azure SQL DB / Hyperscale
1683+
those stats reset on every failover and scaling operation. Appending this
1684+
EXISTS unconditionally drops every index whenever the DMV is empty, which
1685+
silently empties #filtered_objects and makes the procedure exit with no result
1686+
set and no error. With the defaults (@min_reads = 0, @min_writes = 0) we want
1687+
to analyze every index, including never-used ones (the whole point of the tool).
1688+
@min_reads and @min_writes are validated to non-NULL, non-negative above.
1689+
*/
1690+
IF @min_reads > 0
1691+
OR @min_writes > 0
1692+
BEGIN
1693+
IF @debug = 1
1694+
BEGIN
1695+
RAISERROR('adding index usage reads/writes filter', 0, 0) WITH NOWAIT;
1696+
END;
1697+
1698+
SET @sql += N'
16781699
AND EXISTS
16791700
(
16801701
SELECT
@@ -1688,7 +1709,10 @@ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
16881709
SUM(ius.user_seeks + ius.user_scans + ius.user_lookups) >= @min_reads
16891710
OR
16901711
SUM(ius.user_updates) >= @min_writes
1691-
)
1712+
)';
1713+
END;
1714+
1715+
SET @sql += N'
16921716
OPTION(RECOMPILE);
16931717
';
16941718

@@ -1735,15 +1759,16 @@ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
17351759
BEGIN
17361760
IF @debug = 1
17371761
BEGIN
1738-
RAISERROR('No rows inserted into #filtered_objects from %s, continuing to next database...', 10, 0, @current_database_name) WITH NOWAIT;
1762+
RAISERROR('No rows inserted into #filtered_objects from %s; no indexes met the analysis criteria.', 10, 0, @current_database_name) WITH NOWAIT;
17391763
END;
17401764

1741-
IF @get_all_databases = 0
1742-
BEGIN
1743-
RETURN;
1744-
END;
1745-
1746-
/* Get the next database and continue the loop */
1765+
/*
1766+
Advance the cursor instead of bare-RETURNing. A bare RETURN here (the old
1767+
single-database behavior) exited the procedure with no result set and no
1768+
error. Falling through lets the database surface in the 'DATABASES WITH NO
1769+
QUALIFYING OBJECTS' result set below, so the caller always gets feedback. In
1770+
single-database mode the cursor is now exhausted and the loop ends normally.
1771+
*/
17471772
FETCH NEXT
17481773
FROM @database_cursor
17491774
INTO

0 commit comments

Comments
 (0)