Skip to content

Commit 26be0d4

Browse files
authored
Merge pull request #3846 from BrentOzarULTD/3736_sp_BlitzIndex_JSON
#3736 Add JSON index support in sp_BlitzIndex
2 parents 8d6c648 + 6a028e0 commit 26be0d4

2 files changed

Lines changed: 115 additions & 6 deletions

File tree

Documentation/sp_BlitzIndex_Checks_by_Priority.md

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -6,8 +6,8 @@ Before adding a new check, make sure to add a Github issue for it first, and hav
66

77
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.
88

9-
CURRENT HIGH CHECKID: 128
10-
If you want to add a new check, start at 129.
9+
CURRENT HIGH CHECKID: 129
10+
If you want to add a new check, start at 130.
1111

1212
| Priority | FindingsGroup | Finding | URL | CheckID |
1313
| -------- | ----------------------- | --------------------------------------------------------------- | ---------------------------------------------------------------------------------------------- | ------- |
@@ -47,6 +47,7 @@ If you want to add a new check, start at 129.
4747
| 150 | Abnormal Design Pattern | In-Memory OLTP | https://www.brentozar.com/go/AbnormalPsychology | 73 |
4848
| 150 | Abnormal Design Pattern | Non-Aligned Index on a Partitioned Table | https://www.brentozar.com/go/AbnormalPsychology | 65 |
4949
| 150 | Abnormal Design Pattern | Partitioned Index | https://www.brentozar.com/go/AbnormalPsychology | 64 |
50+
| 150 | Abnormal Design Pattern | JSON Index | https://www.brentozar.com/go/AbnormalPsychology | 129 |
5051
| 150 | Abnormal Design Pattern | Spatial Index | https://www.brentozar.com/go/AbnormalPsychology | 62 |
5152
| 150 | Abnormal Design Pattern | XML Index | https://www.brentozar.com/go/AbnormalPsychology | 60 |
5253
| 150 | Over-Indexing | Approximate: Wide Indexes (7 or More Columns) | https://www.brentozar.com/go/IndexHoarder | 23 |

sp_BlitzIndex.sql

Lines changed: 112 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -503,7 +503,7 @@ IF OBJECT_ID('tempdb..#dm_db_index_operational_stats') IS NOT NULL
503503
THEN ( user_seeks + user_scans + user_lookups ) / (1.0 * user_updates)
504504
ELSE 0 END AS MONEY) ,
505505
[index_usage_summary] AS
506-
CASE WHEN is_spatial = 1 THEN N'Not Tracked'
506+
CASE WHEN is_spatial = 1 OR is_json = 1 THEN N'Not Tracked'
507507
WHEN is_disabled = 1 THEN N'Disabled'
508508
ELSE N'Reads: ' +
509509
REPLACE(CONVERT(NVARCHAR(30),CAST((user_seeks + user_scans + user_lookups) AS MONEY), 1), N'.00', N'')
@@ -1795,7 +1795,7 @@ BEGIN TRY
17951795
CASE WHEN ( @IncludeInactiveIndexes = 0
17961796
AND @Mode IN (0, 4)
17971797
AND @TableName IS NULL )
1798-
THEN N'AND ( us.user_seeks + us.user_scans + us.user_lookups + us.user_updates ) > 0'
1798+
THEN N'AND ( us.user_seeks + us.user_scans + us.user_lookups + us.user_updates > 0 OR si.type = 9 )'
17991799
ELSE N''
18001800
END
18011801
+ N'OPTION ( RECOMPILE );
@@ -2201,6 +2201,54 @@ BEGIN TRY
22012201

22022202
END; --End Check For @SkipPartitions = 0
22032203

2204+
/* Populate JSON index sizes from internal tables - JSON indexes store their data
2205+
in sys.internal_tables, not in sys.dm_db_partition_stats for the parent table */
2206+
IF EXISTS (SELECT * FROM sys.all_objects WHERE name = 'json_indexes')
2207+
BEGIN
2208+
RAISERROR (N'Inserting JSON index size data from internal tables',0,1) WITH NOWAIT;
2209+
SET @dsql = N'SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED;
2210+
SELECT ' + CAST(@DatabaseID AS NVARCHAR(10)) + N' AS database_id,
2211+
it.parent_id AS object_id,
2212+
s.name AS schema_name,
2213+
it.parent_minor_id AS index_id,
2214+
ps.partition_number,
2215+
ps.row_count,
2216+
ps.reserved_page_count * 8. / 1024. AS reserved_MB,
2217+
ps.lob_reserved_page_count * 8. / 1024. AS reserved_LOB_MB,
2218+
ps.row_overflow_reserved_page_count * 8. / 1024. AS reserved_row_overflow_MB,
2219+
NULL AS lock_escalation_desc,
2220+
NULL AS data_compression_desc,
2221+
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
2222+
0 AS reserved_dictionary_MB
2223+
FROM ' + QUOTENAME(@DatabaseName) + N'.sys.internal_tables it
2224+
JOIN ' + QUOTENAME(@DatabaseName) + N'.sys.dm_db_partition_stats ps ON it.object_id = ps.object_id AND ps.index_id = 1
2225+
JOIN ' + QUOTENAME(@DatabaseName) + N'.sys.objects so ON it.parent_id = so.object_id
2226+
JOIN ' + QUOTENAME(@DatabaseName) + N'.sys.schemas s ON so.schema_id = s.schema_id
2227+
WHERE it.internal_type_desc = ''JSON_INDEX_TABLE''
2228+
' + CASE WHEN @ObjectID IS NOT NULL THEN N'AND it.parent_id = ' + CAST(@ObjectID AS NVARCHAR(30)) + N' ' ELSE N'' END + N'
2229+
OPTION (RECOMPILE);';
2230+
2231+
IF @Debug = 1
2232+
BEGIN
2233+
PRINT SUBSTRING(@dsql, 0, 4000);
2234+
PRINT SUBSTRING(@dsql, 4000, 8000);
2235+
END;
2236+
2237+
INSERT #IndexPartitionSanity ( [database_id], [object_id], [schema_name], index_id, partition_number,
2238+
row_count, reserved_MB, reserved_LOB_MB, reserved_row_overflow_MB,
2239+
lock_escalation_desc, data_compression_desc,
2240+
leaf_insert_count, leaf_delete_count, leaf_update_count,
2241+
range_scan_count, singleton_lookup_count, forwarded_fetch_count,
2242+
lob_fetch_in_pages, lob_fetch_in_bytes,
2243+
row_overflow_fetch_in_pages, row_overflow_fetch_in_bytes,
2244+
row_lock_count, row_lock_wait_count, row_lock_wait_in_ms,
2245+
page_lock_count, page_lock_wait_count, page_lock_wait_in_ms,
2246+
index_lock_promotion_attempt_count, index_lock_promotion_count,
2247+
page_latch_wait_count, page_latch_wait_in_ms,
2248+
page_io_latch_wait_count, page_io_latch_wait_in_ms,
2249+
reserved_dictionary_MB)
2250+
EXEC sp_executesql @dsql;
2251+
END;
22042252

22052253
IF @Mode NOT IN(1, 2)
22062254
BEGIN
@@ -3200,6 +3248,42 @@ FROM #IndexSanity si
32003248
AND c.index_id = si.index_id
32013249
) AS D4 ( count_included_columns, count_key_columns );
32023250

3251+
/* JSON indexes have key_ordinal=0 in sys.index_columns, so the above updates skip them.
3252+
Populate column names for JSON indexes from #IndexColumns where key_ordinal = 0. */
3253+
RAISERROR (N'Updating column names for JSON indexes',0,1) WITH NOWAIT;
3254+
UPDATE si
3255+
SET key_column_names = c.column_name
3256+
+ N' {' + c.system_type_name
3257+
+ CASE c.max_length WHEN -1 THEN N' (max)' ELSE
3258+
CASE
3259+
WHEN c.system_type_name IN (N'char',N'varchar',N'binary',N'varbinary') THEN N' (' + CAST(c.max_length AS NVARCHAR(20)) + N')'
3260+
WHEN c.system_type_name IN (N'nchar',N'nvarchar') THEN N' (' + CAST(c.max_length/2 AS NVARCHAR(20)) + N')'
3261+
ELSE N' ' + CAST(c.max_length AS NVARCHAR(50))
3262+
END
3263+
END
3264+
+ N'}',
3265+
key_column_names_with_sort_order = c.column_name
3266+
+ N' {' + c.system_type_name
3267+
+ CASE c.max_length WHEN -1 THEN N' (max)' ELSE
3268+
CASE
3269+
WHEN c.system_type_name IN (N'char',N'varchar',N'binary',N'varbinary') THEN N' (' + CAST(c.max_length AS NVARCHAR(20)) + N')'
3270+
WHEN c.system_type_name IN (N'nchar',N'nvarchar') THEN N' (' + CAST(c.max_length/2 AS NVARCHAR(20)) + N')'
3271+
ELSE N' ' + CAST(c.max_length AS NVARCHAR(50))
3272+
END
3273+
END
3274+
+ N'}',
3275+
key_column_names_with_sort_order_no_types = QUOTENAME(c.column_name),
3276+
count_key_columns = 1
3277+
FROM #IndexSanity si
3278+
JOIN #IndexColumns c ON si.database_id = c.database_id
3279+
AND si.schema_name = c.schema_name
3280+
AND si.object_id = c.object_id
3281+
AND si.index_id = c.index_id
3282+
AND c.key_ordinal = 0
3283+
AND c.is_included_column = 0
3284+
WHERE si.is_json = 1
3285+
AND si.key_column_names IS NULL;
3286+
32033287
RAISERROR (N'Updating index_sanity_id on #IndexPartitionSanity',0,1) WITH NOWAIT;
32043288
UPDATE #IndexPartitionSanity
32053289
SET index_sanity_id = i.index_sanity_id
@@ -3322,7 +3406,11 @@ SELECT
33223406
CASE index_id WHEN 0 THEN N'ALTER TABLE ' + QUOTENAME([database_name]) + N'.' + QUOTENAME([schema_name]) + N'.' + QUOTENAME([object_name]) + ' REBUILD;'
33233407
ELSE
33243408
CASE WHEN is_XML = 1 OR is_spatial = 1 OR is_in_memory_oltp = 1 THEN N'' /* Not even trying for these just yet...*/
3325-
ELSE
3409+
WHEN is_json = 1 THEN
3410+
N'CREATE JSON INDEX ' + QUOTENAME(index_name) + N' ON ' +
3411+
QUOTENAME([database_name]) + N'.' + QUOTENAME([schema_name]) + N'.' + QUOTENAME([object_name]) +
3412+
N' (' + ISNULL(key_column_names_with_sort_order_no_types, N'') + N');'
3413+
ELSE
33263414
CASE WHEN is_primary_key=1 THEN
33273415
N'ALTER TABLE ' + QUOTENAME([database_name]) + N'.' + QUOTENAME([schema_name]) +
33283416
N'.' + QUOTENAME([object_name]) +
@@ -5798,7 +5886,27 @@ BEGIN
57985886
ISNULL(sz.index_size_summary,'') AS index_size_summary
57995887
FROM #IndexSanity AS i
58005888
JOIN #IndexSanitySize sz ON i.index_sanity_id = sz.index_sanity_id
5801-
WHERE i.is_spatial = 1
5889+
Where i.is_spatial = 1
5890+
OPTION ( RECOMPILE );
5891+
5892+
RAISERROR(N'check_id 129: JSON indexes', 0,1) WITH NOWAIT;
5893+
INSERT #BlitzIndexResults ( check_id, index_sanity_id, Priority, findings_group, finding, [database_name], URL, details, index_definition,
5894+
secret_columns, index_usage_summary, index_size_summary )
5895+
SELECT 129 AS check_id,
5896+
i.index_sanity_id,
5897+
150 AS Priority,
5898+
N'Abnormal Design Pattern' AS findings_group,
5899+
N'JSON Index' AS finding,
5900+
[database_name] AS [Database Name],
5901+
N'https://www.brentozar.com/go/AbnormalPsychology' AS URL,
5902+
i.db_schema_object_indexid AS details,
5903+
i.index_definition,
5904+
i.secret_columns,
5905+
i.index_usage_summary,
5906+
ISNULL(sz.index_size_summary,'') AS index_size_summary
5907+
FROM #IndexSanity AS i
5908+
JOIN #IndexSanitySize sz ON i.index_sanity_id = sz.index_sanity_id
5909+
WHERE i.is_json = 1
58025910
OPTION ( RECOMPILE );
58035911

58045912
RAISERROR(N'check_id 63: Compressed indexes', 0,1) WITH NOWAIT;

0 commit comments

Comments
 (0)