how-to/sqlmi-how-to-all-in-one.sql (115 lines of code) (raw):

-- *************************************************************************************************************************** -- Azure SQL Managed Instance (aka SQLMI) how-to queries -- If you are interested in the whole series with screenshots and explanations, please check out https://aka.ms/sqlmi-howto -- Shared under MIT licence -- Copyright @2021 Microsoft -- *************************************************************************************************************************** -- *************************************************************************************************************************** -- Service Tier basic information -- *************************************************************************************************************************** -- Determines if we are using SQL MI IF( SERVERPROPERTY('EngineEdition') = 8 ) BEGIN PRINT 'This is an Azure SQL Managed Instance.'; END ELSE BEGIN PRINT 'This is NOT an Azure SQL Managed Instance.'; END -- Gets the SQLMI Service Tier SELECT TOP 1 sku as ServiceTier FROM [sys].[server_resource_stats] ORDER BY end_time DESC; -- Gets the SQLMI Hardware Generation SELECT TOP 1 hardware_generation as HardwareGeneration FROM [sys].[server_resource_stats] ORDER BY end_time DESC; -- *************************************************************************************************************************** -- CPU Cores and total amount of RAM -- *************************************************************************************************************************** -- Gets the number of CPU vCores and the total amount of RAM SELECT cpu_rate / 100 as CPU_vCores, CAST( (process_memory_limit_mb) /1024. as DECIMAL(9,1)) as TotalMemoryGB FROM sys.dm_os_job_object; -- Displays total & available amounts of RAM SELECT cpu_rate / 100 as CPU_vCores, CAST( (process_memory_limit_mb) /1024. as DECIMAL(9,1)) as TotalMemoryGB, CAST( non_sos_mem_gap_mb /1024. as DECIMAL(9,1)) as NonSOSMemGapGB, CAST( (process_memory_limit_mb - non_sos_mem_gap_mb) /1024. as DECIMAL(9,1)) as TotalAvailableMemoryGB FROM sys.dm_os_job_object; -- *************************************************************************************************************************** -- Disk Space -- *************************************************************************************************************************** -- Gets the total Reserved & Used Disk Space SELECT TOP 1 CAST( reserved_storage_mb / 1024. as DECIMAL(9,2) ) as ReservedStorageGB, CAST( storage_space_used_mb / 1024. as DECIMAL(9,2) ) as UsedStorageGB, CAST( (storage_space_used_mb * 100. / reserved_storage_mb) as DECIMAL(9,2)) as [ReservedStoragePercentage] FROM master.sys.server_resource_stats ORDER BY end_time DESC; -- Gets the available space for TempDB SELECT vs.volume_mount_point as VolumeMountPoint, CAST(MIN(total_bytes / 1024. / 1024 / 1024) AS NUMERIC(9,2)) as LocallyUsedGB, CAST(MIN(available_bytes / 1024. / 1024 / 1024) AS NUMERIC(9,2)) as LocallyAvailableGB, CAST(MIN((total_bytes+available_bytes) / 1024. / 1024 / 1024) AS NUMERIC(9,2)) as LocallyTotalGB FROM sys.master_files AS f CROSS APPLY sys.dm_os_volume_stats(f.database_id, f.file_id) vs WHERE UPPER(vs.volume_mount_point) like 'C:\%' GROUP BY vs.volume_mount_point; -- Gets the total amount of space available on SQLMI -- This one can be bigger than the total Reserved disk space on GP (General Purpose) service tier SELECT SUM(TotalGB) as TotalSpaceGB FROM ( SELECT vs.volume_mount_point as VolumeMountPoint, CAST(MIN(total_bytes / 1024. / 1024 / 1024) AS NUMERIC(9,2)) as UsedGB, CAST(MIN(available_bytes / 1024. / 1024 / 1024) AS NUMERIC(9,2)) as AvailableGB, CAST(MIN((total_bytes+available_bytes) / 1024. / 1024 / 1024) AS NUMERIC(9,2)) as TotalGB FROM sys.master_files AS f CROSS APPLY sys.dm_os_volume_stats(f.database_id, f.file_id) vs GROUP BY vs.volume_mount_point) fsrc; -- *************************************************************************************************************************** -- The last SQL MI failover -- *************************************************************************************************************************** -- Determines the last SQL MI failover time select sqlserver_start_time as LastInstanceStart, DATEDIFF(HOUR, sqlserver_start_time, GETDATE()) as HoursSinceFailover from sys.dm_os_sys_info; -- *************************************************************************************************************************** -- SQL MI Failover Group configuration -- *************************************************************************************************************************** -- Determines if your SQL MI is using Failover Group DECLARE @FGpartnerServer NVARCHAR(32); SELECT @FGpartnerServer = partner_server FROM sys.dm_hadr_fabric_continuous_copy_status; IF( NOT EXISTS (SELECT 1 as IsPrimaryReplica FROM sys.dm_hadr_database_replica_states rs WHERE rs.is_primary_replica = 1 ) ) BEGIN SELECT 'Your have established a connection to a Secondary Replica of SQL MI, where the information about Failover Groups is not available!' as FailoverGroupInfo; END ELSE BEGIN IF( @FGpartnerServer IS NOT NULL ) BEGIN SELECT 'Your SQL MI is using Failover Group with a partner SQL MI ''' + @FGpartnerServer + '.database.windows.net''' as FailoverGroupInfo; END ELSE SELECT 'Your SQL MI is NOT using Failover Groups!' as FailoverGroupInfo; END -- *************************************************************************************************************************** -- SQL MI Failover Group & HA Replicas Details -- *************************************************************************************************************************** -- Exposes SQL MI Replicas count SELECT IsPrimaryReplica, CASE WHEN DATABASEPROPERTYEX ('master', 'Updateability' ) = 'READ_ONLY' THEN 1 ELSE 0 END as IsHAReplica, LocallyVisibleHAReplicas, CASE WHEN GeoPartnerName IS NOT NULL AND ReplicaRole != 0 THEN 1 ELSE 0 END as IsGeoReplica, CASE WHEN GeoPartnerName IS NOT NULL AND ReplicaRole = 0 THEN 1 ELSE CASE WHEN GeoPartnerName IS NULL AND ReplicaRole IS NULL THEN NULL ELSE 0 END END as IsGeoReplicated FROM (SELECT MAX( CAST(is_primary_replica AS INT) ) as IsPrimaryReplica, MAX( role ) as ReplicaRole, MAX( partner_server ) as GeoPartnerName, SUM( CASE WHEN is_primary_replica = 0 AND is_commit_participant = 1 THEN 1 ELSE 0 END ) as LocallyVisibleHAReplicas FROM sys.dm_hadr_database_replica_states rs LEFT JOIN sys.dm_hadr_fabric_continuous_copy_status fgc ON rs.group_id = fgc.physical_database_id WHERE rs.database_id = (SELECT ISNULL(MAX(maxsrc.database_id),4) FROM sys.dm_hadr_database_replica_states maxsrc WHERE maxsrc.database_id BETWEEN 5 AND 32759) ) src; -- *************************************************************************************************************************** -- SQL MI Failover Group & HA Replicas Details -- *************************************************************************************************************************** -- Shows which Databases have Lag and/or Health Problems SELECT DB_NAME(database_id) as DatabaseName, AVG(secondary_lag_seconds*1.0) as AVGSecondaryLagSeconds, SUM( CASE WHEN synchronization_health <> 2 THEN 1 ELSE 0 END ) as NonHealthyReplicas, SUM( CASE WHEN database_state <> 0 THEN 1 ELSE 0 END ) as NonOnlineReplicas, SUM( CASE WHEN is_suspended <> 0 THEN 1 ELSE 0 END ) as SuspendedReplicas FROM sys.dm_hadr_database_replica_states GROUP BY database_id ORDER BY DB_NAME(database_id); -- Shows which replicas have problems SELECT CASE WHEN fabric_replica_role_desc IS NOT NULL THEN fabric_replica_role_desc ELSE link_type END as ReplicaRole, CASE WHEN replication_endpoint_url IS NOT NULL THEN replication_endpoint_url ELSE partner_server END as EndpointURL, synchronization_state_desc, is_commit_participant, synchronization_health_desc, is_suspended, suspend_reason_desc, DB_NAME(repl_states.database_id) as DatabaseName, repl_states.database_state_desc FROM sys.dm_hadr_database_replica_states repl_states LEFT JOIN sys.dm_hadr_fabric_replica_states frs ON repl_states.replica_id = frs.replica_id LEFT OUTER JOIN sys.dm_hadr_physical_seeding_stats seedStats ON seedStats.remote_machine_name = replication_endpoint_url AND (seedStats.local_database_name = repl_states.group_id OR seedStats.local_database_name = DB_NAME(database_id)) --AND seedStats.internal_state_desc NOT IN ('Success', 'Failed') LEFT OUTER JOIN sys.dm_hadr_fabric_continuous_copy_status fccs ON repl_states.group_database_id = fccs.copy_guid ORDER BY ReplicaRole DESC, DatabaseName; -- Measures Lag & last hardened & redone timestamps for HA & DR (Failover Groups) scenarios SELECT CASE WHEN fabric_replica_role_desc IS NOT NULL THEN fabric_replica_role_desc ELSE link_type END as ReplicaRole, CASE WHEN replication_endpoint_url IS NOT NULL THEN replication_endpoint_url ELSE partner_server END as EndpointURL, DB_NAME(repl_states.database_id) as DatabaseName, synchronization_state_desc, synchronization_health_desc, secondary_lag_seconds, last_commit_time, last_hardened_time, last_redone_time, DATEDIFF( MS, last_commit_time, last_redone_time) / 1024. as LastRedoDelaySec, log_send_queue_size, redo_queue_size FROM sys.dm_hadr_database_replica_states repl_states LEFT JOIN sys.dm_hadr_fabric_replica_states frs ON repl_states.replica_id = frs.replica_id LEFT OUTER JOIN sys.dm_hadr_fabric_continuous_copy_status fccs ON repl_states.group_database_id = fccs.copy_guid ORDER BY DatabaseName -- The queries in this section can be enhanced with the following predicate to show only the problematic situations --WHERE ( ( synchronization_health <> 2 ) -- OR -- ( database_state <> 0 ) -- OR -- ( synchronization_state <> 2 AND is_commit_participant = 1 ) -- OR -- (is_suspended = 1) ) -- *************************************************************************************************************************** -- End -- ***************************************************************************************************************************