【问题标题】:C# : Forcing a clean run in a long running SQL reader loop?C#:在长时间运行的 SQL 阅读器循环中强制干净运行?
【发布时间】:2010-05-26 16:17:06
【问题描述】:

我有一个 SQL 数据阅读器,可以从 sql db 表中读取 2 列。 一旦完成它的工作,它就会再次开始选择另外 2 列。

我会一口气完成所有工作,但这会带来另一组挑战。

我的问题是该表包含大量数据(大约 300 万行左右),这使得处理整个数据集有点问题。

我正在尝试验证字段值,因此我先提取 ID 列,然后提取其他列之一,并通过验证管道运行列中的每个值,结果存储在另一个数据库中。

我的问题是,当读者到达处理一列的末尾时,我需要强制它立即清理所使用的每一小块 ram,因为此过程使用大约 700MB 并且它有大约 200 列要通过。

如果没有完整的垃圾收集,我肯定会耗尽内存。

有人知道我该怎么做吗?

我正在使用许多小的可重用对象,我的想法是我可以在每个读取周期结束时调用 GC.Collect() 并且这会清除所有内容,不幸的是由于某种原因这没有发生。

好的,我希望这适合,但这是有问题的方法...

void AnalyseTable(string ObjectName, string TableName)
{
    Console.WriteLine("Initialising analysis process for SF object \"" + ObjectName + "\"");
    Console.WriteLine("   The data being used is in table [" + TableName + "]");
    // get some helpful stuff from the databases
    SQLcols = Target.GetData("SELECT Column_Name, Is_Nullable, Data_Type, Character_Maximum_Length FROM information_schema.columns WHERE table_name = '" + TableName + "'");
    SFcols = SchemaSource.GetData("SELECT * FROM [" + ObjectName + "Fields]");
    PickLists = SchemaSource.GetData("SELECT * FROM [" + ObjectName + "PickLists]");

    // get the table definition
    DataTable resultBatch = new DataTable();
    resultBatch.TableName = TableName;
    int counter = 0;

    foreach (DataRow Column in SQLcols.Rows)
    {
        if (Column["Column_Name"].ToString().ToLower() != "id")
            resultBatch.Columns.Add(new DataColumn(Column["Column_Name"].ToString(), typeof(bool)));
        else
            resultBatch.Columns.Add(new DataColumn("ID", typeof(string)));
    }
    // create the validation results table
    //SchemaSource.CreateTable(resultBatch, "ValidationResults_");
    // cache the id's from the source table in the validation table
    //CacheIDColumn(TableName);

    // validate the source table
    // iterate through each sql column
    foreach (DataRow Column in SQLcols.Rows)
    {
        // we do this here to save making this call a lot more later
        string colName = Column["Column_Name"].ToString().ToLower();
        // id col is only used to identify records not in validation
        if (colName != "id")
        {
            // prepare to process
            counter = 0;
            resultBatch.Rows.Clear();
            resultBatch.Columns.Clear();
            resultBatch.Columns.Add(new DataColumn("ID", typeof(string)));
            resultBatch.Columns.Add(new DataColumn(colName, typeof(bool)));

            // identify matching SF col
            foreach (DataRow SFDefinition in SFcols.Rows)
            {
                // case insensitive compare on the col name to ensure we have a match ...
                if (SFDefinition["Name"].ToString().ToLower() == colName)
                {
                    // select the id column and the column data to validate (current column data)
                    using (SqlCommand com = new SqlCommand("SELECT ID, [" + colName + "] FROM [" + TableName + "]", new SqlConnection(ConfigurationManager.ConnectionStrings["AnalysisTarget"].ConnectionString)))
                    {
                        com.Connection.Open();
                        SqlDataReader reader = com.ExecuteReader();

                        Console.WriteLine("   Validating column \"" + colName + "\"");
                        // foreach row in the given object dataset 
                        while (reader.Read())
                        {
                            // create a new validation result row
                            DataRow result = resultBatch.NewRow();
                            bool hasFailed = false;
                            // validate it
                            object vResult = ValidateFieldValue(SFDefinition, reader[Column["Column_Name"].ToString()]);
                            // if we have the relevant col definition lets decide how to validate this value ...
                            result[colName] = vResult;

                            if (vResult is bool)
                            {
                                // if it's deemed to have failed validation mark it as such
                                if (!(bool)vResult)
                                    hasFailed = true;
                            }

                            // no point in adding rows we can't trace
                            if (reader["id"] != DBNull.Value && reader["id"] != null)
                            {
                                // add the failed row to the result set
                                if (hasFailed)
                                {
                                    result["id"] = reader["id"];
                                    resultBatch.Rows.Add(result);
                                }
                            }

                            // submit to db in batches of 200
                            if (resultBatch.Rows.Count > 199)
                            {
                                counter += resultBatch.Rows.Count;
                                Console.Write("   Result batch completed,");
                                SchemaSource.Update(resultBatch, "ValidationResults_");
                                Console.WriteLine("      committed " + counter.ToString() + " fails to the database so far.");
                                Console.SetCursorPosition(0, Console.CursorTop-1);
                                resultBatch.Rows.Clear();
                            }
                        }
                        // get rid of these likely very heavy objects
                        reader.Close();
                        reader.Dispose();
                        com.Connection.Close();
                        com.Dispose();
                        // ensure .Net does a full cleanup because we will need the resources.
                        GC.Collect();

                        if (resultBatch.Rows.Count > 0)
                        {
                            counter += resultBatch.Rows.Count;
                            Console.WriteLine("   All batches for column complete,");
                            SchemaSource.Update(resultBatch, "ValidationResults_");
                            Console.WriteLine("      committed " + counter.ToString() + " fails to the database.");
                        }
                    }
                }
            }
        }

        Console.WriteLine("   Completed processing column \"" + colName + "\"");
        Console.WriteLine("");
    }

    Console.WriteLine("Object processing complete.");
}

【问题讨论】:

  • 为什么不能在 SQL Server 中运行验证?
  • 验证过程涉及一些无法在 SQL 中实现的复杂规则。可以根据其他数据库甚至其他网络中的值设置值。
  • 这似乎不是一个容易解决的问题......现在我在一台有更多内存的机器上运行这段代码,我确实让它更干净了,但它仍然推动我的内存使用到 4.5GB,我还没有在一张大桌子上尝试过(得到一张包含大约 9000 万条记录的桌子)。

标签: c# sql garbage-collection sqldatareader


【解决方案1】:

你能发布一些代码吗? .NET 的数据阅读器应该是在 RAM 上吝啬的“消防水管”,除非正如 Freddy 建议的那样,您的列数据值很大。这个验证+数据库写入需要多长时间?

一般来说,如果需要GC并且可以完成,它就会完成。我可能听起来像是一张破唱片,但如果你必须使用 GC.Collect() 其他东西是错误的。

【讨论】:

  • +1 我同意,在这种情况下强制垃圾收集似乎是完全错误的。此外,如果没有顺序访问,读者将保留对 OP 无论如何都打算释放的实例的内部引用。
【解决方案2】:

Open the reader with Sequential access,它可能会给你你需要的行为。此外,假设这是一个 blob,您最好还是分块阅读它。

为 DataReader 提供一种方法来处理包含具有较大二进制值的列的行。 SequentialAccess 不是加载整个行,而是使 DataReader 能够将数据作为流加载。然后,您可以使用 GetBytes 或 GetChars 方法指定开始读取操作的字节位置,以及返回数据的有限缓冲区大小。

当您指定 SequentialAccess 时,您需要按照列的返回顺序读取它们,尽管您不需要读取每一列。一旦您读取了返回的数据流中的某个位置,就无法再从 DataReader 读取该位置处或之前的数据。使用 OleDbDataReader 时,您可以重新读取当前列值,直到读取过去。使用SqlDataReader时,一个列的值只能读取一次。

【讨论】:

  • 我对单个字段中的数据大小没有问题,只是我需要阅读的字段数量太多了。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2020-08-14
  • 1970-01-01
  • 2020-05-09
  • 1970-01-01
  • 2014-08-27
  • 1970-01-01
相关资源
最近更新 更多