返回一个小型DataTable时出现C#OutOfMemory异常

时间:2016-12-09 09:55:49

标签: c# sqlite system.data.sqlite

我有一个SQLite数据库,它有一个包含1800万行和24列的表。我在C#中编写了一个SQL查询函数,我用ExcelDNA将其暴露给Excel。

这是完整的代码,

    string constr = constr = "Data Source=" + FilePath + ";Version=3;Synchronous=OFF;temp_store=memory;cache_size=700000;count_changes=off;";
    DataTable dt = new DataTable();
    try
    {                                
        SQLiteConnection conn = new SQLiteConnection(constr);
        SQLiteCommand command = new SQLiteCommand(SQLStatement,conn);                
        conn.Open();                
        SQLiteDataAdapter sda = new SQLiteDataAdapter(command);                                
        sda.Fill(dt);
        sda.Dispose();
        command.Dispose();
        conn.Dispose();
        int numRows = (IncludeHeaders ? dt.Rows.Count + 1 : dt.Rows.Count);
        object[,] ret = new object[numRows, dt.Columns.Count];
        int rowCount = 0;
        if (IncludeHeaders)
        {
            int colCount = 0;
            foreach (DataColumn col in dt.Columns)
            {
                ret[rowCount, colCount] = col.ColumnName;
                colCount++;
            }
        }
        rowCount = (IncludeHeaders ? 1 : 0);
        foreach (DataRow row in dt.Rows)
        {
            int colCount = 0;
            foreach (DataColumn col in dt.Columns)
            {
                if (row[col] != DBNull.Value)
                    ret[rowCount, colCount] = row[col];
                else
                    ret[rowCount, colCount] = "";
                colCount++;
            }
            rowCount++;
        }
        return ret;
    }
    catch (Exception ex)
    {
        object[,] err = new object[1, 1];
        err[0, 0] = ex.ToString();
        return err;
    }
    finally
    {
        dt.Clear();
        dt.Dispose();
        dt = null;  
    }

如果我运行两次查询(两次点击Shift + F9),我会得到一个OutOfMemoryException。在任务管理器中,我可以看到EXCEL.EXE映像的工作集(内存)在抛出异常之前从200MB变为1500MB。

但是,这种行为并不完全一致。我返回5列和1100行的其他查询工作得很好。我在任务管理器中看到内存使用情况,并且一旦结果返回到Excel,我就会看到内存重新出现。

调试上面的应用程序会显示它在sda.Fill(dt)行前进。

会不会有任何想法?我会更好地使用SQLiteDataReader吗?或者我可以使用任何其他提示或技巧?谢谢。

顺便说一句,如果我通过Python运行确切的查询,我不会遇到这个问题,所以我认为它与C#中的垃圾收集有关。

以下是有关数据库和查询的一些详细信息。架构沿着

Date (VARCHAR)
CompanyName (VARCHAR)
Amount (REAL)
AggCode (VARCHAR)
Level1 ... Level20 (VARCHAR)

通常会在Level9子句中合并字段Level5AggCodeDateCompanyNameWHERE来运行查询。因此除了原始表之外,我还配置了以下四个索引,

CREATE INDEX idx1 on my(Level09, AggCode);
CREATE INDEX idx2 on my(Level05, AggCode);
CREATE INDEX idx3 on my(CompanyName, AggCode);
CREATE INDEX idx4 on my(Date, AggCode);

成功返回1100行和2列的查询

SELECT CompanyName, SUM(Amount) FROM my where Level09="T_EU" and AggCode = "R_S_V" GROUP BY CompanyName ORDER BY SUM(Amount) DESC

抛出内存异常的查询

SELECT Date, CompanyName, Sum(Amount) FROM my WHERE Level05 ="M_TO" AND AggCode = "C_DTA" GROUP BY Date, CompanyName

第二个查询在Python中返回163行和3列。

异常的完整堆栈跟踪如下,

System.Data.SQLite.SQLiteException (0x80004005): out of memory
out of memory
at System.Data.SQLite.SQLite3.Reset(SQLiteStatement stmt)
at System.Data.SQLite.SQLite3.Step(SQLiteStatement stmt)
at System.Data.SQLite.SQLiteDataReader.NextResult()
at System.Data.SQLite.SQLiteDataReader..ctor(SQLiteCommand cmd, CommandBehavior behave)
at System.Data.SQLite.SQLiteCommand.ExecuteReader(CommandBehavior behavior)
at System.Data.SQLite.SQLiteCommand.ExecuteDbDataReader(CommandBehavior behavior)
at System.Data.Common.DbCommand.System.Data.IDbCommand.ExecuteReader(CommandBehavior behavior)
at System.Data.Common.DbDataAdapter.FillInternal(DataSet dataset, DataTable[] datatables, Int32 startRecord, Int32 maxRecords, String srcTable, IDbCommand command, CommandBehavior behavior)
at System.Data.Common.DbDataAdapter.Fill(DataTable[] dataTables, Int32 startRecord, Int32 maxRecords, IDbCommand command, CommandBehavior behavior)
at System.Data.Common.DbDataAdapter.Fill(DataTable dataTable)
at UtilXL.Utils.UtilsSQLite.RunQueryCSLite(String SQLStatement, String FilePath, Boolean IncludeHeaders) in h:\Projects\UtilXL\UtilXL\Utils\UtilsSQLite.cs:line 37

上面引用的第37行是sda.Fill()来电。

如果我使用SqlDataReader,那么它会落在ExecuteReader()命令

System.Data.SQLite.SQLiteException (0x80004005): out of memory
at System.Data.SQLite.SQLite3.Reset(SQLiteStatement stmt)
at System.Data.SQLite.SQLite3.Step(SQLiteStatement stmt)
at System.Data.SQLite.SQLiteDataReader.NextResult() 
at System.Data.SQLite.SQLiteDataReader..ctor(SQLiteCommand cmd, CommandBehavior behave)
at System.Data.SQLite.SQLiteCommand.ExecuteReader(CommandBehavior behavior)
at System.Data.SQLite.SQLiteCommand.ExecuteReader()
at UtilXL.Utils.UtilsSQLite.RunQueryCSReader(String SQLStatement, String FilePath, Boolean IncludeHeaders) in h:\Projects\UtilXL\UtilXL\Utils\UtilsSQLite.cs:line 111

1 个答案:

答案 0 :(得分:0)

您的代码没有任何可能导致OutOfMemoryException的内容。因此可能是巨大的数据表和内存不能立即恢复。一种可能性是 - 当你正在阅读excel时...它最后可能有空行,这可能会增加dt大小......

由于数据表是托管对象,因此调用dispose实际上并没有帮助..但是,一旦使用结束,你可以在每次清除dt时使用它。