Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 4 additions & 2 deletions Documentation/sp_BlitzIndex_Checks_by_Priority.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,8 +6,8 @@ Before adding a new check, make sure to add a Github issue for it first, and hav

If you want to change anything about a check - the priority, finding, URL, or ID - open a Github issue first. The relevant scripts have to be updated too.

CURRENT HIGH CHECKID: 124
If you want to add a new check, start at 125.
CURRENT HIGH CHECKID: 126
If you want to add a new check, start at 127.

| Priority | FindingsGroup | Finding | URL | CheckID |
| -------- | ----------------------- | --------------------------------------------------------------- | ---------------------------------------------------------------- | ------- |
Expand All @@ -25,6 +25,7 @@ If you want to add a new check, start at 125.
| 80 | Abnormal Design Pattern | Filter Columns Not In Index Definition | https://www.brentozar.com/go/IndexFeatures | 34 |
| 80 | Abnormal Design Pattern | History Table With NonClustered Index | https://sqlserverfast.com/blog/hugo/2023/09/an-update-on-merge/ | 124 |
| 90 | Statistics Warnings | Low Sampling Rates | https://www.brentozar.com/go/stats | 91 |
| 90 | Statistics Warnings | Persisted Sampling Rates (Unexpected) | `https://www.youtube.com/watch?v=V5illj_KOJg&t=758s` | 125 |
| 90 | Statistics Warnings | Statistics Not Updated Recently | https://www.brentozar.com/go/stats | 90 |
| 90 | Statistics Warnings | Statistics with NO RECOMPUTE | https://www.brentozar.com/go/stats | 92 |
| 100 | Over-Indexing | NC index with High Writes:Reads | https://www.brentozar.com/go/IndexHoarder | 48 |
Expand Down Expand Up @@ -61,6 +62,7 @@ If you want to add a new check, start at 125.
| 200 | Abnormal Design Pattern | Temporal Tables | https://www.brentozar.com/go/AbnormalPsychology | 110 |
| 200 | Repeated Calculations | Computed Columns Not Persisted | https://www.brentozar.com/go/serialudf | 100 |
| 200 | Statistics Warnings | Statistics With Filters | https://www.brentozar.com/go/stats | 93 |
| 200 | Statistics Warnings | Persisted Sampling Rates (Expected) | `https://www.youtube.com/watch?v=V5illj_KOJg&t=758s` | 126 |
| 200 | Over-Indexing | High Ratio of Nulls | https://www.brentozar.com/go/IndexHoarder | 25 |
| 200 | Over-Indexing | High Ratio of Strings | https://www.brentozar.com/go/IndexHoarder | 27 |
| 200 | Over-Indexing | Wide Tables: 35+ cols or > 2000 non-LOB bytes | https://www.brentozar.com/go/IndexHoarder | 26 |
Expand Down
1 change: 1 addition & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -288,6 +288,7 @@ In addition to the [parameters common to many of the stored procedures](#paramet

* @SkipPartitions = 1 - add this if you want to analyze large partitioned tables. We skip these by default for performance reasons.
* @SkipStatistics = 0 - right now, by default, we skip statistics analysis because we've had some performance issues on this.
* @UsualStatisticsSamplingPercent = 100 (default) - By default, @SkipStatistics = 0 with either @Mode = 0 or @Mode = 4 does not inform you of persisted statistics sample rates if that rate is 100. Use a different float if you usually persist a different sample percentage and do not want to be warned about it. Use NULL if you want to hear about every persisted sample rate.
* @Filter = 0 (default) - 1=No low-usage warnings for objects with 0 reads. 2=Only warn for objects >= 500MB
* @OutputDatabaseName, @OutputSchemaName, @OutputTableName - these only work for @Mode = 2, index usage detail.

Expand Down
87 changes: 79 additions & 8 deletions sp_BlitzIndex.sql
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ ALTER PROCEDURE dbo.sp_BlitzIndex
/*Note:@Filter doesn't do anything unless @Mode=0*/
@SkipPartitions BIT = 0,
@SkipStatistics BIT = 1,
@UsualStatisticsSamplingPercent FLOAT = 100, /* FLOAT to match sys.dm_db_stats_properties. More detail later. 100 by default because Brent suggests that if people are persisting statistics at all, they are probably doing 100 in lots of places and not filtering that out would produce noise. */
@GetAllDatabases BIT = 0,
@ShowColumnstoreOnly BIT = 0, /* Will show only the Row Group and Segment details for a table with a columnstore index. */
@BringThePain BIT = 0,
Expand Down Expand Up @@ -738,7 +739,8 @@ IF OBJECT_ID('tempdb..#dm_db_index_operational_stats') IS NOT NULL
table_modify_date DATETIME NULL,
no_recompute BIT NULL,
has_filter BIT NULL,
filter_definition NVARCHAR(MAX) NULL
filter_definition NVARCHAR(MAX) NULL,
persisted_sample_percent FLOAT NULL
);

CREATE TABLE #ComputedColumns
Expand Down Expand Up @@ -2279,7 +2281,7 @@ OPTION (RECOMPILE);';
INSERT #Statistics ( database_id, database_name, table_name, schema_name, index_name, column_names, statistics_name, last_statistics_update,
days_since_last_stats_update, rows, rows_sampled, percent_sampled, histogram_steps, modification_counter,
percent_modifications, modifications_before_auto_update, index_type_desc, table_create_date, table_modify_date,
no_recompute, has_filter, filter_definition)
no_recompute, has_filter, filter_definition, persisted_sample_percent)
SELECT DB_ID(N' + QUOTENAME(@DatabaseName,'''') + N') AS [database_id],
@i_DatabaseName AS database_name,
obj.name AS table_name,
Expand All @@ -2306,7 +2308,18 @@ OPTION (RECOMPILE);';
CONVERT(DATETIME, obj.modify_date) AS table_modify_date,
s.no_recompute,
s.has_filter,
s.filter_definition
s.filter_definition,
'
+ CASE WHEN EXISTS
(
/* We cannot trust checking version numbers, like we did above, because Azure disagrees. */
SELECT 1
FROM sys.all_columns AS all_cols
WHERE all_cols.[object_id] = OBJECT_ID(N'sys.dm_db_stats_properties', N'IF') AND all_cols.[name] = N'persisted_sample_percent'
)
THEN N'ddsp.persisted_sample_percent'
ELSE N'NULL AS persisted_sample_percent' END
+ N'
FROM ' + QUOTENAME(@DatabaseName) + N'.sys.stats AS s
JOIN ' + QUOTENAME(@DatabaseName) + N'.sys.objects obj
ON s.object_id = obj.object_id
Expand Down Expand Up @@ -2354,7 +2367,7 @@ OPTION (RECOMPILE);';
INSERT #Statistics(database_id, database_name, table_name, schema_name, index_name, column_names, statistics_name,
last_statistics_update, days_since_last_stats_update, rows, modification_counter,
percent_modifications, modifications_before_auto_update, index_type_desc, table_create_date, table_modify_date,
no_recompute, has_filter, filter_definition)
no_recompute, has_filter, filter_definition, persisted_sample_percent)
SELECT DB_ID(N' + QUOTENAME(@DatabaseName,'''') + N') AS [database_id],
@i_DatabaseName AS database_name,
obj.name AS table_name,
Expand All @@ -2379,9 +2392,11 @@ OPTION (RECOMPILE);';
'
+ CASE WHEN @SQLServerProductVersion NOT LIKE '9%'
THEN N's.has_filter,
s.filter_definition'
s.filter_definition,'
ELSE N'NULL AS has_filter,
NULL AS filter_definition' END
NULL AS filter_definition,' END
/* Certainly NULL. This branch does not even join on the table that this column comes from. */
+ N'NULL AS persisted_sample_percent'
+ N'
FROM ' + QUOTENAME(@DatabaseName) + N'.sys.stats AS s
INNER HASH JOIN ' + QUOTENAME(@DatabaseName) + N'.sys.sysindexes si
Expand Down Expand Up @@ -4454,7 +4469,7 @@ BEGIN
END;

----------------------------------------
--Statistics Info: Check_id 90-99
--Statistics Info: Check_id 90-99, as well as 125-126
----------------------------------------

RAISERROR(N'check_id 90: Outdated statistics', 0,1) WITH NOWAIT;
Expand Down Expand Up @@ -4503,6 +4518,63 @@ BEGIN
OR (s.rows > 1000000 AND s.percent_sampled < 1)
OPTION ( RECOMPILE );

RAISERROR(N'check_id 125: Persisted Sampling Rates (Unexpected)', 0,1) WITH NOWAIT;
INSERT #BlitzIndexResults ( check_id, Priority, findings_group, finding, [database_name], URL, details, index_definition,
secret_columns, index_usage_summary, index_size_summary )
SELECT 125 AS check_id,
90 AS Priority,
'Statistics Warnings' AS findings_group,
'Persisted Sampling Rates (Unexpected)',
s.database_name,
'https://www.youtube.com/watch?v=V5illj_KOJg&t=758s' AS URL,
'The persisted statistics sample rate is ' + CONVERT(NVARCHAR(100), s.persisted_sample_percent) + '%'
+ CASE WHEN @UsualStatisticsSamplingPercent IS NOT NULL
THEN (N' rather than your expected @UsualStatisticsSamplingPercent value of ' + CONVERT(NVARCHAR(100), @UsualStatisticsSamplingPercent) + '%')
ELSE ''
END
+ N'. This may indicate that somebody is doing statistics rocket surgery. If not, consider updating statistics more frequently.' AS details,
QUOTENAME(database_name) + '.' + QUOTENAME(s.schema_name) + '.' + QUOTENAME(s.table_name) + '.' + QUOTENAME(s.index_name) + '.' + QUOTENAME(s.statistics_name) + '.' + QUOTENAME(s.column_names) AS index_definition,
'N/A' AS secret_columns,
'N/A' AS index_usage_summary,
'N/A' AS index_size_summary
FROM #Statistics AS s
/*
We have to do float comparison here, so it is time to explain why @UsualStatisticsSamplingPercent is a float.
The foremost reason is that it is a float because we are comparing it to the persisted_sample_percent column in sys.dm_db_stats_properties and that column is a float.
You may correctly object that CREATE STATISTICS with a decimal as your WITH SAMPLE [...] PERCENT is a syntax error and conclude that integers are enough.
However, `WITH SAMPLE [...] ROWS` is allowed with PERSIST_SAMPLE_PERCENT = ON and you can use that to persist a non-integer sample rate.
So, yes, we really have to use floats.
*/
WHERE
/* persisted_sample_percent is either zero or NULL when the statistic is not persisted. */
s.persisted_sample_percent > 0.0001
AND
(
ABS(@UsualStatisticsSamplingPercent - s.persisted_sample_percent) > 0.1
OR @UsualStatisticsSamplingPercent IS NULL
)
OPTION ( RECOMPILE );

RAISERROR(N'check_id 126: Persisted Sampling Rates (Expected)', 0,1) WITH NOWAIT;
INSERT #BlitzIndexResults ( check_id, Priority, findings_group, finding, [database_name], URL, details, index_definition,
secret_columns, index_usage_summary, index_size_summary )
SELECT 126 AS check_id,
200 AS Priority,
'Statistics Warnings' AS findings_group,
'Persisted Sampling Rates (Expected)',
s.database_name,
'https://www.youtube.com/watch?v=V5illj_KOJg&t=758s' AS URL,
CONVERT(NVARCHAR(100), COUNT(*)) + ' statistic(s) with a persisted sample rate matching your desired persisted sample rate, ' + CONVERT(NVARCHAR(100), @UsualStatisticsSamplingPercent) + N'%. Set @UsualStatisticsSamplingPercent to NULL if you want to see all of them in this result set. Its default value is 100.' AS details,
s.database_name + N' (Entire database)' AS index_definition,
'N/A' AS secret_columns,
'N/A' AS index_usage_summary,
'N/A' AS index_size_summary
FROM #Statistics AS s
WHERE ABS(@UsualStatisticsSamplingPercent - s.persisted_sample_percent) <= 0.1
AND @UsualStatisticsSamplingPercent IS NOT NULL
GROUP BY s.database_name
OPTION ( RECOMPILE );

RAISERROR(N'check_id 92: Statistics with NO RECOMPUTE', 0,1) WITH NOWAIT;
INSERT #BlitzIndexResults ( check_id, Priority, findings_group, finding, [database_name], URL, details, index_definition,
secret_columns, index_usage_summary, index_size_summary )
Expand All @@ -4521,7 +4593,6 @@ BEGIN
WHERE s.no_recompute = 1
OPTION ( RECOMPILE );


RAISERROR(N'check_id 94: Check Constraints That Reference Functions', 0,1) WITH NOWAIT;
INSERT #BlitzIndexResults ( check_id, Priority, findings_group, finding, [database_name], URL, details, index_definition,
secret_columns, index_usage_summary, index_size_summary )
Expand Down
Loading