Skip to content

Commit f95b65d

Browse files
authored
Merge pull request #512 from BrentOzarULTD/BlitzIndex_473
BlitzIndex now checks for some potential statistics issues
2 parents 2dde32b + c235930 commit f95b65d

File tree

1 file changed

+181
-2
lines changed

1 file changed

+181
-2
lines changed

sp_BlitzIndex.sql

Lines changed: 181 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -146,6 +146,10 @@ IF OBJECT_ID('tempdb..#IndexCreateTsql') IS NOT NULL
146146
IF OBJECT_ID('tempdb..#DatabaseList') IS NOT NULL
147147
DROP TABLE #DatabaseList;
148148

149+
IF OBJECT_ID('tempdb..#Statistics') IS NOT NULL
150+
DROP TABLE #Statistics;
151+
152+
149153
RAISERROR (N'Create temp tables.',0,1) WITH NOWAIT;
150154
CREATE TABLE #BlitzIndexResults
151155
(
@@ -509,6 +513,27 @@ IF OBJECT_ID('tempdb..#DatabaseList') IS NOT NULL
509513
DatabaseName NVARCHAR(256)
510514
)
511515

516+
CREATE TABLE #Statistics (
517+
database_name NVARCHAR(256) NOT NULL,
518+
table_name NVARCHAR(128) NULL,
519+
index_name sysname NOT NULL,
520+
column_name sysname NOT NULL,
521+
statistics_name NVARCHAR(128) NOT NULL,
522+
last_statistics_update DATETIME NULL,
523+
days_since_last_stats_update INT NULL,
524+
rows BIGINT NULL,
525+
rows_sampled BIGINT NULL,
526+
percent_sampled DECIMAL(18, 1) NULL,
527+
histogram_steps INT NULL,
528+
modification_counter BIGINT NULL,
529+
percent_modifications DECIMAL(18, 1) NULL,
530+
modifications_before_auto_update INT NULL,
531+
index_type_desc NVARCHAR(128) NOT NULL,
532+
table_create_date DATETIME NULL,
533+
table_modify_date DATETIME NULL
534+
);
535+
536+
512537
IF @GetAllDatabases = 1
513538
BEGIN
514539
INSERT INTO #DatabaseList (DatabaseName)
@@ -1353,8 +1378,110 @@ BEGIN TRY
13531378
AS create_tsql
13541379
FROM #IndexSanity
13551380
WHERE database_id = @DatabaseID;
1356-
1357-
END
1381+
1382+
IF ((PARSENAME(@SQLServerProductVersion, 4) >= 12)
1383+
OR (PARSENAME(@SQLServerProductVersion, 4) = 11 AND PARSENAME(@SQLServerProductVersion, 2) >= 3000)
1384+
OR (PARSENAME(@SQLServerProductVersion, 4) = 10 AND PARSENAME(@SQLServerProductVersion, 3) = 50 AND PARSENAME(@SQLServerProductVersion, 2) >= 2500))
1385+
BEGIN
1386+
RAISERROR (N'Gathering Statistics Info With Newer Syntax.',0,1) WITH NOWAIT;
1387+
SET @dsql=N'SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED;
1388+
SELECT ' + QUOTENAME(@DatabaseName,'''') + N' AS database_name,
1389+
OBJECT_NAME(s.object_id) AS table_name,
1390+
ISNULL(i.name, ''System Statistic'') AS index_name,
1391+
c.name AS column_name,
1392+
s.name AS statistics_name,
1393+
CONVERT(DATETIME, ddsp.last_updated) AS last_statistics_update,
1394+
DATEDIFF(DAY, ddsp.last_updated, GETDATE()) AS days_since_last_stats_update,
1395+
ddsp.rows,
1396+
ddsp.rows_sampled,
1397+
CAST(ddsp.rows_sampled / ( 1. * ddsp.rows ) * 100 AS DECIMAL(18, 1)) AS percent_sampled,
1398+
ddsp.steps AS histogram_steps,
1399+
ddsp.modification_counter,
1400+
CASE WHEN ddsp.modification_counter > 0
1401+
THEN CAST(ddsp.modification_counter / ( 1. * ddsp.rows ) * 100 AS DECIMAL(18, 1))
1402+
ELSE ddsp.modification_counter
1403+
END AS percent_modifications,
1404+
CASE WHEN ddsp.rows < 500 THEN 500
1405+
ELSE CAST(( ddsp.rows * .20 ) + 500 AS INT)
1406+
END AS modifications_before_auto_update,
1407+
ISNULL(i.type_desc, ''System Statistic - N/A'') AS index_type_desc,
1408+
CONVERT(DATETIME, obj.create_date) AS table_create_date,
1409+
CONVERT(DATETIME, obj.modify_date) AS table_modify_date
1410+
FROM ' + QUOTENAME(@DatabaseName) + N'.sys.stats AS s
1411+
JOIN ' + QUOTENAME(@DatabaseName) + N'.sys.stats_columns sc
1412+
ON sc.object_id = s.object_id
1413+
AND sc.stats_id = s.stats_id
1414+
JOIN ' + QUOTENAME(@DatabaseName) + N'.sys.columns c
1415+
ON c.object_id = sc.object_id
1416+
AND c.column_id = sc.column_id
1417+
JOIN ' + QUOTENAME(@DatabaseName) + N'.sys.objects obj
1418+
ON s.object_id = obj.object_id
1419+
LEFT JOIN ' + QUOTENAME(@DatabaseName) + N'.sys.indexes AS i
1420+
ON i.object_id = s.object_id
1421+
AND i.index_id = s.stats_id
1422+
CROSS APPLY ' + QUOTENAME(@DatabaseName) + N'.sys.dm_db_stats_properties(s.object_id, s.stats_id) AS ddsp
1423+
WHERE obj.is_ms_shipped = 0;'
1424+
1425+
IF @dsql IS NULL
1426+
RAISERROR('@dsql is null',16,1);
1427+
1428+
RAISERROR (N'Inserting data into #Statistics',0,1) WITH NOWAIT;
1429+
INSERT #Statistics ( database_name, table_name, index_name, column_name, statistics_name, last_statistics_update,
1430+
days_since_last_stats_update, rows, rows_sampled, percent_sampled, histogram_steps, modification_counter,
1431+
percent_modifications, modifications_before_auto_update, index_type_desc, table_create_date, table_modify_date)
1432+
1433+
EXEC sp_executesql @dsql;
1434+
1435+
END
1436+
ELSE
1437+
BEGIN
1438+
RAISERROR (N'Gathering Statistics Info With Older Syntax.',0,1) WITH NOWAIT;
1439+
SET @dsql=N'SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED;
1440+
SELECT ' + QUOTENAME(@DatabaseName,'''') + N' AS DatabaseName,
1441+
OBJECT_NAME(s.object_id) AS table_name,
1442+
ISNULL(i.name, ''System Statistic'') AS index_name,
1443+
c.name AS column_name,
1444+
s.name AS statistics_name,
1445+
CONVERT(DATETIME, STATS_DATE(obj.object_id, i.index_id)) AS last_statistics_update,
1446+
DATEDIFF(DAY, STATS_DATE(obj.object_id, i.index_id), GETDATE()) AS days_since_last_stats_update,
1447+
si.rowcnt,
1448+
si.rowmodctr,
1449+
CASE WHEN si.rowmodctr > 0 THEN CAST(si.rowmodctr / ( 1. * si.rowcnt ) * 100 AS DECIMAL(18, 1))
1450+
ELSE si.rowmodctr
1451+
END AS percent_modifications,
1452+
CASE WHEN si.rowcnt < 500 THEN 500
1453+
ELSE CAST(( si.rowcnt * .20 ) + 500 AS INT)
1454+
END AS modifications_before_auto_update,
1455+
ISNULL(i.type_desc, ''System Statistic - N/A'') AS index_type_desc,
1456+
CONVERT(DATETIME, obj.create_date) AS table_create_date,
1457+
CONVERT(DATETIME, obj.modify_date) AS table_modify_date
1458+
FROM ' + QUOTENAME(@DatabaseName) + N'.sys.stats AS s
1459+
JOIN ' + QUOTENAME(@DatabaseName) + N'.sys.sysindexes si
1460+
ON si.name = s.name
1461+
JOIN ' + QUOTENAME(@DatabaseName) + N'.sys.stats_columns sc
1462+
ON sc.object_id = s.object_id
1463+
AND sc.stats_id = s.stats_id
1464+
JOIN ' + QUOTENAME(@DatabaseName) + N'.sys.columns c
1465+
ON c.object_id = sc.object_id
1466+
AND c.column_id = sc.column_id
1467+
JOIN ' + QUOTENAME(@DatabaseName) + N'.sys.objects obj
1468+
ON s.object_id = obj.object_id
1469+
LEFT JOIN ' + QUOTENAME(@DatabaseName) + N'.sys.indexes AS i
1470+
ON i.object_id = s.object_id
1471+
AND i.index_id = s.stats_id
1472+
WHERE obj.is_ms_shipped = 0;'
1473+
1474+
IF @dsql IS NULL
1475+
RAISERROR('@dsql is null',16,1);
1476+
1477+
RAISERROR (N'Inserting data into #Statistics',0,1) WITH NOWAIT;
1478+
INSERT #Statistics(database_name, table_name, index_name, column_name, statistics_name,
1479+
last_statistics_update, days_since_last_stats_update, rows, modification_counter,
1480+
percent_modifications, modifications_before_auto_update, index_type_desc, table_create_date, table_modify_date)
1481+
1482+
EXEC sp_executesql @dsql;
1483+
END
1484+
END
13581485
END TRY
13591486
BEGIN CATCH
13601487
RAISERROR (N'Failure populating temp tables.', 0,1) WITH NOWAIT;
@@ -2824,6 +2951,58 @@ BEGIN;
28242951

28252952

28262953
END
2954+
2955+
----------------------------------------
2956+
--Statistics Info: Check_id 90-99
2957+
----------------------------------------
2958+
BEGIN
2959+
2960+
RAISERROR(N'check_id 90: Outdated statistics', 0,1) WITH NOWAIT;
2961+
INSERT #BlitzIndexResults ( check_id, Priority, findings_group, finding, [database_name], URL, details, index_definition,
2962+
secret_columns, index_usage_summary, index_size_summary )
2963+
SELECT 90 AS check_id,
2964+
200 AS Priority,
2965+
'Functioning Statistaholics' AS findings_group,
2966+
'Statistic Abandonment Issues',
2967+
s.database_name,
2968+
'' AS URL,
2969+
'Statistics on this table were last updated ' +
2970+
CASE s.last_statistics_update WHEN NULL THEN N' NEVER '
2971+
ELSE CONVERT(NVARCHAR(20), s.last_statistics_update) +
2972+
' have had ' + CONVERT(NVARCHAR(100), s.modification_counter) +
2973+
' modifications in that time, which is ' +
2974+
CONVERT(NVARCHAR(100), s.percent_modifications) +
2975+
'% of the table.'
2976+
END,
2977+
QUOTENAME(database_name) + '.' + QUOTENAME(s.index_name) + '.' + QUOTENAME(s.statistics_name) + '.' + QUOTENAME(s.column_name) AS index_definition,
2978+
'N/A' AS secret_columns,
2979+
'N/A' AS index_usage_summary,
2980+
'N/A' AS index_size_summary
2981+
FROM #Statistics AS s
2982+
WHERE s.last_statistics_update <= CONVERT(DATETIME, GETDATE() - 7)
2983+
AND s.percent_modifications >= 10.
2984+
AND s.rows >= 10000
2985+
2986+
RAISERROR(N'check_id 91: Statistics with a low sample rate', 0,1) WITH NOWAIT;
2987+
INSERT #BlitzIndexResults ( check_id, Priority, findings_group, finding, [database_name], URL, details, index_definition,
2988+
secret_columns, index_usage_summary, index_size_summary )
2989+
SELECT 91 AS check_id,
2990+
200 AS Priority,
2991+
'Functioning Statistaholics' AS findings_group,
2992+
'Antisocial Samples',
2993+
s.database_name,
2994+
'' AS URL,
2995+
'Only ' + CONVERT(NVARCHAR(100), s.percent_sampled) + '% of the rows were samplped during the last statistics update. This may lead to poor cardinality estimates.' ,
2996+
QUOTENAME(database_name) + '.' + QUOTENAME(s.index_name) + '.' + QUOTENAME(s.statistics_name) + '.' + QUOTENAME(s.column_name) AS index_definition,
2997+
'N/A' AS secret_columns,
2998+
'N/A' AS index_usage_summary,
2999+
'N/A' AS index_size_summary
3000+
FROM #Statistics AS s
3001+
WHERE s.rows_sampled < 1.
3002+
AND s.rows >= 10000
3003+
3004+
3005+
END
28273006

28283007
RAISERROR(N'Insert a row to help people find help', 0,1) WITH NOWAIT;
28293008
IF DATEDIFF(MM, @VersionDate, GETDATE()) > 6

0 commit comments

Comments
 (0)