C#内存不足并插入Oracle DB数据--100万行 - OutOfMemory Exception

时间:2017-09-27 11:35:45

标签: c# oracle out-of-memory

问题:

我有一个Web应用程序,人们可以上传xml,xmls,csv文件。 然后我将他们的内容并将其插入我的Oracle DB中。

技术细节:

我最近遇到了一个问题,我在尝试使用数据时遇到OutOfMemory Exception。 之前的开发人员创建了数据列表列表以便对其进行管理。但是,这给了我们OutOfMemory Exception。 我们正在使用LinqToExcel库。

示例代码:

excel = new ExcelQueryFactory(excelFile);
IEnumerable<RowNoHeader> data = from row in excel.WorksheetNoHeader(sheetName)
                       select row;
List<List<string>> d = new List<List<string>>(data.Count());
foreach (RowNoHeader row in data)
{
    List<string> list = new List<string>();
    foreach (Cell cell in row)
    {
        string cellValue = cell.Value.ToString().Trim(' ').Trim(null);
        list.Add(cellValue);
    }

d.Add(list);
}

我试图更改代码,而是这样做:

string connectionstring = string.Format(@"Provider=Microsoft.ACE.OLEDB.12.0;Data Source={0};Extended Properties='Excel 12.0;HDR=YES;';", excelFile);
OleDbConnection connection = new OleDbConnection();
connection.ConnectionString = connectionstring;
OleDbCommand excelCommand = new OleDbCommand();
excelCommand.Connection = connection;
excelCommand.CommandText = String.Format("Select * FROM [{0}$]", sheetName);
connection.Open();
DataTable dtbl = CreateTable(TableColumns);
OleDbDataReader reader = excelCommand.ExecuteReader();
while (reader.Read())
{
    DataRow row = dtbl.NewRow();

    dtbl.Rows.Add(row);
}

using (OracleCommand command = new OracleCommand(selectCommand, _oracleConnection))
{
    using (OracleDataAdapter adapter = new OracleDataAdapter(command))
    {
        using (OracleCommandBuilder builder = new OracleCommandBuilder(adapter))
        {
            OracleTransaction trans = _oracleConnection.BeginTransaction();
            command.Transaction = trans;
            adapter.InsertCommand = builder.GetInsertCommand(true);
            adapter.Update(dtbl);
            trans.Commit();
        }
    }
}

但是,我仍然得到相同的 OutOfMemory异常。 我已经在线阅读,并且已经看到我应该使我的项目x64并使用以下内容:

<runtime>
    <gcAllowVeryLargeObjects enabled="true" />    
</runtime>

但是,我无法将我的Web应用程序更改为在x64上运行。

我的解决方案是批量生产这样的:

int rowCount = 0;
while (reader.Read())
{
    DataRow row = dtbl.NewRow();

    dtbl.Rows.Add(row);
    if (rowCount % _batches == 0 && rowCount != 0)
    {
        DBInsert(dtbl, selectCommand);
        dtbl = CreateTable(TableColumns);
    }
}

private void DBInsert(DataTable dt, string selectCommand)
{
        using (OracleCommand command = new OracleCommand(selectCommand, _oracleConnection))
        {
            using (OracleDataAdapter adapter = new OracleDataAdapter(command))
            {
                using (OracleCommandBuilder builder = new OracleCommandBuilder(adapter))
                {
                    OracleTransaction trans = _oracleConnection.BeginTransaction();
                    command.Transaction = trans;
                    adapter.InsertCommand = builder.GetInsertCommand(true);
                    adapter.Update(dt);
                    trans.Commit();
                }
            }
        }
    }
}

它有效,但这很慢。我想知道是否有办法串行解决内存问题或并行写入内存。

我尝试使用线程并行插入数据,但这需要大量内存并且还会抛出 OutOfMemory Exception

1 个答案:

答案 0 :(得分:1)

只是将1M行加载到DataTable中。使用可用的任何批量导入机制来加载行流。与SQL Server一样,Oracle提供了几种批量导入数据的方法。

像List或DataTable这样的集合使用内部缓冲区来存储它们在填满时重新分配的数据,使用原始大小的两倍。使用1M行会导致重新分配的批次以及内存碎片的批次。运行时可能无法再找到连续的内存块,其大小足以存储2M条目。这就是为什么在创建新List时设置capacity参数很重要。

除此之外,它不会用于将所有内容加载到内存中,然后然后将其发送到数据库。读取每个文件后立即发送数据实际上更快,或者只要加载足够大的数字就立即发送数据。不要一次尝试加载1M行,而是每次读取500或1000行,并将它们发送到数据库。

此外,Oracle的ADO.NET提供程序包含OracleBulkCopy类,其工作方式类似于SQL Server的SqlBulkCopy。 WriteToServer方法可以接受DataTable 或DataReader 。您可以使用DataTable重载来发送批量项目。更好的想法是使用接受阅读器的重载并让收集批处理并将其发送到数据库。

例如:

using(var bcp=OracleBulkCopy(connectionString))
{
    bcp.BatchSize=5000;
    bcp.DestinationTableName = "MyTable";

    //For each source/target column pair, add a mapping
        bcp.ColumnMappings.Add("ColumnA","ColumnA");

    var reader = excelCommand.ExecuteReader();

    bcp.WriteToServer(reader);
}