SQL Server sp_helpdb 改寫

在SQL Server 數據庫管理中,我們常使用sp_helpdb 來查看數據庫的基本信息,如我們要查看test數據庫的信息,可以執行下面腳本:

EXEC sp_helpdb test

查詢結果如下:

但結果中無論是db_size 還是文件的size,都是分配的空間,我們不能看出數據庫使用的空間有多少,未使用空間有多少。這個數據庫的使用空間對前文中數據庫收縮有很大的用處。在最開始我是通過SSMS對象資源管理器,右擊目標數據庫,在任務→收縮→文件中查看數據庫或者數據庫文件的空間使用情況的,如下圖:

這樣操作對於高逼格的管理員來說,顯得就比較low了。當然最主要的是重複性操作性低。爲此查看系統視圖 sysfiles、database_files,系統過程 sp_helpfile 均只記錄數據庫的分配空間大小。使用前文中提到的 dbcc showfilestats,發現其只有數據文件的空間分配使用情況。最終找到了fileProperty內置函數,使用其 usedSpace 參數,結合系統視圖、獲取到了所有數據庫文件空間分配使用情況。爲滿足高效管理的需要,我將sp_helpdb 改寫爲 sp_DBA_helpdb,具體腳本如下:

USE [master]
GO
/*
      數據庫基本信息查看
      完善系統過程sp_helpdb
      --exec sp_DBA_helpdb 'master'
      create by Jack
*/
CREATE PROCEDURE [dbo].[sp_DBA_helpdb] @dbname SYSNAME = NULL   -- 數據庫名
AS
BEGIN
DECLARE @exec_stmt NVARCHAR(625) 
DECLARE @showdev BIT 
DECLARE @name SYSNAME 
DECLARE @cmd NVARCHAR(285) -- (26 + 258) + 1 extra 
DECLARE @dbdesc VARCHAR(600) /* 數據庫的屬性信息*/ 
DECLARE @propdesc VARCHAR(40) 
 
SET nocount ON 
CREATE TABLE #spdbdesc
    (
      dbName SYSNAME
      ,owner SYSNAME NULL
      ,created NVARCHAR(11)
      ,dbid SMALLINT
      ,dbdesc NVARCHAR(600) NULL
      ,dbTotalSize NVARCHAR(13) NULL  --數據庫分配空間(總空間)
      ,dbUsedSize NVARCHAR(13) NULL       --數據庫已經使用的空間
      ,dbFreeSizeRatio NVARCHAR(15) NULL  --數據庫可用空間比例
      ,cmptLevel TINYINT  --兼容級別
    ) 
/* 
**  如果指定數據庫名稱爲null,返回實例上所有數據庫信息,
**  否則返回對應數據庫信息及數據庫的文件信息
*/ 
IF @dbname IS NULL
    SELECT  @showdev = 0 
ELSE
    SELECT  @showdev = 1 
 
/* 
**  檢驗數據庫是否存在
*/ 
IF NOT EXISTS ( SELECT  *
                FROM    master.dbo.sysdatabases
                WHERE   ( @dbname IS NULL
                          OR name = @dbname
                        ) )
    BEGIN 
        RAISERROR(15010,-1,-1,@dbname) 
        RETURN (1) 
    END 
 
 
/* 
**  從系統視圖sysdatabases 初始化#spdbdesc 表
*/ 
INSERT  INTO #spdbdesc ( dbname, owner, created, dbid, cmptlevel )
        SELECT  name, ISNULL(SUSER_SNAME(sid), '~~UNKNOWN~~')
                  , CONVERT(NVARCHAR(11), crdate), dbid, cmptlevel
        FROM    master.dbo.sysdatabases
        WHERE   ( @dbname IS NULL
                  OR name = @dbname
                )  --如果指定數據庫名稱爲null,則返回所有實例上所有數據庫信息
 
/*
      **fileProperty 只能獲取當前會話的文件屬性,否則返回NULL
      **下面將循環每個庫的每個文件,獲取已經使用的文件大小
*/
IF OBJECT_ID('tempdb..#fileSize') IS NOT NULL
    DROP TABLE #fileSize
CREATE TABLE #fileSize
    (
      dbName VARCHAR(256) ,
      fName VARCHAR(256) ,
      totalSize BIGINT ,
      usedSize BIGINT
    )
 
DECLARE ms_crs_c1 CURSOR global
FOR
SELECT  DB_NAME(dbid)
FROM    #spdbdesc 
OPEN ms_crs_c1 
FETCH ms_crs_c1 INTO @name 
WHILE @@fetch_status >= 0
    BEGIN 
        IF ( HAS_DBACCESS(@name) <> 1 )  --判斷是否有權限訪問數據庫,不等於無權限訪問
            BEGIN 
                DELETE  #spdbdesc
                WHERE CURRENT OF ms_crs_c1 
                RAISERROR(15622,-1,-1, @name) 
            END 
        ELSE
            BEGIN 
                IF OBJECT_ID('tempdb..#fileName') IS NOT NULL
                    DROP TABLE #fileName
                CREATE TABLE #fileName
                    (
                      name VARCHAR(256) ,
                      TotalSize BIGINT
                    )
                SET @exec_stmt = 'select name,size from ' + QUOTENAME(@name) + '.sys.sysfiles'
                INSERT  INTO #fileName ( name, TotalSize )
                        EXEC ( @exec_stmt
                            )
                DECLARE @fname VARCHAR(256) ,
                    @TotalSize BIGINT
                DECLARE fName CURSOR
                FOR
                SELECT  name, TotalSize
                FROM    #fileName
                OPEN fName
                FETCH NEXT FROM fName INTO @fName, @TotalSize
                WHILE @@FETCH_STATUS >= 0
                    BEGIN
                        SET @exec_stmt = 'use ' + QUOTENAME(@name) + ' select fileProperty(''' + @fname
                            + ''',''SpaceUsed'')'
                        INSERT  INTO #fileSize ( usedSize )
                                EXEC ( @exec_stmt)
                        UPDATE  #fileSize
                        SET     dbName = @name, fName = @fname, totalSize = @TotalSize
                        WHERE   dbName IS NULL
                        FETCH NEXT FROM fName INTO @fName, @TotalSize
                    END
                CLOSE fName
                DEALLOCATE fName
       
            END 
        FETCH ms_crs_c1 INTO @name 
    END 
DEALLOCATE ms_crs_c1;
WITH    cte
          AS (
               SELECT   dbName, SUM(totalSize) totalSize, SUM(usedSize) usedSize
               FROM     #fileSize
               GROUP BY dbName
             )
UPDATE  #spdbdesc
SET     dbTotalSize = cte.totalSize, dbUsedSize = cte.usedSize
FROM    #spdbdesc s
LEFT JOIN cte ON cte.dbName = s.dbName
UPDATE  #spdbdesc
SET     dbFreeSizeRatio = STR(( CONVERT(dec(17, 2), dbTotalSize) - CONVERT(dec(17, 2), dbUsedSize) ) * 100.0
                              / CONVERT(dec(17, 2), dbTotalSize), 5, 2) + '%'
UPDATE  #spdbdesc
SET     dbTotalSize = STR(CONVERT(DECIMAL(17,2), dbTotalSize)/128.0,10,2) + ' MB'
      , dbUsedSize = STR(CONVERT(DECIMAL(17,2), dbUsedSize)/128.0,10,2) + ' MB'
/* 
**  Now for each dbid in #spdbdesc, build the database status 
**  description. 
*/ 
DECLARE @curdbid SMALLINT /* the one we're currently working on */ 
/* 
**  Set @curdbid to the first dbid. 
*/ 
SELECT  @curdbid = MIN(dbid)
FROM    #spdbdesc 
 
 
WHILE @curdbid IS NOT NULL
    BEGIN 
        SET @name = DB_NAME(@curdbid) 
 
 -- These properties always available 
        SELECT  @dbdesc = 'Status=' + CONVERT(SYSNAME, DATABASEPROPERTYEX(@name, 'Status')) 
        SELECT  @dbdesc = @dbdesc + ', Updateability=' + CONVERT(SYSNAME, DATABASEPROPERTYEX(@name, 'Updateability')) 
        SELECT  @dbdesc = @dbdesc + ', UserAccess=' + CONVERT(SYSNAME, DATABASEPROPERTYEX(@name, 'UserAccess')) 
        SELECT  @dbdesc = @dbdesc + ', Recovery=' + CONVERT(SYSNAME, DATABASEPROPERTYEX(@name, 'Recovery')) 
        SELECT  @dbdesc = @dbdesc + ', Version=' + CONVERT(SYSNAME, DATABASEPROPERTYEX(@name, 'Version')) 
 
 -- These props only available if db not shutdown 
        IF DATABASEPROPERTY(@name, 'IsShutdown') = 0
            BEGIN 
                SELECT  @dbdesc = @dbdesc + ', Collation=' + CONVERT(SYSNAME, DATABASEPROPERTYEX(@name, 'Collation')) 
                SELECT  @dbdesc = @dbdesc + ', SQLSortOrder=' + CONVERT(SYSNAME, DATABASEPROPERTYEX(@name,
                                                                                                    'SQLSortOrder')) 
            END 
 
 -- These are the boolean properties 
        IF DATABASEPROPERTYEX(@name, 'IsAutoClose') = 1
            SELECT  @dbdesc = @dbdesc + ', ' + 'IsAutoClose' 
        IF DATABASEPROPERTYEX(@name, 'IsAutoShrink') = 1
            SELECT  @dbdesc = @dbdesc + ', ' + 'IsAutoShrink' 
        IF DATABASEPROPERTYEX(@name, 'IsInStandby') = 1
            SELECT  @dbdesc = @dbdesc + ', ' + 'IsInStandby' 
        IF DATABASEPROPERTYEX(@name, 'IsTornPageDetectionEnabled') = 1
            SELECT  @dbdesc = @dbdesc + ', ' + 'IsTornPageDetectionEnabled' 
        IF DATABASEPROPERTYEX(@name, 'IsAnsiNullDefault') = 1
            SELECT  @dbdesc = @dbdesc + ', ' + 'IsAnsiNullDefault' 
        IF DATABASEPROPERTYEX(@name, 'IsAnsiNullsEnabled') = 1
            SELECT  @dbdesc = @dbdesc + ', ' + 'IsAnsiNullsEnabled' 
        IF DATABASEPROPERTYEX(@name, 'IsAnsiPaddingEnabled') = 1
            SELECT  @dbdesc = @dbdesc + ', ' + 'IsAnsiPaddingEnabled' 
        IF DATABASEPROPERTYEX(@name, 'IsAnsiWarningsEnabled') = 1
            SELECT  @dbdesc = @dbdesc + ', ' + 'IsAnsiWarningsEnabled' 
        IF DATABASEPROPERTYEX(@name, 'IsArithmeticAbortEnabled') = 1
            SELECT  @dbdesc = @dbdesc + ', ' + 'IsArithmeticAbortEnabled' 
        IF DATABASEPROPERTYEX(@name, 'IsAutoCreateStatistics') = 1
            SELECT  @dbdesc = @dbdesc + ', ' + 'IsAutoCreateStatistics' 
        IF DATABASEPROPERTYEX(@name, 'IsAutoUpdateStatistics') = 1
            SELECT  @dbdesc = @dbdesc + ', ' + 'IsAutoUpdateStatistics' 
        IF DATABASEPROPERTYEX(@name, 'IsCloseCursorsOnCommitEnabled') = 1
            SELECT  @dbdesc = @dbdesc + ', ' + 'IsCloseCursorsOnCommitEnabled' 
        IF DATABASEPROPERTYEX(@name, 'IsFullTextEnabled') = 1
            SELECT  @dbdesc = @dbdesc + ', ' + 'IsFullTextEnabled' 
        IF DATABASEPROPERTYEX(@name, 'IsLocalCursorsDefault') = 1
            SELECT  @dbdesc = @dbdesc + ', ' + 'IsLocalCursorsDefault' 
        IF DATABASEPROPERTYEX(@name, 'IsNullConcat') = 1
            SELECT  @dbdesc = @dbdesc + ', ' + 'IsNullConcat' 
        IF DATABASEPROPERTYEX(@name, 'IsNumericRoundAbortEnabled') = 1
            SELECT  @dbdesc = @dbdesc + ', ' + 'IsNumericRoundAbortEnabled' 
        IF DATABASEPROPERTYEX(@name, 'IsQuotedIdentifiersEnabled') = 1
            SELECT  @dbdesc = @dbdesc + ', ' + 'IsQuotedIdentifiersEnabled' 
        IF DATABASEPROPERTYEX(@name, 'IsRecursiveTriggersEnabled') = 1
            SELECT  @dbdesc = @dbdesc + ', ' + 'IsRecursiveTriggersEnabled' 
        IF DATABASEPROPERTYEX(@name, 'IsMergePublished') = 1
            SELECT  @dbdesc = @dbdesc + ', ' + 'IsMergePublished' 
        IF DATABASEPROPERTYEX(@name, 'IsPublished') = 1
            SELECT  @dbdesc = @dbdesc + ', ' + 'IsPublished' 
        IF DATABASEPROPERTYEX(@name, 'IsSubscribed') = 1
            SELECT  @dbdesc = @dbdesc + ', ' + 'IsSubscribed' 
        IF DATABASEPROPERTYEX(@name, 'IsSyncWithBackup') = 1
            SELECT  @dbdesc = @dbdesc + ', ' + 'IsSyncWithBackup' 
 
        UPDATE  #spdbdesc
        SET     dbdesc = @dbdesc
        WHERE   dbid = @curdbid 
 
 /* 
 **  Now get the next, if any dbid. 
 */ 
        SELECT  @curdbid = MIN(dbid)
        FROM    #spdbdesc
        WHERE   dbid > @curdbid 
    END 
 
/* 
**  Now #spdbdesc is complete so we can print out the db info 
*/ 
SELECT  name = dbname, dbTotalsize, dbUsedSize, dbFreeSizeRatio, owner = owner, dbid = dbid, created = created,
        status = dbdesc, compatibility_level = cmptlevel
FROM    #spdbdesc
ORDER BY dbname 
 
/* 
**  If we are looking at one database, show its file allocation. 
*/ 
IF @showdev = 1
    AND HAS_DBACCESS(@dbname) = 1
    BEGIN 
        PRINT N' ' 
        IF OBJECT_ID('tempdb..#fileInfo') IS NOT NULL
            DROP TABLE #fileInfo
        CREATE TABLE #fileInfo
            (
              Name VARCHAR(256) ,
              Fileid SMALLINT ,
              filename VARCHAR(256) ,
              FileGroup VARCHAR(520) ,
              TotalSize NVARCHAR(13) ,
              UsedSize NVARCHAR(13) ,
              FreeSizeRatio NVARCHAR(15) ,
              maxsize VARCHAR(20) ,
              growth VARCHAR(15) ,
              usage VARCHAR(20)
            )
        SELECT  @cmd = N'use ' + QUOTENAME(@dbname) + N' exec sys.sp_helpfile' 
        INSERT  INTO #fileInfo ( Name, Fileid, filename, FileGroup, TotalSize, maxsize, growth, usage )
                EXEC ( @cmd
                    )
       
        UPDATE  #fileInfo
        SET     TotalSize = STR(CONVERT(dec(17, 2), LEFT(TotalSize, LEN(TotalSize) - 2)) / 1024.0, 10, 2)
        UPDATE  #fileInfo
        SET     TotalSize = STR(f.TotalSize, 10, 2) + ' MB'
                        , UsedSize = STR(CONVERT(dec(17, 2),s.usedSize)/128.0, 10, 2) + ' MB',
                FreeSizeRatio = STR(( CONVERT(dec(17, 2), s.TotalSize) - CONVERT(dec(17, 2), s.usedSize) ) * 100.0
                                    / CONVERT(dec(17, 2), s.TotalSize), 5, 2) + '%'
        FROM    #fileInfo f
        LEFT JOIN #fileSize s ON f.Name = s.fName
                                 AND s.dbName = @dbname
        SELECT  Name, Fileid, filename, FileGroup, TotalSize, UsedSize, FreeSizeRatio, maxsize, growth, usage
        FROM    #fileInfo
 
    END 
RETURN (0) -- sp_helpdb 
END
EXEC sys.sp_MS_marksystemobject 'sp_DBA_helpdb';

對改寫後的sp_DBA_helpdb進行測試,首先傳入參數NULL,如下:

EXEC sp_DBA_helpdb NULL

然後在將參數換爲指定的數據庫,這裏使用的是test庫:

EXEC sp_DBA_helpdb test

從測試的結果來看,完全符合我們的預期。

這樣當我們使用前文中的sp_DBA_shrikfile對單個數據庫文件進行收縮時,sp_DBA_helpdb能夠快速爲我們確定各參數的值。同時sp_DBA_helpdb亦能幫助我們快速確定哪些文件需要收縮、收縮哪些文件可以快速釋放大量空間。

後記

這裏我直接改了sp_helpdb來實現目標,當然也可以先修改sp_helpfile,實現目標後再調用修改後的sp_helpfile 修改sp_DBA_helpdb。 

​如果想更爲及時的獲取最新文章,可以搜索注公衆 MSQLServer,將有更多精彩。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章