mssql常用脚本
那些你一定会用到的 SQL Server 查询脚本,本文脚本基于 SQL Server 2008 - 2019 测试,可用于定期巡检。
1.运行状态
- 频繁使用的表(以user_seeks倒排序)TOP 200
IF OBJECT_ID('tempdb..#tables', 'U') <> 0
DROP TABLE #tables
GO
SELECT db_id() AS database_id,db_name() AS database_name,s.name AS schema_name,t.name AS table_name,t.object_id AS table_id INTO #tables FROM sys.tables AS t join sys.schemas AS s ON t.schema_id=s.schema_id WHERE 1 = 0
EXECUTE master.sys.sp_MSforeachdb 'if ''?'' not in (''master'',''model'',''msdb'',''tempdb'') begin USE [?]; insert into #tables SELECT db_id(),db_name(),s.name,t.name,t.object_id FROM sys.tables AS t join sys.schemas AS s ON t.schema_id=s.schema_id end'
SELECT TOP 200 tmp.database_name,tmp.schema_name,tmp.table_name,sd.index_id,sd.user_seeks,sd.user_scans,sd.user_lookups,sd.user_updates
FROM sys.dm_db_index_usage_stats as sd
INNER JOIN #tables as tmp ON db_name(sd.database_id)=tmp.database_name and sd.object_id= tmp.table_id
ORDER BY (sd.user_seeks + sd.user_scans + sd.user_lookups + sd.user_updates) DESC
- 连接数使用率
SELECT
(SELECT COUNT(1) FROM sys.dm_exec_connections WHERE session_id > 50) AS active_connections,
CONVERT(INT, ( SELECT value FROM sys.configurations WHERE name = 'user connections' )) AS configured_user_connections,
@@MAX_CONNECTIONS AS max_connections,
CASE WHEN CONVERT(INT, (SELECT value FROM sys.configurations WHERE name = 'user connections')) = 0
THEN 0.00
ELSE CAST(
(SELECT COUNT(1) FROM sys.dm_exec_connections WHERE session_id > 50) * 100.0 /
CONVERT(INT, (SELECT value FROM sys.configurations WHERE name = 'user connections'))
AS DECIMAL(18,2)
)
END AS connection_usage_percent;
- 实例配置信息
--.查看基本配置
SET NOCOUNT ON;
SET QUOTED_IDENTIFIER ON;
DECLARE @SVer TABLE(ID int, Name sysname, Internal_Value int, Value nvarchar(512))
DECLARE @drivers TABLE(drive sysname, MB_Avail bigint)
DECLARE @instance_info TABLE(sid int IDENTITY(1,1), config_name nvarchar(40), run_value nvarchar(256))
DECLARE @manufacturer TABLE(LogDate datetime, ProcessInfo sysname, Info nvarchar(1024))
DECLARE @license TABLE(LogDate datetime, ProcessInfo sysname, Info nvarchar(1024))
DECLARE @port TABLE(LogDate datetime, ProcessInfo sysname, Info nvarchar(1024))
INSERT INTO @SVer EXEC master.dbo.xp_msver;
INSERT INTO @drivers EXEC master..xp_fixeddrives;
--.优化.若当前sqlserverlog过大则直接读取会非常慢,改为读取最小日志中的Server信息
--INSERT INTO @manufacturer EXEC master.sys.xp_readerrorlog 0, 1, N'Manufacturer';
DECLARE @MinID tinyint
DECLARE @tbLog TABLE(Archive int, LogDate datetime, LogFileSize bigint)
INSERT INTO @tbLog EXEC master.dbo.sp_enumerrorlogs
SELECT @MinID=Archive FROM @tbLog WHERE LogFileSize = (SELECT min(LogFileSize) FROM @tbLog)
DECLARE @errorlog2 TABLE(logdate datetime,info varchar(20),text nvarchar(max))
INSERT INTO @manufacturer EXEC master.sys.xp_readerrorlog @MinID,1,N'Manufacturer';
INSERT INTO @license EXEC master.sys.xp_readerrorlog @MinID,1,N'processors';
INSERT INTO @port EXEC master.sys.xp_readerrorlog @MinID,1,N'established';
--.Version
INSERT INTO @instance_info(config_name,run_value) SELECT 'Manufacturer',Info FROM @manufacturer
INSERT INTO @instance_info(config_name,run_value) SELECT 'InstanceName',@@SERVERNAME
INSERT INTO @instance_info(config_name,run_value) SELECT 'ServerName',CAST(SERVERPROPERTY('ServerName') AS nvarchar(128))
INSERT INTO @instance_info(config_name,run_value) SELECT 'ProductVersion',CAST(SERVERPROPERTY('ProductVersion') AS VARCHAR(40))
INSERT INTO @instance_info(config_name,run_value) SELECT 'Version',REPLACE(SUBSTRING(@@VERSION,1,CHARINDEX('-', (@@VERSION))),'-','')
INSERT INTO @instance_info(config_name,run_value) SELECT 'ProductLevel',CAST(SERVERPROPERTY('ProductLevel') AS VARCHAR(40))
INSERT INTO @instance_info(config_name,run_value) SELECT 'Edition',CAST(SERVERPROPERTY('Edition') AS VARCHAR(60))
INSERT INTO @instance_info(config_name,run_value) SELECT Name,Value FROM @SVer WHERE [Name] = 'Language'
--.CPU
DECLARE @ProcessorNameString nvarchar(512)
-- EXEC master.sys.xp_instance_regread N'HKEY_LOCAL_MACHINE', N'HARDWARE\DESCRIPTION\System\CentralProcessor\0', N'ProcessorNameString', @ProcessorNameString OUTPUT
-- INSERT INTO @instance_info(config_name,run_value) SELECT 'ProcessorNameString',@ProcessorNameString
INSERT INTO @instance_info(config_name,run_value) SELECT 'Physical_CPU_Count',cpu_count/hyperthread_ratio FROM sys.dm_os_sys_info WITH(NOLOCK)
INSERT INTO @instance_info(config_name,run_value) SELECT 'Hyperthread_Ratio',hyperthread_ratio FROM sys.dm_os_sys_info WITH(NOLOCK)
INSERT INTO @instance_info(config_name,run_value) SELECT 'Logical_CPU_Count',cpu_count FROM sys.dm_os_sys_info WITH(NOLOCK)
INSERT INTO @instance_info(config_name,run_value) SELECT 'CPU_Usage_Percent',CAST(100-SystemIdle AS numeric(18,2)) FROM (SELECT SystemIdle=record.value('(./Record/SchedulerMonitorEvent/SystemHealth/SystemIdle)[1]','int') FROM (SELECT TOP 1 CONVERT(xml,record) AS [record] FROM master.sys.dm_os_ring_buffers WHERE ring_buffer_type = N'RING_BUFFER_SCHEDULER_MONITOR' AND record LIKE N'%%' ORDER BY [timestamp] DESC) AS x ) AS y
--.Memory
INSERT INTO @instance_info(config_name,run_value) SELECT 'Physical_Memory_MB',total_physical_memory_kb/1024 FROM sys.dm_os_sys_memory WITH (NOLOCK)
INSERT INTO @instance_info(config_name,run_value) SELECT 'Available_Memory_MB',available_physical_memory_kb/1024 FROM sys.dm_os_sys_memory WITH (NOLOCK)
INSERT INTO @instance_info(config_name,run_value) SELECT 'Memory_Usage_Percent',CAST(1-CAST(available_physical_memory_kb AS numeric(18,2))/CAST(total_physical_memory_kb AS numeric(18,2)) AS numeric(18,2))*100 FROM sys.dm_os_sys_memory WITH (NOLOCK)
INSERT INTO @instance_info(config_name,run_value) SELECT 'System_Memory_State',system_memory_state_desc FROM sys.dm_os_sys_memory WITH (NOLOCK)
INSERT INTO @instance_info(config_name,run_value) SELECT Name,CONVERT(NVARCHAR(20),Value_in_use) FROM sys.configurations WITH(NOLOCK) WHERE Name = 'min server memory (MB)'
INSERT INTO @instance_info(config_name,run_value) SELECT Name,CONVERT(NVARCHAR(20),Value_in_use) FROM sys.configurations WITH(NOLOCK) WHERE Name = 'max server memory (MB)'
INSERT INTO @instance_info(config_name,run_value) SELECT Name,CONVERT(NVARCHAR(20),Value_in_use) FROM sys.configurations WITH(NOLOCK) WHERE Name = 'awe enabled'
INSERT INTO @instance_info(config_name,run_value) SELECT 'Available_GB: '+drive,MB_Avail/1024 FROM @drivers
DECLARE @ProductName nvarchar(512), @SmoLoginMode int, @SmoAuditLevel int, @param nvarchar(512)
DECLARE @SmoDefaultFile nvarchar(512), @SmoDefaultLog nvarchar(512), @BackupDirectory nvarchar(512)
-- EXEC master.dbo.xp_regread 'HKEY_LOCAL_MACHINE','SOFTWARE\Microsoft\Windows NT\CurrentVersion',N'ProductName', @ProductName OUTPUT
EXEC master.dbo.xp_instance_regread N'HKEY_LOCAL_MACHINE', N'Software\Microsoft\MSSQLServer\MSSQLServer', N'DefaultData', @SmoDefaultFile OUTPUT
EXEC master.dbo.xp_instance_regread N'HKEY_LOCAL_MACHINE', N'Software\Microsoft\MSSQLServer\MSSQLServer', N'DefaultLog', @SmoDefaultLog OUTPUT
EXEC master.dbo.xp_instance_regread N'HKEY_LOCAL_MACHINE', N'Software\Microsoft\MSSQLServer\MSSQLServer', N'BackupDirectory', @BackupDirectory OUTPUT
EXEC master.dbo.xp_instance_regread N'HKEY_LOCAL_MACHINE', N'Software\Microsoft\MSSQLServer\MSSQLServer', N'LoginMode', @SmoLoginMode OUTPUT
EXEC master.dbo.xp_instance_regread N'HKEY_LOCAL_MACHINE', N'Software\Microsoft\MSSQLServer\MSSQLServer', N'AuditLevel', @SmoAuditLevel OUTPUT
EXEC master.dbo.xp_instance_regread N'HKEY_LOCAL_MACHINE', N'SOFTWARE\Microsoft\MSSQLServer\Setup', N'SQLDataRoot', @param OUTPUT
INSERT INTO @instance_info(config_name,run_value) SELECT 'ProductName',@ProductName
INSERT INTO @instance_info(config_name,run_value) SELECT 'DefaultData',@SmoDefaultFile
INSERT INTO @instance_info(config_name,run_value) SELECT 'DefaultLog',@SmoDefaultLog
INSERT INTO @instance_info(config_name,run_value) SELECT 'DefaultBackup',@BackupDirectory
INSERT INTO @instance_info(config_name,run_value) SELECT 'DefaultLogin' ,CASE WHEN @SmoLoginMode='1' THEN 'Windows Authentication mode' ELSE 'SQL Server and Windows Authentication mode' END
INSERT INTO @instance_info(config_name,run_value) SELECT 'AuditLevel', CASE WHEN @SmoAuditLevel = 0 THEN 'None' WHEN @SmoAuditLevel = 1 THEN 'Successful Logins Only' WHEN @SmoAuditLevel = 2 THEN 'Failed Logins Only' WHEN @SmoAuditLevel = 3 THEN 'Both Failed and Successful Logins' END
INSERT INTO @instance_info(config_name,run_value) SELECT Name,CONVERT(NVARCHAR(20),Value_in_use) FROM sys.configurations WITH(NOLOCK) WHERE Name = 'backup compression default'
INSERT INTO @instance_info(config_name,run_value) SELECT Name,CONVERT(NVARCHAR(20),Value_in_use) FROM sys.configurations WITH(NOLOCK) WHERE Name = 'max degree of parallelism'
INSERT INTO @instance_info(config_name,run_value) SELECT Name,CONVERT(NVARCHAR(20),Value_in_use) FROM sys.configurations WITH(NOLOCK) WHERE Name = 'cost threshold for parallelism'
INSERT INTO @instance_info(config_name,run_value) SELECT 'DefaultErrorLog',@param+'\Log'
INSERT INTO @instance_info(config_name,run_value) SELECT 'Instance_Collation',CAST(SERVERPROPERTY ('Collation') AS VARCHAR(40))
INSERT INTO @instance_info(config_name,run_value) SELECT Name,CONVERT(VARCHAR(10),Value_in_use) FROM sys.configurations WITH(NOLOCK) WHERE Name = 'Database Mail XPs'
INSERT INTO @instance_info(config_name,run_value) SELECT Name,CONVERT(VARCHAR(10),Value_in_use) FROM sys.configurations WITH(NOLOCK) WHERE Name = 'default trace enabled'
-- INSERT INTO @instance_info(config_name,run_value) SELECT 'TracePath',path FROM sys.traces WITH(NOLOCK) WHERE status = 1
INSERT INTO @instance_info(config_name,run_value) SELECT 'local_net_address',CAST(ConnectionProperty('local_net_address') AS varchar(20))
INSERT INTO @instance_info(config_name,run_value) SELECT 'local_tcp_port',CAST(ConnectionProperty('local_tcp_port') AS varchar(10))
INSERT INTO @instance_info(config_name,run_value) SELECT TOP 1 'local_net_address',local_net_address FROM SYS.DM_EXEC_CONNECTIONS WITH(NOLOCK) WHERE local_net_address IS NOT NULL AND net_transport = 'TCP' AND local_net_address not in('127.0.0.1','::1') AND len(local_net_address)<16
INSERT INTO @instance_info(config_name,run_value) SELECT 'sqlserver_start_time',convert(nvarchar(20),sqlserver_start_time,120) FROM sys.dm_os_sys_info WITH(NOLOCK)
INSERT INTO @instance_info(config_name,run_value) SELECT 'session_count',COUNT(session_id) FROM sys.dm_exec_sessions WITH(NOLOCK)
INSERT INTO @instance_info(config_name,run_value) SELECT 'ListeningPort',Info FROM @port
INSERT INTO @instance_info(config_name,run_value) SELECT 'LicenseCAL',Info FROM @license
--.为了避免导出excel格式错乱, 将 run_value 中的逗号分隔符替换为.
SELECT sid,config_name,replace(run_value,',','.') as run_value FROM @instance_info
- 磁盘使用率
--.The following script is only supported in SQL Server 2008 R2 SP3(10.50.6000) and later versions.
SET NOCOUNT ON;
DECLARE @version SYSNAME
DECLARE @exec NVARCHAR(1024)
SET @version = CAST(SERVERPROPERTY('ProductVersion') AS VARCHAR(40))
SET @version = SUBSTRING(@version,1,CHARINDEX('.',@version)-1)
IF @version >= 11
BEGIN
SET @exec = 'SELECT DISTINCT UPPER(vs.volume_mount_point) AS disk_partition, vs.file_system_type, vs.logical_volume_name, CONVERT(DECIMAL(18,2),vs.total_bytes/1073741824.0) AS [total_size_gb]' + CHAR(10)
+ ',CONVERT(DECIMAL(18,2),vs.available_bytes/1073741824.0) AS [available_size_gb], CAST(1-CAST(vs.available_bytes AS FLOAT) / CAST(vs.total_bytes AS FLOAT) AS DECIMAL(18,2)) * 100 AS [space_used_pct]' + CHAR(10)
+ 'FROM sys.master_files AS mf WITH(NOLOCK)' + CHAR(10)
+ 'CROSS APPLY sys.dm_os_volume_stats(mf.database_id, mf.[file_id]) AS vs ORDER BY disk_partition ASC'
EXECUTE (@exec)
END
- IOPS及QPS:采样间隔10秒
--.采集连接数及QPS
SET NOCOUNT ON;
DECLARE
@RecTimePre DATETIME
,@RecTimeAft DATETIME
,@Interval INT
,@Total_IOPS_Count_Pre BIGINT
,@Total_QPS_Count_Pre BIGINT
,@Total_Throughout_Pre BIGINT
,@Total_DeadLock_Count_Pre BIGINT
,@Total_LockWait_Count_Pre BIGINT
,@Total_IOPS_Count_Aft BIGINT
,@Total_QPS_Count_Aft BIGINT
,@Total_Throughout_Aft BIGINT
,@Total_DeadLock_Count_Aft BIGINT
,@Total_LockWait_Count_Aft BIGINT
,@Total_Connections BIGINT
SET @RecTimePre=GETDATE()
SELECT @Total_IOPS_Count_Pre=SUM(num_of_reads + num_of_writes) FROM sys.dm_io_virtual_file_stats(DEFAULT, DEFAULT)
SELECT @Total_Throughout_Pre=SUM(num_of_bytes_read+num_of_bytes_written) FROM sys.dm_io_virtual_file_stats(DEFAULT, DEFAULT)
SELECT @Total_QPS_Count_Pre=cntr_value FROM sys.dm_os_performance_counters WHERE object_name = 'SQLServer:SQL Statistics' AND counter_name = 'Batch Requests/sec'
SELECT @Total_DeadLock_Count_Pre=cntr_value FROM sys.dm_os_performance_counters WHERE object_name = 'SQLServer:Locks' AND counter_name = 'Number of Deadlocks/sec' AND instance_name = '_Total'
SELECT @Total_LockWait_Count_Pre=cntr_value FROM sys.dm_os_performance_counters WHERE object_name = 'SQLServer:Locks' AND counter_name = 'Lock Waits/sec' AND instance_name = '_Total'
--.采集间隔.
WAITFOR DELAY '00:00:10'
SET @RecTimeAft=GETDATE()
SET @Interval=DATEDIFF(ss,@RecTimePre,@RecTimeAft)
SELECT @Total_IOPS_Count_Aft=SUM(num_of_reads + num_of_writes) FROM sys.dm_io_virtual_file_stats(DEFAULT, DEFAULT)
SELECT @Total_QPS_Count_Aft=cntr_value FROM sys.dm_os_performance_counters WHERE object_name = 'SQLServer:SQL Statistics' AND counter_name = 'Batch Requests/sec'
SELECT @Total_DeadLock_Count_Aft=cntr_value FROM sys.dm_os_performance_counters WHERE object_name = 'SQLServer:Locks' AND counter_name = 'Number of Deadlocks/sec' AND instance_name = '_Total'
SELECT @Total_LockWait_Count_Aft=cntr_value FROM sys.dm_os_performance_counters WHERE object_name = 'SQLServer:Locks' AND counter_name = 'Lock Waits/sec' AND instance_name = '_Total'
SELECT @Total_Throughout_Aft=SUM(num_of_bytes_read+num_of_bytes_written) FROM sys.dm_io_virtual_file_stats(DEFAULT, DEFAULT)
SELECT @Total_Connections=COUNT(1)
FROM Sys.dm_exec_requests AS r WITH(NOLOCK)
RIGHT OUTER JOIN Sys.dm_exec_sessions AS s WITH(NOLOCK) ON r.session_id = s.session_id
RIGHT OUTER JOIN Sys.dm_exec_connections AS c WITH(NOLOCK) ON s.session_id = c.session_id
WHERE s.session_id >50 AND s.session_id<>@@SPID
SELECT
CONVERT(varchar(16),GETDATE(),120)+':00' AS BeginTime
,@Interval AS Interval
,@Total_Connections AS Connections
,(@Total_IOPS_Count_Aft-@Total_IOPS_Count_Pre)/@Interval AS IOPS
,(@Total_QPS_Count_Aft-@Total_QPS_Count_Pre)/@Interval AS QPS
,(@Total_DeadLock_Count_Aft-@Total_DeadLock_Count_Pre)/@Interval AS DeadLock
,(@Total_LockWait_Count_Aft-@Total_LockWait_Count_Pre)/@Interval AS LockWait
2.扩展性评估
- 数据库使用情况
IF OBJECT_ID('tempdb..#dblog', 'U') <> 0
DROP TABLE #dblog
GO
CREATE TABLE #dblog(database_name sysname, log_size_mb numeric(18,2), log_space_used numeric(18,2), log_status int)
INSERT INTO #dblog EXECUTE ('dbcc sqlperf(logspace) with no_infomsgs')
GO
WITH cte_dbsize AS ( SELECT DB_NAME(database_id) AS dbname, CAST(SUM(size)/128.0 AS numeric(18,2)) AS database_size_mb FROM sys.master_files WITH(NOLOCK) WHERE state=0 GROUP BY database_id )
SELECT sd.name AS database_name,database_id,owner_sid,cte.database_size_mb,tmp.log_size_mb,tmp.log_space_used,tmp.log_status,create_date,compatibility_level,user_access_desc,is_auto_shrink_on,state_desc,recovery_model_desc,log_reuse_wait_desc
FROM master.sys.databases AS sd WITH(NOLOCK)
INNER JOIN cte_dbsize AS cte WITH(NOLOCK) ON sd.name = cte.dbname
INNER JOIN #dblog AS tmp WITH(NOLOCK) ON tmp.database_name = sd.name
ORDER BY sd.database_id
- 大库(以数据大小倒排序)TOP x
DECLARE
@database_id int,
@database_name sysname,
@sql_string nvarchar(2000)
DECLARE @file_size TABLE (
[database_name] [sysname] NULL,
[group_id] [smallint] NULL,
[group_name] sysname NULL,
[file_id] [smallint] NULL,
[file_size_mb] [decimal](12, 2) NULL,
[space_used_mb] [decimal](12, 2) NULL,
[free_space_mb] [decimal](12, 2) NULL,
[name] [sysname] NOT NULL,
[file_name] [nvarchar](260) NOT NULL )
SELECT TOP 1 @database_id = database_id ,@database_name = name
FROM sys.databases WITH(NOLOCK)
WHERE database_id > 4 AND state = 0
ORDER BY database_id
WHILE @database_name IS NOT NULL
BEGIN
SET @sql_string = 'USE ' + QUOTENAME(@database_name) + CHAR(10)
SET @sql_string = @sql_string + '
SELECT
DB_NAME()
,sysfilegroups.groupid
,sysfilegroups.groupname
,fileid
,convert(decimal(12,2),cast(round(sysfiles.size/128.000,2) as float)) as file_size
,convert(decimal(12,2),cast(round(fileproperty(sysfiles.name,''SpaceUsed'')/128.000,2) as float)) as space_used
,convert(decimal(12,2),cast(round((sysfiles.size-fileproperty(sysfiles.name,''SpaceUsed''))/128.000,2) as float)) as free_space
,sysfiles.name
,sysfiles.filename
FROM sys.sysfiles WITH(NOLOCK)
LEFT OUTER JOIN sys.sysfilegroups WITH(NOLOCK) ON sysfiles.groupid = sysfilegroups.groupid'
INSERT INTO @file_size EXEC sp_executesql @sql_string
--Grab next database
SET @database_name = NULL
SELECT TOP 1 @database_id = database_id, @database_name = name
FROM sys.databases
WHERE database_id > 4 AND state = 0 AND database_id > @database_id
ORDER BY database_id
END
--File Sizes
SELECT database_name, ISNULL(group_name,'LOG') group_name, file_id, name, file_size_mb, space_used_mb, free_space_mb, file_name
FROM @file_size
ORDER BY file_size_mb DESC
- 大表(以数据大小倒排序)TOP 200
IF OBJECT_ID('tempdb..#tb', 'U') <> 0
DROP TABLE #tb
GO
CREATE TABLE #tb(database_name varchar(60),schema_name varchar(60),table_name varchar(100),type_desc varchar(20),table_rows bigint,total_pages bigint,used_pages bigint)
EXECUTE master.sys.sp_MSforeachdb 'if ''?'' not in (''master'',''model'',''msdb'',''tempdb'') BEGIN use [?]; insert into #tb SELECT db_name(),s.name,object_name(t.object_id),u.type_desc,max(p.rows),sum(u.total_pages),sum(u.used_pages) FROM sys.allocation_units AS u JOIN sys.partitions AS p ON u.container_id = p.hobt_id JOIN sys.tables AS t ON p.object_id = t.object_id INNER JOIN sys.schemas s ON t.schema_id = s.schema_id WHERE p.index_id IN (0,1) AND u.type = 1 AND p.rows > 0 GROUP BY s.name,t.object_id,object_name(t.object_id),u.type_desc END'
SELECT TOP 200 database_name,schema_name,table_name,type_desc,table_rows,CONVERT(NUMERIC(18,2),total_pages * 8 / 1024.0) AS total_pages_mb,CONVERT(NUMERIC(18,2),used_pages * 8 / 1024.0) AS used_pages_mb FROM #tb ORDER BY total_pages DESC
- 自增id上限使用情况
IF OBJECT_ID('tempdb..#tb', 'U') <> 0
DROP TABLE #tb
GO
CREATE TABLE #tb(database_name varchar(60),schema_name varchar(60),table_name varchar(60),column_name varchar(60),column_type varchar(20));
EXECUTE master.sys.sp_MSforeachdb 'if ''?'' not in (''master'',''model'',''msdb'',''tempdb'') BEGIN use [?]; insert into #tb SELECT db_name(),s.name,t.name,c.name,p.name FROM sys.tables t INNER JOIN sys.schemas s ON t.schema_id = s.schema_id INNER JOIN sys.columns c ON t.object_id = c.object_id LEFT JOIN sys.types AS p ON c.system_type_id = p.user_type_id WHERE c.is_identity = 1 END'
GO
IF OBJECT_ID('tempdb..#seed', 'U') <> 0
DROP TABLE #seed
GO
CREATE TABLE #seed(id int identity(1,1),database_name varchar(60),schema_name varchar(60),table_name varchar(60),column_name varchar(60),column_type varchar(20),current_identity bigint)
INSERT INTO #seed(database_name,schema_name,table_name,column_name,column_type) SELECT database_name,schema_name,table_name,column_name,column_type FROM #tb ORDER BY database_name,table_name
DECLARE @i int, @j int, @database_name varchar(60), @schema_name varchar(60), @table_name varchar(60), @sql varchar(200), @identity_current bigint
SELECT @i=1,@j=MAX(id) FROM #seed
WHILE @i <= @j
BEGIN
SELECT @database_name = database_name, @schema_name = schema_name, @table_name = table_name FROM #seed WHERE id = @i
SET @sql = 'UPDATE #seed SET current_identity = IDENT_CURRENT('''+@database_name+'.'+@schema_name+'.'+@table_name+''') where id = '+CAST(@i AS VARCHAR(20))+''
EXECUTE (@sql)
SET @i = @i + 1
END
SELECT
database_name,schema_name,table_name,column_name,column_type,current_identity
,CASE column_type
WHEN 'tinyint' THEN 255
WHEN 'smallint' THEN 32767
WHEN 'int' THEN 2147483647
WHEN 'bigint' THEN 9223372036854775807
ELSE NULL
END AS max_identity
,CASE
WHEN current_identity IS NULL THEN 0.00
ELSE CAST(
(CAST(CONVERT(BIGINT, current_identity) AS FLOAT) /
CASE column_type
WHEN 'tinyint' THEN 255.0
WHEN 'smallint' THEN 32767.0
WHEN 'int' THEN 2147483647.0
WHEN 'bigint' THEN 9223372036854775807.0
ELSE 1.0
END) * 100 AS DECIMAL(5,2))
END AS identity_used_percent
FROM #seed
WHERE (column_type = 'tinyint' AND current_identity > 240) OR (column_type = 'smallint' AND current_identity > 30000) OR (column_type = 'int' AND current_identity > 1900000000) OR (column_type = 'bigint' AND current_identity > 8000000000000000000)
ORDER BY identity_used_percent DESC
3.稳定性评估
- 单表索引过多(超过6个):过多的索引降低了 insert 及 update 的效率
IF OBJECT_ID('tempdb..#tb', 'U') <> 0
DROP TABLE #tb
GO
CREATE TABLE #tb(database_name varchar(60),schema_name varchar(60),table_name varchar(60),index_id int,index_name varchar(100));
EXECUTE master.sys.sp_MSforeachdb 'if ''?'' not in (''master'',''model'',''msdb'',''tempdb'') BEGIN use [?]; insert into #tb select db_name(), sc.name, t.name, si.index_id, si.name from sys.indexes as si join sys.tables as t on si.object_id = t.object_id join sys.schemas as sc on t.schema_id = sc.schema_id where t.type = ''U'' and si.index_id > 0 END'
SELECT TOP 200 database_name,schema_name,table_name,count(index_name) as index_count FROM #tb GROUP BY database_name,schema_name,table_name HAVING count(index_name)>=6 ORDER BY count(index_name) DESC
- 索引缺失(性能提升90%):全表扫描将锁表,数据增多或高并发将造成灾难
SELECT TOP 200
db_name(mid.database_id) AS database_name
,mid.[object_id] AS objectid
,mid.statement
,CONVERT (decimal (28,2), migs.avg_total_user_cost * migs.avg_user_impact * (migs.user_seeks + migs.user_scans)) AS improvement_measure
,migs.avg_user_impact
,CONVERT (decimal (28,2),migs.avg_total_user_cost) AS avg_total_user_cost
--,mid.index_handle
,migs.user_seeks
,migs.user_scans
--,migs.last_user_seek
--,migs.last_user_scan
,'CREATE INDEX missing_index_' + CONVERT (varchar, mig.index_group_handle) + '_' + CONVERT (varchar, mid.index_handle)
+ ' ON ' + mid.statement
+ ' (' + ISNULL (mid.equality_columns,'')
+ CASE WHEN mid.equality_columns IS NOT NULL AND mid.inequality_columns IS NOT NULL THEN ',' ELSE '' END + ISNULL (mid.inequality_columns, '')
+ ')'
+ ISNULL (' INCLUDE (' + mid.included_columns + ')', '') AS create_index_statement
FROM sys.dm_db_missing_index_groups AS mig WITH(NOLOCK)
INNER JOIN sys.dm_db_missing_index_group_stats AS migs WITH(NOLOCK) ON migs.group_handle = mig.index_group_handle
INNER JOIN sys.dm_db_missing_index_details AS mid WITH(NOLOCK) ON mig.index_handle = mid.index_handle
WHERE CONVERT (decimal (28,1), migs.avg_total_user_cost * migs.avg_user_impact * (migs.user_seeks + migs.user_scans)) > 10
ORDER BY migs.avg_total_user_cost * migs.avg_user_impact * (migs.user_seeks + migs.user_scans) DESC
- 索引碎片(超过70%):过多碎片的索引会降低查询性能,导致应用程序响应慢
--.查看所有db所有table的索引碎片(行数超过1W+碎片超过70%,首次执行耗时约6m14s,再次执行耗时6s)
--.2026.06.09.在一个500G的实例上运行37m未结束...
SET NOCOUNT ON;
IF OBJECT_ID('tempdb..#tb', 'U') <> 0
DROP TABLE #tb
GO
--.提前过滤掉行数小于10000的表
CREATE TABLE #tb(database_name varchar(60),database_id int,schema_name varchar(60),table_id bigint,table_name varchar(60),index_id int,index_name varchar(100),rows bigint);
EXECUTE master.sys.sp_MSforeachdb 'if ''?'' not in (''master'',''model'',''msdb'',''tempdb'') BEGIN use [?]; insert into #tb select distinct db_name(),db_id(),sc.name,t.object_id,t.name,si.index_id,si.name,ss.rows from sys.indexes as si join sys.tables as t on si.object_id = t.object_id join sys.schemas as sc on t.schema_id = sc.schema_id join sys.sysindexes as ss on ss.id = t.object_id where t.type = ''U'' and si.index_id > 0 and ss.rows > 10000 END'
--.防止数据太多,仅返回top 200
SELECT TOP 200
tmp.database_name
,tmp.schema_name
,tmp.table_name
,tmp.rows AS table_rows
,tmp.index_name
,CAST(sdb.avg_fragmentation_in_percent AS DECIMAL(18,2)) AS index_frag_percent
,'ALTER INDEX '+tmp.index_name+' ON '+tmp.database_name+'.'+tmp.schema_name+'.'+tmp.table_name+' REBUILD WITH(ONLINE=ON)' AS index_rebuild_sql
-- 使用 CROSS APPLY 替代全表扫描, 只针对符合条件的索引调用 sys.dm_db_index_physical_stats 而不是扫描所有索引
-- FROM sys.dm_db_index_physical_stats (NULL, NULL, NULL , NULL, 'LIMITED') AS sdb INNER JOIN #tb as tmp ON sdb.database_id = tmp.database_id AND sdb.object_id = tmp.table_id AND sdb.index_id = tmp.index_id
FROM #tb AS tmp CROSS APPLY sys.dm_db_index_physical_stats(tmp.database_id, tmp.table_id, tmp.index_id, NULL, 'LIMITED') AS sdb
WHERE sdb.index_id > 0 --排除堆表
AND sdb.partition_number = 1 --排除分区表partition_number>1的索引
AND sdb.avg_fragmentation_in_percent > 70 -- 碎片超过70%
ORDER BY sdb.avg_fragmentation_in_percent DESC
- 无效索引(使用次数为0):索引已创建但从未被使用(查询次数/访问次数为0)
IF OBJECT_ID('tempdb..#tb', 'U') <> 0
DROP TABLE #tb
GO
CREATE TABLE #tb(database_name varchar(60),database_id int,schema_name varchar(60),schema_id int,table_id bigint,table_name varchar(60),index_id int,index_name varchar(100),rows bigint,is_disabled int,is_primary_key int,is_unique_constraint int);
EXECUTE master.sys.sp_MSforeachdb 'if ''?'' not in (''master'',''model'',''msdb'',''tempdb'') BEGIN use [?]; insert into #tb select distinct db_name(),db_id(),sc.name,sc.schema_id,t.object_id,t.name,si.index_id,si.name,ss.rows,si.is_disabled,si.is_primary_key,si.is_unique_constraint from sys.indexes as si join sys.tables as t on si.object_id = t.object_id join sys.schemas as sc on t.schema_id = sc.schema_id join sys.sysindexes as ss on ss.id = t.object_id and ss.indid = 1 where t.type = ''U'' and si.index_id > 0 AND si.type_desc <> ''HEAP'' END'
SELECT TOP 200
tmp.database_name
,tmp.schema_name
,tmp.table_name
,tmp.index_name
,tmp.is_primary_key
,tmp.is_unique_constraint
,tmp.is_disabled
,ISNULL(ius.user_updates,0) AS user_updates --.index write
,ISNULL(ius.user_seeks,0) AS user_seeks --.index read
,ISNULL(ius.user_scans,0) AS user_scans --.index read
,ISNULL(ius.user_lookups,0) AS user_lookups --.index read
,CASE
WHEN tmp.is_primary_key = 1 THEN 'ALTER TABLE [' + tmp.database_name + ']' + '.' + '[' + tmp.schema_name + ']' + '.' + '[' + tmp.table_name + '] DROP CONSTRAINT [' + tmp.index_name + ']'
WHEN tmp.is_unique_constraint = 1 THEN 'ALTER TABLE [' + tmp.database_name + ']' + '.' + '[' + tmp.schema_name + ']' + '.' + '[' + tmp.table_name + '] DROP CONSTRAINT [' + tmp.index_name + ']'
ELSE 'DROP INDEX [' + tmp.index_name + '] ON [' + tmp.database_name + ']' + '.' + '[' + tmp.schema_name + ']' + '.' + '[' + tmp.table_name + ']'
END AS drop_index_statement
FROM #tb AS tmp
LEFT JOIN sys.dm_db_index_usage_stats AS ius WITH(NOLOCK) ON tmp.table_id = ius.object_id AND tmp.index_id = ius.index_id
WHERE ( ius.index_id IS NULL OR (ius.user_updates > 0 AND ius.user_seeks = 0 AND ius.user_scans = 0 AND ius.user_lookups = 0) )
ORDER BY tmp.database_name, tmp.table_name, tmp.index_name
- 慢查询(以 avg_logical_reads 倒排序)top (200)
IF OBJECT_ID('tempdb..#slowlog', 'U') <> 0
DROP TABLE #slowlog
GO
SELECT
qs.execution_count
,qs.total_logical_reads
,(qs.total_logical_reads / NULLIF(qs.execution_count, 0)) AS avg_logical_reads
,qs.total_logical_writes
,(qs.total_logical_writes / NULLIF(qs.execution_count, 0)) AS avg_logical_writes
,qs.total_physical_reads
,(qs.total_physical_reads / NULLIF(qs.execution_count, 0)) AS avg_physical_reads
,(qs.total_logical_reads + qs.total_logical_writes) AS total_logical_io
,(qs.total_logical_reads + qs.total_logical_writes) / NULLIF(qs.execution_count, 0) AS avg_logical_io
,(qs.total_logical_reads + qs.total_physical_reads + qs.total_logical_writes) / NULLIF(qs.execution_count, 0) AS average_io
,qs.total_worker_time/1000 AS total_worker_time_ms
,(qs.total_worker_time/1000) / NULLIF(qs.execution_count, 0) AS avg_worker_time_ms
,qs.total_elapsed_time/1000 AS total_elapsed_time_ms
,(qs.total_elapsed_time/1000) / NULLIF(qs.execution_count, 0) AS avg_elapsed_time_ms
,qs.last_execution_time
,qs.plan_generation_num
,db_name(st.dbid) AS database_name
,st.objectid
,SUBSTRING(st.text,(qs.statement_start_offset/2)+1,((CASE qs.statement_end_offset WHEN -1 THEN DATALENGTH(st.text) ELSE qs.statement_end_offset END - qs.statement_start_offset)/2)+1) AS query_text --.未将换行符替换为空格之前的 query_text
INTO #slowlog
FROM (
SELECT TOP 200
qs.sql_handle,
qs.plan_handle,
qs.execution_count,
qs.total_logical_reads,
qs.total_logical_writes,
qs.total_physical_reads,
qs.total_worker_time,
qs.total_elapsed_time,
qs.last_execution_time,
qs.plan_generation_num,
qs.statement_start_offset,
qs.statement_end_offset
FROM sys.dm_exec_query_stats AS qs WITH(NOLOCK)
WHERE qs.total_worker_time > 0 AND qs.execution_count > 0
ORDER BY qs.total_logical_reads DESC
) AS qs
CROSS APPLY sys.dm_exec_sql_text(qs.sql_handle) AS st
OUTER APPLY sys.dm_exec_query_plan(qs.plan_handle) AS qp
ORDER BY total_logical_reads DESC OPTION (RECOMPILE)
--.为了避免导出excel格式错乱, 将 query_text 中的换行符及制表符替换为空格, 将分隔符,改为.
UPDATE #slowlog SET query_text = REPLACE(REPLACE(REPLACE(query_text, CHAR(9), ' '), CHAR(10), ' '), CHAR(13), ' ')
UPDATE #slowlog SET query_text = REPLACE(query_text, ',', '.')
SELECT * FROM #slowlog
- 空表(行数为0):这些表的行数为0,疑似空表或临时表,请确认是否在用
IF OBJECT_ID('tempdb..#tb', 'U') <> 0
DROP TABLE #tb
GO
CREATE TABLE #tb(database_name varchar(60),schema_name varchar(60),table_name varchar(100),type_desc varchar(20),table_rows bigint,total_pages bigint,used_pages bigint)
EXECUTE master.sys.sp_MSforeachdb 'if ''?'' not in (''master'',''model'',''msdb'',''tempdb'') BEGIN use [?]; insert into #tb SELECT db_name(),s.name,object_name(t.object_id),u.type_desc,max(p.rows),sum(u.total_pages),sum(u.used_pages) FROM sys.allocation_units AS u JOIN sys.partitions AS p ON u.container_id = p.hobt_id JOIN sys.tables AS t ON p.object_id = t.object_id INNER JOIN sys.schemas s ON t.schema_id = s.schema_id WHERE p.index_id IN (0,1) AND u.type = 1 AND p.rows = 0 GROUP BY s.name,t.object_id,object_name(t.object_id),u.type_desc END'
SELECT TOP 200 database_name,schema_name,table_name,type_desc,table_rows,CONVERT(NUMERIC(18,2),total_pages * 8 / 1024.0) AS total_pages_mb,CONVERT(NUMERIC(18,2),used_pages * 8 / 1024.0) AS used_pages_mb FROM #tb ORDER BY database_name,schema_name,table_name
4.安全性评估
- 数据库备份记录:合理的备份方案可保证在灾难发生时将数据损失降到最低
DECLARE @tb TABLE(database_name nvarchar(40) COLLATE Chinese_PRC_CI_AS,last_backup_date varchar(20) COLLATE Chinese_PRC_CI_AS,backup_type varchar(30) COLLATE Chinese_PRC_CI_AS);
INSERT INTO @tb SELECT database_name, MAX(ISNULL(CONVERT(varchar,backup_finish_date,120),'')) AS last_backup_date
, CASE type
WHEN 'D' THEN 'last_full_backup_date'
WHEN 'L' THEN 'last_log_backup_date'
WHEN 'I' THEN 'last_diff_backup_date'
WHEN 'F' THEN 'last_file_backup_date'
ELSE ''
END AS backup_type
FROM msdb.dbo.backupset WITH(NOLOCK) GROUP BY database_name, type;
WITH cte_dbsize AS ( SELECT DB_NAME(database_id) AS dbname, SUM(size)/128 AS database_size_mb FROM sys.master_files WITH(NOLOCK) WHERE state=0 GROUP BY database_id )
SELECT sd.name as database_name,sd.recovery_model_desc,cte.database_size_mb,b.last_full_backup_date,b.last_log_backup_date,b.last_diff_backup_date,b.last_file_backup_date
FROM sys.databases AS sd INNER JOIN cte_dbsize AS cte on sd.name = cte.dbname
LEFT JOIN @tb PIVOT(MAX(last_backup_date) FOR backup_type IN (last_full_backup_date,last_diff_backup_date,last_log_backup_date,last_file_backup_date)) AS b ON sd.name = b.database_name
WHERE sd.name <> 'tempdb' order by sd.name
- 高权限账号:有sa权限的账号
SELECT sa.name,sa.status,sa.sysadmin,sb.is_disabled,sb.default_database_name,sb.is_policy_checked,sb.is_expiration_checked
FROM sys.syslogins sa INNER JOIN sys.sql_logins sb ON sa.name = sb.name
WHERE sa.sysadmin = 1 AND sb.is_disabled = 0
- 查看最近30日已启用作业的最近7条执行记录
WITH cte_jobhistory AS (
SELECT
a.name as job_name
,a.enabled as is_enabled
,b.run_date
,STUFF(STUFF(RIGHT(REPLICATE('0', 6) + CAST(b.run_time as varchar(6)), 6), 3, 0, ':'), 6, 0, ':') as run_time
,STUFF(STUFF(STUFF(RIGHT(REPLICATE('0', 8) + CAST(b.run_duration as varchar(8)), 8), 3, 0, ':'), 6, 0, ':'), 9, 0, ':') as run_duration
,b.step_name
,b.message
,case b.run_status when 0 then 'Failed' when 1 then 'Succeeded' when 2 then 'Retry' when 3 then 'Canceled' when 4 then 'Running' end as run_status
,ROW_NUMBER() OVER (PARTITION BY b.job_id ORDER BY msdb.dbo.agent_datetime(b.run_date, b.run_time) DESC) AS execution_rank
FROM msdb.dbo.sysjobs a
LEFT JOIN msdb.dbo.sysjobhistory b ON a.job_id = b.job_id
WHERE b.run_date > convert(char(8),getdate()-30,112) AND b.step_id = 0 AND a.enabled = 1
)
SELECT * FROM cte_jobhistory WHERE execution_rank <= 7 ORDER BY job_name ASC, run_date DESC, run_time DESC
- 查看 alwayson 节点信息
--.The following script is only supported in SQL Server 2012 and later versions.
SET NOCOUNT ON;
DECLARE @version SYSNAME
DECLARE @exec NVARCHAR(1024)
SET @version = CAST(SERVERPROPERTY('ProductVersion') AS VARCHAR(40))
SET @version = SUBSTRING(@version,1,CHARINDEX('.',@version)-1)
IF @version >= 11
BEGIN
SELECT
ag.name AS alwayson_name,
ar.replica_server_name,
ar.endpoint_url,
ar.availability_mode_desc,
ar.failover_mode_desc,
ar.primary_role_allow_connections_desc,
ar.secondary_role_allow_connections_desc
FROM sys.availability_groups ag
JOIN sys.availability_replicas ar ON ag.group_id = ar.group_id
END
- 查看 alwayson 延迟信息
--.The following script is only supported in SQL Server 2012 and later versions.
SET NOCOUNT ON;
DECLARE @version SYSNAME
DECLARE @exec NVARCHAR(1024)
SET @version = CAST(SERVERPROPERTY('ProductVersion') AS VARCHAR(40))
SET @version = SUBSTRING(@version,1,CHARINDEX('.',@version)-1)
IF @version >= 11
BEGIN
SELECT
ag.name AS ag_name,
ar.replica_server_name,
adc.database_name,
drs.is_local,
drs.synchronization_state_desc,
drs.synchronization_health_desc,
drs.last_commit_time,
CASE WHEN drs.last_commit_time IS NOT NULL AND drs.last_commit_lsn IS NOT NULL THEN DATEDIFF(ss, drs.last_commit_time, GETDATE()) ELSE NULL END AS estimated_delay_seconds,
ISNULL(drs.redo_queue_size,0) AS redo_queue_size, --[Redo等待队列(KB)]
ISNULL(drs.redo_rate,0) AS redo_rate, --[Redo速率(KB/S)]
CASE WHEN drs.redo_rate > 0 THEN CAST(drs.redo_queue_size AS FLOAT) / drs.redo_rate ELSE 0 END AS redo_delay_time_seconds, --[Redo延迟(秒)]
ISNULL(drs.log_send_queue_size,0) AS log_send_queue_size, --[Log传送等待队列(KB)]
ISNULL(drs.log_send_rate,0) AS log_send_rate, --[Log传送速率(KB/S)]
CASE WHEN drs.log_send_rate > 0 THEN CAST(drs.log_send_queue_size AS FLOAT) / drs.log_send_rate ELSE 0 END AS log_send_delay_time_seconds --[Log传送延迟(秒)]
FROM sys.dm_hadr_database_replica_states drs
JOIN sys.availability_replicas ar ON drs.replica_id = ar.replica_id
JOIN sys.availability_groups ag ON ar.group_id = ag.group_id
JOIN sys.availability_databases_cluster adc ON drs.group_database_id = adc.group_database_id
ORDER BY ar.replica_server_name, adc.database_name
END