web-dev-qa-db-fra.com

qu'est-ce qui cause un dépassement arithmétique dans la requête ci-dessous?

lorsque j'exécute la requête suivante sur MY_Database

select * from sys.sysfiles

J'obtiens les résultats suivants:

enter image description here

Mais lorsque j'exécute une requête dynamique qui obtient le pourcentage d'espace libre que j'obtiens:

Msg 8115, niveau 16, état 7, ligne 93 Erreur de dépassement arithmétique lors de la conversion numérique en type de données numérique.

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

J'ai du mal à trouver la raison du débordement arithmétique. pourquoi cela se produit-il?

Pourquoi diviser par 128? C'est parce que sys.sysfiles et PROPRIÉTÉ DE FICHIER donnent le nombre de pages 8K, pas MB, et pour convertir de 8K pages en Mo que vous divisez par 128 comme cela est expliqué ici

Pourquoi est-il dynamique?

Parce que j'obtiens réellement les valeurs de chaque base de données en utilisant sp_ForEachDB comme vous pouvez le voir sur l'exemple ci-dessous:

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  
5

J'ai du mal à trouver la raison du débordement arithmétique. pourquoi cela se produit-il?

Très probablement, les métadonnées renvoient des valeurs inattendues que votre code ne peut pas gérer. Par exemple:

-- 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;

... renvoie l'erreur mentionnée dans la question, car la valeur calculée (négative!) ne rentrera pas dans decimal(5,2).

Il existe des raisons pour lesquelles la taille peut être signalée comme étant beaucoup plus faible que l'espace utilisé, y compris les croissances de fichiers tempdb, les fichiers filestream, les bogues dans les anciennes versions de SQL Server ... trop nombreux pour être répertoriés. Vous pourriez/devriez coder défensivement contre cette possibilité (et aussi pour les fichiers hors ligne/disparus ... et ainsi de suite).

La question est étiquetée SQL Server 2014, il n'est donc pas nécessaire d'utiliser la vue obsolète sys.sysfiles (pour une compatibilité descendante avec SQL Server 2000 ):

Je pourrais écrire cette requête comme:

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);

Principaux avantages:

  • Il sépare les étapes de calcul
  • Utilise l'arithmétique float pour éviter les débordements
  • STR formate le résultat et ne déclenche pas d'erreur en cas de débordement
  • Cela n'entraîne pas la levée de l'erreur dans la question

Une version SQL dynamique (pour collecter des informations pour toutes les bases de données):

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

mises en garde habituelles sur l'utilisation de sp_MSforeachdb.

15
Paul White 9

Peut-être avez-vous oublié qu'un fichier peut avoir 100% d'espace libre?

Dans ce cas, vous avez besoin de DECIMAL (5,2), pas DECIMAL (4,2).

4
Twinkles

Exécutez la requête dans SSMS et examinez le plan d'exécution réel. Mettez en surbrillance le calcul scalaire et examinez ses propriétés (F4). Regardez la propriété Defined Values. Sur mon système, j'ai obtenu ceci:

[Expr1008] = Scalar Operator(db_name()), [Expr1009] = 
Scalar Operator(CONVERT(int,[Expr1013]/(128.0)CONVERT_IMPLICIT(numeric(10,0),
fileproperty([ReadReceipt].[sys].[sysprufiles].[lname],'SpaceUsed'),0)
/(128.0),0)), [Expr1010] = Scalar Operator(CONVERT(varchar(8),(100.)
*CONVERT(decimal(5,2),([Expr1013]/(128.0)-CONVERT_IMPLICIT(numeric(10,0),
fileproperty([ReadReceipt].[sys].[sysprufiles].[lname],'SpaceUsed'),0)
/(128.0))/([Expr1013]/(128.0)),0),0)+'')

Il y a beaucoup de CONVERT_IMPLICIT en cours là-dedans. Il est probable que l'une de vos bases de données déborde de l'un de ces calculs intermédiaires. Je ne vois pas d'erreur sur ma petite boîte de développement.

Pour déboguer, je commenterais à tour de rôle chacune de vos valeurs calculées pour voir laquelle génère l'erreur. Ensuite, je filtrerais les grandes bases de données en utilisant WHERE. Si cela fonctionne pour les petites bases de données, ce serait un indice. Ensuite, supprimez le calcul à son minimum et exécutez-le uniquement pour la plus grande base de données. Ajoutez les CAST l'un après l'autre et comparez les valeurs définies pour les dispositions où cela fonctionne et où il échoue.

Mon sentiment est qu'un seul CAST à INT autour de l'ensemble du calcul est susceptible d'être votre meilleure option.

Cet article l'état du paramètre ARITHABORT a également un impact.

3
Michael Green