From 4c8e4724fc48954d4a85f3e0756b3b8b1cae00ec Mon Sep 17 00:00:00 2001 From: Erik Darling <2136037+erikdarlingdata@users.noreply.github.com> Date: Sun, 3 May 2026 11:05:36 -0400 Subject: [PATCH 1/2] Docs: per-database grants for FinOps Index Analysis (#915) Adds a Permissions subsection covering the per-user-database mapping + VIEW DATABASE STATE + VIEW DEFINITION grants that sp_IndexCleanup needs to avoid hanging at 100% CPU when the dashboard/Lite login lacks access to a target database. Includes a single-DB block and an sp_MSforeachdb helper, plus the engine-bug explanation (infinite recompile loop, not sys.dm_db_partition_stats per se). Adds a matching troubleshooting entry naming the hang symptom and linking to the new section. --- README.md | 30 ++++++++++++++++++++++++++++++ 1 file changed, 30 insertions(+) diff --git a/README.md b/README.md index cc65b83..9f65b6e 100644 --- a/README.md +++ b/README.md @@ -539,6 +539,7 @@ Common issues: 2. **Query Store tab empty** — Query Store must be enabled on the target database (`ALTER DATABASE [YourDB] SET QUERY_STORE = ON`). 3. **Blocked process reports empty** — Both editions attempt to auto-configure the blocked process threshold to 5 seconds via `sp_configure`. On **AWS RDS**, `sp_configure` is not available — you must set `blocked process threshold (s)` through an RDS Parameter Group (see "AWS RDS Parameter Group Configuration" above). On **Azure SQL Database**, the threshold is fixed at 20 seconds and cannot be changed. If you still see no data on other platforms, verify the login has `ALTER SETTINGS` permission. 4. **Connection failures** — Verify network connectivity, firewall rules, and that the login has the required [permissions](#permissions). For Azure SQL Database, use a contained database user with `VIEW DATABASE STATE`. +5. **FinOps Index Analysis hangs or times out** — `sp_IndexCleanup` runs against each user database under your dashboard/Lite login. If that login has no user mapping in a target database, the procedure can hang at 100% CPU instead of erroring. See [FinOps Index Analysis](#finops-index-analysis-per-database-grants) below for the per-database grants that fix this. --- @@ -589,6 +590,35 @@ CREATE USER [YourLogin] FOR LOGIN [YourLogin]; ALTER ROLE [SQLAgentReaderRole] ADD MEMBER [YourLogin]; ``` +### FinOps Index Analysis (per-database grants) + +Applies to **both editions**. The FinOps Index Analysis tab runs `sp_IndexCleanup` against each user database you ask it to inspect, executing as your dashboard/Lite login. The grants above (`VIEW SERVER STATE`, `db_owner` on `PerformanceMonitor`, `SQLAgentReaderRole` on `msdb`) are *not* sufficient on their own — the login also needs a user mapping in every user database it will analyze, plus `VIEW DATABASE STATE` and `VIEW DEFINITION` in each. + +For each target user database: + +```sql +USE [YourTargetDatabase]; +CREATE USER [SQLServerPerfMon] FOR LOGIN [SQLServerPerfMon]; +GRANT VIEW DATABASE STATE TO [SQLServerPerfMon]; +GRANT VIEW DEFINITION TO [SQLServerPerfMon]; +``` + +Or apply broadly with `sp_MSforeachdb`: + +```sql +EXEC sp_MSforeachdb N' +USE [?]; +IF DB_ID() > 4 AND DATABASEPROPERTYEX(DB_NAME(), ''Updateability'') = ''READ_WRITE'' +BEGIN + IF NOT EXISTS (SELECT 1 FROM sys.database_principals WHERE name = ''SQLServerPerfMon'') + CREATE USER [SQLServerPerfMon] FOR LOGIN [SQLServerPerfMon]; + GRANT VIEW DATABASE STATE TO [SQLServerPerfMon]; + GRANT VIEW DEFINITION TO [SQLServerPerfMon]; +END'; +``` + +**Symptom if missing.** Without these grants, `sp_IndexCleanup` can hang at 100% CPU with no waits and never return — instead of failing fast with `Msg 916` like every other catalog DMV. The hang isn't a deadlock or a long-running scan; it's a SQL Server engine bug where a permission check at execute time gets misclassified as "this plan needs to be recompiled," producing an infinite recompile loop. Reproduces on SQL Server 2016 SP3 through 2025 CU4. Adding the grants above eliminates the hang. See issue [#915](https://github.com/erikdarlingdata/PerformanceMonitor/issues/915) for the full diagnosis. + ### Azure SQL Database (Lite Only) Azure SQL Database doesn't support server-level logins. Create a **contained database user** directly on the target database: From 83650b66d8d8f15438597d0488cb4fe3cb49dba0 Mon Sep 17 00:00:00 2001 From: Erik Darling <2136037+erikdarlingdata@users.noreply.github.com> Date: Sun, 3 May 2026 22:18:36 -0400 Subject: [PATCH 2/2] Docs: add SELECT on sys.sql_expression_dependencies grant (#915) Earlier permissions block was incomplete. VIEW DATABASE STATE + VIEW DEFINITION are necessary but not sufficient: sp_IndexCleanup also queries sys.sql_expression_dependencies via three-part name when scanning for computed columns / check constraints with UDF references, and SELECT on that catalog view defaults to db_owner only. VIEW DEFINITION does not include it. OP confirmed in the issue thread that the previous grant set still yielded Msg 229 on a real workload database. Reproduced locally on SQL2019 with a database containing a UDF-bound computed column and check constraint; adding GRANT SELECT ON sys.sql_expression_dependencies clears it. Run completes in <1s and returns the expected UDF rows. Updates: - Single-DB and sp_MSforeachdb blocks now include the third grant. - Symptoms section split into the two distinct failure modes (hang vs. Msg 229) so readers can identify which grant they are missing. - Troubleshooting bullet covers both symptoms. Co-Authored-By: Claude Opus 4.7 (1M context) --- README.md | 23 ++++++++++++++++------- 1 file changed, 16 insertions(+), 7 deletions(-) diff --git a/README.md b/README.md index 9f65b6e..bfef46a 100644 --- a/README.md +++ b/README.md @@ -539,7 +539,7 @@ Common issues: 2. **Query Store tab empty** — Query Store must be enabled on the target database (`ALTER DATABASE [YourDB] SET QUERY_STORE = ON`). 3. **Blocked process reports empty** — Both editions attempt to auto-configure the blocked process threshold to 5 seconds via `sp_configure`. On **AWS RDS**, `sp_configure` is not available — you must set `blocked process threshold (s)` through an RDS Parameter Group (see "AWS RDS Parameter Group Configuration" above). On **Azure SQL Database**, the threshold is fixed at 20 seconds and cannot be changed. If you still see no data on other platforms, verify the login has `ALTER SETTINGS` permission. 4. **Connection failures** — Verify network connectivity, firewall rules, and that the login has the required [permissions](#permissions). For Azure SQL Database, use a contained database user with `VIEW DATABASE STATE`. -5. **FinOps Index Analysis hangs or times out** — `sp_IndexCleanup` runs against each user database under your dashboard/Lite login. If that login has no user mapping in a target database, the procedure can hang at 100% CPU instead of erroring. See [FinOps Index Analysis](#finops-index-analysis-per-database-grants) below for the per-database grants that fix this. +5. **FinOps Index Analysis hangs, times out, or returns `Msg 229` on `sql_expression_dependencies`** — `sp_IndexCleanup` runs against each user database under your dashboard/Lite login. If that login has no user mapping in a target database, the procedure can hang at 100% CPU instead of erroring; if it is mapped but missing `SELECT` on `sys.sql_expression_dependencies`, it errors immediately on databases that have UDF-bound computed columns or check constraints. See [FinOps Index Analysis](#finops-index-analysis-per-database-grants) below for the full per-database grant set that fixes both. --- @@ -592,15 +592,18 @@ ALTER ROLE [SQLAgentReaderRole] ADD MEMBER [YourLogin]; ### FinOps Index Analysis (per-database grants) -Applies to **both editions**. The FinOps Index Analysis tab runs `sp_IndexCleanup` against each user database you ask it to inspect, executing as your dashboard/Lite login. The grants above (`VIEW SERVER STATE`, `db_owner` on `PerformanceMonitor`, `SQLAgentReaderRole` on `msdb`) are *not* sufficient on their own — the login also needs a user mapping in every user database it will analyze, plus `VIEW DATABASE STATE` and `VIEW DEFINITION` in each. +Applies to **both editions**. The FinOps Index Analysis tab runs `sp_IndexCleanup` against each user database you ask it to inspect, executing as your dashboard/Lite login. The grants above (`VIEW SERVER STATE`, `db_owner` on `PerformanceMonitor`, `SQLAgentReaderRole` on `msdb`) are *not* sufficient on their own — the login also needs a user mapping in every user database it will analyze, plus `VIEW DATABASE STATE`, `VIEW DEFINITION`, and `SELECT` on `sys.sql_expression_dependencies` in each. + +The third grant is the easy one to miss: by default only members of `db_owner` have `SELECT` on `sys.sql_expression_dependencies`, and `VIEW DEFINITION` does not include it. `sp_IndexCleanup` queries that catalog view (via three-part name to the target database) when checking for computed columns and check constraints that reference UDFs, so the failure only surfaces on databases that actually have those — which is why a smoke-test database may pass and a real workload database fails with `Msg 229`. For each target user database: ```sql USE [YourTargetDatabase]; CREATE USER [SQLServerPerfMon] FOR LOGIN [SQLServerPerfMon]; -GRANT VIEW DATABASE STATE TO [SQLServerPerfMon]; -GRANT VIEW DEFINITION TO [SQLServerPerfMon]; +GRANT VIEW DATABASE STATE TO [SQLServerPerfMon]; +GRANT VIEW DEFINITION TO [SQLServerPerfMon]; +GRANT SELECT ON sys.sql_expression_dependencies TO [SQLServerPerfMon]; ``` Or apply broadly with `sp_MSforeachdb`: @@ -612,12 +615,18 @@ IF DB_ID() > 4 AND DATABASEPROPERTYEX(DB_NAME(), ''Updateability'') = ''READ_WRI BEGIN IF NOT EXISTS (SELECT 1 FROM sys.database_principals WHERE name = ''SQLServerPerfMon'') CREATE USER [SQLServerPerfMon] FOR LOGIN [SQLServerPerfMon]; - GRANT VIEW DATABASE STATE TO [SQLServerPerfMon]; - GRANT VIEW DEFINITION TO [SQLServerPerfMon]; + GRANT VIEW DATABASE STATE TO [SQLServerPerfMon]; + GRANT VIEW DEFINITION TO [SQLServerPerfMon]; + GRANT SELECT ON sys.sql_expression_dependencies TO [SQLServerPerfMon]; END'; ``` -**Symptom if missing.** Without these grants, `sp_IndexCleanup` can hang at 100% CPU with no waits and never return — instead of failing fast with `Msg 916` like every other catalog DMV. The hang isn't a deadlock or a long-running scan; it's a SQL Server engine bug where a permission check at execute time gets misclassified as "this plan needs to be recompiled," producing an infinite recompile loop. Reproduces on SQL Server 2016 SP3 through 2025 CU4. Adding the grants above eliminates the hang. See issue [#915](https://github.com/erikdarlingdata/PerformanceMonitor/issues/915) for the full diagnosis. +**Symptoms if missing.** There are two distinct failure modes depending on which grant is absent: + +- *No user mapping in the target database* — `sp_IndexCleanup` can hang at 100% CPU with no waits and never return, instead of failing fast with `Msg 916` like every other catalog DMV. The hang isn't a deadlock or a long-running scan; it's a SQL Server engine bug where a permission check at execute time gets misclassified as "this plan needs to be recompiled," producing an infinite recompile loop. Reproduces on SQL Server 2016 SP3 through 2025 CU4. +- *User is mapped with `VIEW DATABASE STATE` + `VIEW DEFINITION` but no `SELECT` on `sys.sql_expression_dependencies`* — fails fast with `Msg 229, Level 14, State 5: The SELECT permission was denied on the object 'sql_expression_dependencies', database 'mssqlsystemresource', schema 'sys'` the moment a database with a UDF-bound computed column or check constraint is reached. + +Adding all three grants above eliminates both. See issue [#915](https://github.com/erikdarlingdata/PerformanceMonitor/issues/915) for the full diagnosis. ### Azure SQL Database (Lite Only)