2

SQL Server から大きな BLOB データを取得するときに、メモリ不足の例外が発生します。6 列の単純なデータと 1 つのデータ列を返すストアド プロシージャを呼び出していvarbinary(max)ます。

このコードを使用してストアド プロシージャを実行しています。

m_DataReader = cmd.ExecuteReader(CommandBehavior.SequentialAccess);

データリーダーから列を順番に読み取るようにしています。

大きなデータの取得に関する MSDN の記事を参照してください。

varbinary(max)列については、次のようにデータを読み込んでいます。

DocBytes = m_DataReader.GetValue(i) as byte[];

私が気付いたのは、メモリ不足の時点で、メモリ内にバイト配列のコピーが 2 つあるように見えることです。1 つはDocBytes配列にあり、もう 1 つは の内部バッファにありSqlDataReaderます。

なぜこれのコピーがあるのですか?私は参照を渡すと思いましたか、それともSqlDataReaderデータを提供する内部的な方法によるものですか?つまり、常にコピーを提供しますか?

データベースからデータを読み取るメモリ効率の良い方法はありますか?

新しい .NET 4.5GetStreamメソッドを見てきましたが、残念ながら、ストリームを渡す機能がありません - メモリ内のバイトが必要です - そのため、ファイルまたは Web 応答へのストリーミングの他の例に従うことはできません。しかし、一度に 1 つのコピーだけがメモリに存在することを確認したいと思います!

これはおそらくあるべき姿であり、複製コピーはまだガベージ コレクションされていないバッファに過ぎないという結論に達しました。私はガベージコレクションを強制的にいじる必要は本当にありません.誰かが別のアプローチについていくつかのアイデアを持っていることを望んでいます.

4

5 に答える 5

1

新しい .NET 4.5 GetStream メソッドを見てきましたが、残念ながら、ストリームを渡す機能がありません - メモリ内のバイトが必要です

したがって、このストリームからバイト配列に読み取るだけで済みます。

または、ここに示す方法を使用して、リーダーから小さなチャンクで読み取ることもできGetBytesます: https://stackoverflow.com/a/625485/29407

于 2013-08-15T09:20:04.370 に答える
1

問題は、 をDbDataReader.GetStream()作成し、MemoryStreamこのストリームにフィールドのデータを入力することです。これを回避するために、拡張メソッドを作成しました。

public static class DataReaderExtensions
{
    /// <summary>
    /// writes the content of the field into a stream
    /// </summary>
    /// <param name="reader"></param>
    /// <param name="ordinal"></param>
    /// <param name="stream"></param>
    /// <returns>number of written bytes</returns>
    public static long WriteToStream(this IDataReader reader, int ordinal, Stream stream)
    {
        if (stream == null)
            throw new ArgumentNullException("stream");

        if (reader.IsDBNull(ordinal))
            return 0;

        long num = 0L;
        byte[] array = new byte[8192];
        long bytes;
        do
        {
            bytes = reader.GetBytes(ordinal, num, array, 0, array.Length);
            stream.Write(array, 0, (int)bytes);
            num += bytes;
        }
        while (bytes > 0L);
        return num;
    }

    /// <summary>
    /// writes the content of the field into a stream
    /// </summary>
    /// <param name="reader"></param>
    /// <param name="field"></param>
    /// <param name="stream"></param>
    /// <returns>number of written bytes</returns>
    public static long WriteToStream(this IDataReader reader, string field, Stream stream)
    {
        int ordinal = reader.GetOrdinal(field);
        return WriteToStream(reader, ordinal, stream);
    }
}
于 2016-11-30T12:13:43.027 に答える
0

データの長さを知っていますか?その場合、ストリーミング アプローチを使用して、データを完全なサイズの にコピーできますbyte[]。これにより、非ストリーミングの場合に ADO.NET 内で発生するように見えるダブル バッファリングが解消されます。

于 2013-08-15T13:44:48.270 に答える
0

DocBytes = m_DataReader.GetValue(i) as byte[];

これにより、サイズ DATA_LENGTH(column_name) のバッファーが作成
され、それが完全に MemoryStream にコピーされます。
DATA_LENGTH(column_name) が大きな値の場合、これは悪いことです。
バッファを介してメモリストリームにコピーする必要があります。

また、ファイルが大きい場合は、MemoryStream に完全に保存するのではなく、一時ファイルに書き込みます。

これが私のやり方です

    // http://stackoverflow.com/questions/2885335/clr-sql-assembly-get-the-bytestream
    // http://stackoverflow.com/questions/891617/how-to-read-a-image-by-idatareader
    // http://stackoverflow.com/questions/4103406/extracting-a-net-assembly-from-sql-server-2005
    public static void RetrieveFileStream(System.Data.IDbCommand cmd, string columnName, string path)
    {
        using (System.Data.IDataReader reader = cmd.ExecuteReader(System.Data.CommandBehavior.SequentialAccess | System.Data.CommandBehavior.CloseConnection))
        {
            bool hasRows = reader.Read();
            if (hasRows)
            {
                const int BUFFER_SIZE = 1024 * 1024 * 10; // 10 MB
                byte[] buffer = new byte[BUFFER_SIZE];

                int col = string.IsNullOrEmpty(columnName) ? 0 : reader.GetOrdinal(columnName);
                int bytesRead = 0;
                int offset = 0;

                // Write the byte stream out to disk
                using (System.IO.FileStream bytestream = new System.IO.FileStream(path, System.IO.FileMode.Create, System.IO.FileAccess.Write, System.IO.FileShare.None))
                {
                    while ((bytesRead = (int)reader.GetBytes(col, offset, buffer, 0, BUFFER_SIZE)) > 0)
                    {
                        bytestream.Write(buffer, 0, bytesRead);
                        offset += bytesRead;
                    } // Whend

                    bytestream.Close();
                } // End Using bytestream 

            } // End if (!hasRows)

            reader.Close();
        } // End Using reader

    } // End Function RetrieveFile

このコードを採用して memoryStream に書き込むのは簡単です。
バッファサイズを小さくしたり大きくしたりする必要があるかもしれません。

    public static System.IO.MemoryStream RetrieveMemoryStream(System.Data.IDbCommand cmd, string columnName, string path)
    {
        System.IO.MemoryStream ms = new System.IO.MemoryStream();

        using (System.Data.IDataReader reader = cmd.ExecuteReader(System.Data.CommandBehavior.SequentialAccess | System.Data.CommandBehavior.CloseConnection))
        {
            bool hasRows = reader.Read();
            if (hasRows)
            {
                const int BUFFER_SIZE = 1024 * 1024 * 10; // 10 MB
                byte[] buffer = new byte[BUFFER_SIZE];

                int col = string.IsNullOrEmpty(columnName) ? 0 : reader.GetOrdinal(columnName);
                int bytesRead = 0;
                int offset = 0;

                // Write the byte stream out to disk
                while ((bytesRead = (int)reader.GetBytes(col, offset, buffer, 0, BUFFER_SIZE)) > 0)
                {
                    ms.Write(buffer, 0, bytesRead);
                    offset += bytesRead;
                } // Whend

            } // End if (!hasRows)

            reader.Close();
        } // End Using reader

        return ms;
    } // End Function RetrieveFile

Response.OutputStream に入れる必要がある場合は、MemoryStream.ToArray() + WriteBytes 経由ではなく、そこに直接書き込むことを検討してください。

于 2016-03-31T14:55:01.413 に答える