使用 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();
}
}
我在连接到远程 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();
}
}