Feedback

Größe einer SQL-Server Datenbank ermitteln

Sprache: VB

Bei Datenbankanwendungen sollte man stets die Datenbankgröße im Auge haben. Das gilt besonders für Nutzer der SQL-Server Express Versionen, da diese auf 4GB beschränkt sind. Mit diesem Beispiel möchte ich zeigen, wie die Datenbankgröße einer SQL-Server Datenbank ermittelt werden kann. Viel Spass bei der kreativen Weiterverwendung.
'''''' <summary>
    '''''' Gibt die Größe einer SQL-Server Datenbank zurück
    '''''' </summary>
    '''''' <param name="database">Name der Datenbank</param>
    '''''' <returns>size + MB</returns>
    '''''' <remarks></remarks>
    Function GetDatabaseSize(ByVal database As String) As String

        Using conn As New SqlConnection("connectionString")

            Dim sql As String = "USE " & database & "; exec sp_spaceused @updateusage=true"
            Dim cmd As New SqlCommand(sql, conn)
            Dim dA As New SqlDataAdapter(cmd)
            Dim sizeDataSet As New DataSet()

            Try
                conn.Open()
                dA.Fill(sizeDataSet, "SizeInfoTable")

                Return sizeDataSet.Tables("SizeInfoTable").Rows(0)("database_size").ToString()
                ''''Die Tabelle stellt noch weitere Spalten bereit.
                ''''Infos unter: http://msdn.microsoft.com/de-de/library/ms188776.aspx
            Catch
                Return "nicht erfasst"
            End Try

        End Using

    End Function
'''''' <summary>
    '''''' Gibt die Größe einer SQL-Server Datenbank zurück
    '''''' </summary>
    '''''' <param name="database">Name der Datenbank</param>
    '''''' <returns>size + MB</returns>
    '''''' <remarks></remarks>
    Function GetDatabaseSize(ByVal database As String) As String

        Using conn As New SqlConnection("connectionString")

            Dim sql As String = "USE " & database & "; exec sp_spaceused @updateusage=true"
            Dim cmd As New SqlCommand(sql, conn)
            Dim dA As New SqlDataAdapter(cmd)
            Dim sizeDataSet As New DataSet()

            Try
                conn.Open()
                dA.Fill(sizeDataSet, "SizeInfoTable")

                Return sizeDataSet.Tables("SizeInfoTable").Rows(0)("database_size").ToString()
                ''''Die Tabelle stellt noch weitere Spalten bereit.
                ''''Infos unter: http://msdn.microsoft.com/de-de/library/ms188776.aspx
            Catch
                Return "nicht erfasst"
            End Try

        End Using

    End Function

1 Kommentar

  1. Das Snippet ist von 2009 und trifft den damaligen Pragmatismus: sp_spaceused aufrufen und irgendeinen String zurückgeben. In aktuellen .NET- und SQL-Server-Setups hat der Ansatz aber ein paar echte Baustellen: SQL-Injection durch String-Konkatenation, unnötig schwere ADO.NET-Objekte (DataSet), potenziell teure @updateusage=true-Statistikanpassung und „Catch-all und schlucke alles“.

    [b]Analyse nach heutigen Kriterien:[/b]
    – [u]Sicherheit[/u]: „USE “ & database wird direkt in SQL zusammengebaut. Ein manipuliertes database-Argument kann zu SQL-Injection führen. „USE“ lässt sich nicht parametrieren, daher muss die Datenbankauswahl anders gelöst werden (ConnectionString/ChangeDatabase oder Whitelist).
    – [u]Korrektheit[/u]: sp_spaceused liefert Strings wie „123.45 MB“ und ist regional/formatabhängig. Das ist für Weiterverarbeitung ungeeignet. Außerdem liefert der Code nur die erste Zeile/Spalte und ignoriert, dass sp_spaceused mehrere Ergebniswerte liefert.
    – [u]Performance[/u]: @updateusage=true kann sehr teuer sein, weil es Nutzungsstatistiken aktualisiert. Für „Monitoring“ ist das in der Regel falsch; man will die Größe schnell abfragen, nicht Wartungsarbeit triggern.
    – [u]Memory-Allokationen[/u]: DataSet/SqlDataAdapter sind für eine einzelne Kennzahl Overkill und verursachen unnötige Allokationen.
    – [u]Robustheit[/u]: Catch ohne Exception-Info und Rückgabe „nicht erfasst“ macht Fehlerdiagnose praktisch unmöglich.
    – [u]Cloud/Container[/u]: In Azure SQL Database ist das Konzept „USE andere Datenbank“ typischerweise nicht gewünscht; pro Verbindung eine DB ist der Normalfall. Streamlined SQL und Microsoft.Data.SqlClient sind heute die realistischere Basis.
    – [u]Thread-Safety[/u]: Die Methode ist an sich thread-safe, solange jede Ausführung ihre eigene Connection verwendet. Die eigentliche Gefahr liegt in konkurrierenden Monitoring-Calls mit @updateusage=true.

    [b]Modernisierte Variante (ohne USE, ohne DataSet, numerische Werte, optional Data/Log getrennt):[/b]
    [code]
    using System;
    using System.Threading;
    using System.Threading.Tasks;
    using Microsoft.Data.SqlClient;

    public static class SqlDatabaseSize
    {
    public static async Task<(long DataBytes, long LogBytes)> GetDatabaseFileSizeBytesAsync(string connectionString, CancellationToken cancellationToken = default)
    {
    if (connectionString is null) throw new ArgumentNullException(nameof(connectionString));

    const string sql = @“
    SELECT
    SUM(CASE WHEN type_desc = ‚ROWS‘ THEN size END) * 8192 AS DataBytes,
    SUM(CASE WHEN type_desc = ‚LOG‘ THEN size END) * 8192 AS LogBytes
    FROM sys.database_files;“;

    await using var conn = new SqlConnection(connectionString);
    await conn.OpenAsync(cancellationToken).ConfigureAwait(false);

    await using var cmd = new SqlCommand(sql, conn);
    await using var reader = await cmd.ExecuteReaderAsync(cancellationToken).ConfigureAwait(false);

    if (!await reader.ReadAsync(cancellationToken).ConfigureAwait(false)) return (0, 0);

    long dataBytes = reader.IsDBNull(0) ? 0 : reader.GetInt64(0);
    long logBytes = reader.IsDBNull(1) ? 0 : reader.GetInt64(1);

    return (dataBytes, logBytes);
    }

    public static async Task GetDatabaseSizeBytesAsync(string connectionString, CancellationToken cancellationToken = default)
    {
    var (dataBytes, logBytes) = await GetDatabaseFileSizeBytesAsync(connectionString, cancellationToken).ConfigureAwait(false);
    return checked(dataBytes + logBytes);
    }
    }
    [/code]

    [b]Warum das heute objektiv besser ist:[/b]
    – [u]Sicherheit[/u]: Kein dynamisches „USE “ aus User-Input. Die DB-Auswahl erfolgt sauber über den ConnectionString (Initial Catalog) oder über eine kontrollierte Quelle.
    – [u]Performance[/u]: Keine Nebenwirkungen wie @updateusage=true. Die Abfrage ist eine reine Metadatenabfrage auf sys.database_files.
    – [u]Memory-Allokationen[/u]: Kein DataSet/Adapter, nur Reader und primitive Werte.
    – [u]Robustheit[/u]: Fehler werden nicht verschluckt. Der Aufrufer bekommt saubere Exceptions und kann Logging/Retry sinnvoll implementieren.
    – [u]Cloud/Container[/u]: Passt zu dem Modell „eine Verbindung = eine Datenbank“ (z. B. Azure SQL Database) und funktioniert gut in Services/Containern.

    [b]Security-Realitätscheck:[/b]
    Datenbanknamen aus User-Eingaben dürfen nie ungeprüft in SQL landen. Wenn du zwingend eine DB per Name auswählen musst, dann nur über eine Whitelist oder indem du vorab die DB-Namen serverseitig ermittelst und gegen bekannte Werte validierst, statt String-Konkatenation.