使用 DbDataReader 读取(迭代)数据时的巨大托管内存分配

Huge managed memory allocation when reading (iterating) data with DbDataReader

我在连接到远程 oracle 数据库(master)的多台客户端机器上编写了一个应用程序 运行 来同步他们的本地开源数据库(slave)。到目前为止效果很好。但有时本地 table 需要完全初始化(删除,然后插入 master 数据库的所有行)。如果主 table 足够大(ColumnCount 或 DataType/DataSize 和特定的 RowSize),应用程序有时会遇到 OutOfMemoryException。 该应用程序在 windows 台装有 .NET 4.0 的机器上 运行。 ODP.NET 的版本是 4.122.18.3。 Oracle 数据库是 12c (12.1.0.2.0).

我不想向任何用户显示数据(应用程序在后台 运行),否则我可以进行一些分页或过滤。由于并非所有 table 都包含键或能够自动排序,因此很难分段获取 table。本地 table 的初始化应该在一个事务中完成,而不是多次部分提交。 我可以将问题归结为一个简单的代码示例,显示了我没有预料到的托管内存分配。在这一点上,我不确定如何解释或解决问题。

using (var connection = new OracleConnection(CONNECTION_STRING))
{
    connection.Open();

    using (var command = connection.CreateCommand())
    {
        command.CommandText = STATEMENT;

        using (var reader = command.ExecuteReader())
        {
            while (reader.Read())
            {
                //reader[2].ToString();
                //reader.GetString(2);
                //reader.GetValue(2);
            }
        }
    }
}

当取消注释三个 reader.* 调用中的任何一个时,请求的列数据的内存似乎由每个记录的 ODP.NET(OracleInternal.Network.OraBuf)在内部固定。对于请求几千条记录,这似乎不是问题。但是当获取 100k+ 记录时,内存分配会达到数百 MB,这会导致 OutOfMemoryException。指定列的数据越多,OOM 发生的速度就越快(主要是 NVARCHAR2)。 另外手动调用 GC.Collect() 不会执行任何操作。图像中显示的 GC.Collect() 是在内部完成的(我自己没有调用)。

因为我没有在任何地方存储读取的数据,所以我希望在迭代 DbDataReader 时不会缓存数据。你能帮我了解这里发生了什么以及如何避免吗?

这似乎是使用托管驱动程序 12.1.0.2 的 ExecuteReader() 方法读取 clob 列值时的已知错误 (Bug 21975120)。解决方法是使用 OracleDataReader 特定方法(例如 oracleDataReader.GetOracleValue(i))。可以显式关闭 OracleClob 值以释放内存分配。

var item = oracleDataReader.GetOracleValue(columnIndex);

if (item is OracleClob clob)
{
    if (clob != null)
    {
        // use clob.Value ...

        clob.Close();
    }
}