如何使用 MySQL 和 C#(MySQL 连接器)正确处理大 blob

How to work with big blobs properly, using MySQL and C# (MySQL Connector)

我需要 save/load 文件 to/from MySQL 数据库。估计文件大小约为 500 Mb。我没有太多使用 MySQL 的经验,也从未在数据库上下文中处理过这么大的文件。我的第一步是阅读 "MySQL Connector/Net Developer Guide",但那里的示例不太适合实际使用。我在这里寻找问题和答案,但我发现的所有关于 blob 的问题都是关于在 db 中保存图片。图片不是很大的文件,远小于 500Mb。 这里的例子,来自"MySQL Connector/Net Developer Guide",我说的是:

Writing BLOB to datavase example

Reading BLOB from database example

这是我根据这些示例编写的代码(在数据库中写入 blob):

public static class TABLES
{
    public struct Column
    {
        public string Name { get; }
        public string Table { get; }

        public string Alias
        {
            get { return Table + Name; }
        }

        public string AS
        {
            get { return string.Format ( "{0} AS {1}", Name, Alias ); }
        }

        public string TableDotName
        {
            get { return string.Format ( "{0}.{1}", Table, Name ); }
        }

        public string TableDotAS
        {
            get { return string.Format ( "{0}.{1}", Table, AS ); }
        }

        public override string ToString ( )
        {
            return Name;
        }

        public Column ( string parTitle, string parTable )
        {
            Name = parTitle;
            Table = parTable;
        }
    }

    public class FILES
    {
        public const string TABLE = "Files";

        public static readonly Column ID = new Column( "id", TABLE );
        public static readonly Column TITLE = new Column( "Title", TABLE );
        public static readonly Column FILE = new Column( "File", TABLE );

        public static readonly Column [] COLUMNS = { ID, TITLE, FILE };
    }

}

public bool AddFileParameter(MySqlCommand parCommand, string parFilePath, string parParameterName, bool parIsRequired )
{
    bool retResult = false;
    if(parCommand == null )
    {
        throw new NullReferenceException ( "MySqlCommand is null" );
    }
    if (System.IO.File.Exists( parFilePath ) )
    {
        using ( System.IO.FileStream fs = new System.IO.FileStream ( parFilePath, System.IO.FileMode.Open, System.IO.FileAccess.Read ) )
        {
            uint FileSize = (uint)fs.Length;
            byte [ ] rawData =  new byte[FileSize];
            fs.Read ( rawData, 0, ( int ) FileSize );
            fs.Close ( );
            parCommand.Parameters.AddWithValue ( parParameterName, rawData );

            retResult = true;
        }
    }
    else if (parIsRequired)
    {
        throw new System.IO.FileNotFoundException ( "File not found", parFilePath );
    }
    return retResult;
}

public int InsertFile ( string parTitle, string parFilePath )
{
    int retResult = 0;
    string queryParTitle = "@Title";
    string queryParFile = "@File";

    string commandText = string.Format(@"INSERT IGNORE INTO {0} ({1}, {2}) VALUES({3}, {4});",
        new object[] {
            TABLES.FILES.TABLE, //0

            TABLES.FILES.TITLE.Name, //1
            TABLES.FILES.FILE.Name, //2

            queryParTitle, //3
            queryParFile, //4
        } );
    try
    {
        using ( var myConnection = DBOpenConnection ( ) )
        {
            try
            {
                using ( MySqlCommand myCommand = new MySqlCommand ( commandText, myConnection ) )
                {
                    myCommand.Prepare ( );

                    myCommand.Parameters.AddWithValue ( queryParTitle, parTitle );

                    AddFileParameter ( myCommand, parFilePath, queryParFile, false );


                    retResult = DBExecuteNonQuery ( myCommand );
                }
            }
            finally
            {
                DBCloseConnection ( myConnection );
            }
        }
        return retResult;

    }
    catch ( DBConnectException e )
    {
        throw e;
    }
    catch ( DBDisconnectException e )
    {
        throw e;
    }
    catch ( DBExecuteQueryException e )
    {
        throw e;
    }
    catch ( DBReadValueException e )
    {
        throw e;
    }
    catch ( Exception e )
    {
        throw ( new DBException ( "Unhadled database error", e ) );
    }
}

它可以工作,但问题是文件已完全加载到 RAM 中。而且我不知道如何逐个上传文件。

第二个问题是从数据库中获取文件。在上面的例子中, 我们必须明确指定 BLOB 的大小。这是一个耻辱。我怎么知道它的大小?为什么数据库不知道呢? MySQL 连接器有方法 "IsDBNull",但我找不到类似 "GetSize" 的方法。 BLOB 再次完全加载到 RAM 中。

那么,我的问题是:如何正确处理大 BLOB?如何逐段下载和上传?以及如何在不知道文件大小的情况下从 BLOB 获取文件。

how to upload file piece by piece?

您可以创建第二个 table 并将文件内容存储在那里,但您需要将每一行设置为具有最大字节数。像这样:

----------    -------------------------------------
|_IDS____|    |_FileId__|_FileData__|_DataLength__|
|fileId1 |    | fileId1 | 01010111  |      8      |
|fileId2 |    | fileId1 | 11101111  |      8      |
----------    | fileId1 | 001001    |      6      |
              | fileId2 | 00001     |      5      |
              -------------------------------------

In the example above, we must explicitly specify the size of the BLOB. How should I know it's size?

有两种方法:
1. 将文件数据存储在某处:

    var buffer = new byte[long.MaxValue];
    var actualNumberOfBytesRead = reader.GetBytes(columnIndex, 0, buffer, 0, buffer.Length);
  1. 通过SQL:SELECT OCTET_LENGTH(yourBlob) FROM yourTable;
    但请注意 mysql 执行计算以检查大小。所以如果你查询尺寸,你的查询可能会很慢。

    How to download and upload it piece by piece?

将 System.IO.FileStream class 与方法 Read() 和 Write() 以及我上面建议的 table 配置一起使用。 .