是什么导致下面查询中的算术溢出?

Mar*_*lli 5 sql-server filegroups dynamic-sql database-size sql-server-2014

当我在 MY_Database 上运行以下查询时

select * from sys.sysfiles
Run Code Online (Sandbox Code Playgroud)

我得到以下结果:

在此处输入图片说明

但是当我运行一个动态查询来获取可用空间的百分比时,我得到:

消息 8115,级别 16,状态 7,第 93 行 将数字转换为数据类型数字的算术溢出错误。

DECLARE @command NVARCHAR(MAX) 
SELECT @command = 'SELECT  db_name() as db_name,
CAST(S.size/128.0 - CAST(FILEPROPERTY(S.name, ' + '''' +  
               'SpaceUsed' + '''' + ' ) AS int)/128.0 AS int) AS FreeSpaceMB  
        ,CAST(100 * (CAST (((S.size/128.0 -CAST(FILEPROPERTY(S.name,  
        ' + '''' + 'SpaceUsed' + '''' + ' ) AS int)/128.0)/(S.size/128.0))  
        AS decimal(5,2))) AS varchar(8)) + ' + '''' +  '''' + ' AS FreeSpacePct
        FROM sys.sysfiles S'  

exec sp_executesql @statement = @command
Run Code Online (Sandbox Code Playgroud)

我正在努力寻找算术溢出的原因。为什么会这样?

为什么要除以 128?这是因为sys.sysfilesFILEPROPERTY 都给出了 8K 页面的数量,而不是 MB,并且要将 8K 页面转换为 MB,您需要除以 128 ,如此处所述

为什么是动态的?

因为我实际上使用 sp_ForEachDB 从每个数据库中获取值如下例所示:

DECLARE @command VARCHAR(5000) 
SELECT @command = 'Use ' + '?' + ' SELECT  db_name() as db_name,
CAST(S.size/128.0 - CAST(FILEPROPERTY(S.name, ' + '''' +  
               'SpaceUsed' + '''' + ' ) AS int)/128.0 AS int) AS FreeSpaceMB  
        --,CAST(100 * (CAST (((S.size/128.0 -CAST(FILEPROPERTY(S.name,  
        --' + '''' + 'SpaceUsed' + '''' + ' ) AS int)/128.0)/(S.size/128.0))  
        --AS decimal(4,2))) AS varchar(8)) + ' + '''' +  '''' + ' AS FreeSpacePct
        FROM dbo.sysfiles S'  

EXEC sp_ForEachDB @command  
Run Code Online (Sandbox Code Playgroud)

Pau*_*ite 15

我正在努力寻找算术溢出的原因。为什么会这样?

元数据很可能会返回一些您的代码无法处理的意外值。例如:

-- Example values returned from sysfiles and FILEPROPERTY
DECLARE 
    @size integer = 1,
    @spaceused integer = 10000;

-- The essence of the code in the question
SELECT
    CAST
    (
        100 * 
        (
            CAST 
            (
                (
                    (@size/128.0 - @spaceused/128.0)/(@size/128.0)
                )  
                AS decimal(5,2)
            )
        ) 
        AS varchar(8)
    ) + '' AS FreeSpacePct;
Run Code Online (Sandbox Code Playgroud)

...返回问题中提到的错误,因为计算出的(负!)值不适合decimal(5,2).

报告的大小可能远低于已用空间的原因有很多,包括 tempdb 文件增长、文件流文件、旧版本 SQL Server 中的错误……太多无法一一列举。您可以/应该对这种可能性进行防御性编码(以及离线/失效文件......等等)。

该问题被标记为 SQL Server 2014,因此无需使用已弃用的sys.sysfiles视图(为了与 SQL Server 2000向后兼容):

我可以将这个查询写为:

SELECT
    DatabaseName = DB_NAME(),
    [FileName] = DF.name,
    FileType = DF.type_desc,
    SizeMB = STR(DF.size * Factor.PagesToMB, 10, 2),
    SpaceUsedMB = STR(FP.SpaceUsed * Factor.PagesToMB, 10, 2),
    FreeSpaceMB = STR(FS.FreeSpace * Factor.PagesToMB, 10, 2),
    FreeSpacePct =  STR(Factor.ToPct * FS.FreeSpace / DF.size, 7, 4)
FROM sys.database_files AS DF
CROSS APPLY (SELECT FILEPROPERTY(DF.name, 'SpaceUsed')) AS FP (SpaceUsed)
CROSS APPLY (SELECT DF.size - FP.SpaceUsed) AS FS (FreeSpace)
CROSS JOIN  (SELECT 8e0 / 1024e0, 1e2) AS Factor (PagesToMB, ToPct);
Run Code Online (Sandbox Code Playgroud)

主要优势:

  • 它分离出计算步骤
  • 使用浮点运算来避免溢出
  • STR 格式化结果并且不会在溢出时引发错误
  • 它不会导致问题中的错误被抛出

动态 SQL 版本(收集所有数据库的信息):

DECLARE @SQL nvarchar(2000);

SET @SQL = N'
USE ?;

SELECT
    DatabaseName = DB_NAME(),
    [FileName] = DF.name,
    FileType = DF.type_desc,
    SizeMB = STR(DF.size * Factor.PagesToMB, 10, 2),
    SpaceUsedMB = STR(FP.SpaceUsed * Factor.PagesToMB, 10, 2),
    FreeSpaceMB = STR(FS.FreeSpace * Factor.PagesToMB, 10, 2),
    FreeSpacePct =  STR(Factor.ToPct * FS.FreeSpace / DF.size, 7, 4)
FROM sys.database_files AS DF
CROSS APPLY (SELECT FILEPROPERTY(DF.name, ''SpaceUsed'')) AS FP (SpaceUsed)
CROSS APPLY (SELECT DF.size - FP.SpaceUsed) AS FS (FreeSpace)
CROSS JOIN  (SELECT 8e0 / 1024e0, 1e2) AS Factor (PagesToMB, ToPct);
';

DECLARE @Results AS table
(
    DatabaseName sysname NOT NULL,
    [FileName] sysname NOT NULL,
    FileType nvarchar(60) NOT NULL,
    SizeMB char(10) NULL,
    SpaceUsedMB char(10) NULL,
    FreeSpaceMB char(10) NULL,
    FreeSpacePct char(7) NULL
);

INSERT @Results
EXECUTE sys.sp_MSforeachdb
    @command1 = @SQL;

SELECT R.*
FROM @Results AS R
ORDER BY R.DatabaseName; -- Or whatever
Run Code Online (Sandbox Code Playgroud)

平常注意事项有关使用sp_MSforeachdb