C#低内存并插入Oracle DB数据 - 100万行 - OutofMemory异常



问题:

我有一个Web应用程序,人们可以上传XML,XML,CSV文件。然后,我将他们的内容插入我的Oracle DB。


技术详细信息:

我最近遇到了一个问题,我会发现尝试使用数据的异常。以前的开发人员创建了数据列表以管理数据。但是,这给了我们OutofMemory例外。我们正在使用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异常

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

诸如列表或DataTable之类的集合使用内部缓冲区来存储它们在填充时重新分配的数据,使用原始大小的两倍。带有1M行,可导致重新分配的 lot 和记忆片段的 lot 。运行时可能不再能够甚至找到一个足以存储2M条目的内存的连续块。这就是为什么在创建新列表时设置capacity参数很重要的原因。

除此之外,它没有任何目的,可以将所有内容加载到内存中,然后将其发送到数据库。实际上,一旦读取每个文件,或者一旦加载足够大的数字,就可以立即发送数据。而不是尝试一次加载1m行,而是每次读取500或1000行,然后将其发送到数据库。

此外,Oracle的Ado.net提供商还包括OracleBulkcopy类,该类以类似于SQLBulkCopy的SQL Server的方式工作。WriteToServer方法可以接受DataTable 或DataReader 。您可以使用Databal Overload发送一批项目。一个更好的主意是使用接受读者的过载并让 class 收集批次并将其发送到数据库。

eg:

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);
}

最新更新