比使用LINQ

时间:2016-10-19 10:38:15

标签: c# mysql .net performance linq

我有大约700K行正在迭代。对于每一行,在数据库上运行SELECT sql语句,以检查当前记录中的“name”字段是否存在于相应的表中。

读取700K次的数据库对我来说效率非常低,所以我选择在循环之前读取所有数据,将其存储在DataTable中,并检查相应的记录是否通过LINQ包含在DataTable中迭代。

在这样做时,性能会恶化很多。这个过程大约需要两倍的时间才能完成(通过基准测试多次证明)。

这是原始(更快)的代码:

for (int index = 0; index < dtSightings.Rows.Count; index++)
{
   DataTable dtResults = Utilities.ExecuteQueryMysqlString(connectionString, "SELECT name FROM my_table WHERE name = @name AND month_year = @monthYear", dictionary);

   if (dtResults == null || dtResults.Rows.Count == 0)
   {
   //Continue
   }
}

public static DataTable ExecuteQueryMysqlString(string connectionString, string sql, Dictionary<string, object> listParameters)
        {
            DataTable dtResults = new DataTable();

            if (string.IsNullOrWhiteSpace(connectionString) == false)
            {
                connectionString += ";Allow User Variables=True;";

                try
                {
                    using (MySqlConnection connection = new MySqlConnection(connectionString))
                    {
                        connection.Open();

                        using (MySqlCommand cmd = connection.CreateCommand())
                        {
                            cmd.CommandTimeout = 0;
                            cmd.CommandText = sql;

                            if (listParameters != null && listParameters.Count > 0)
                            {
                                foreach (string currentKey in listParameters.Keys)
                                {
                                    cmd.Parameters.Add(new MySqlParameter(currentKey, GetDictionaryValue(listParameters, currentKey)));
                                }
                            }

                            using (MySqlDataAdapter da = new MySqlDataAdapter(cmd))
                            {
                                da.Fill(dtResults);
                            }
                        }
                    }

                    return dtResults;
                }
                catch (Exception ex)
                {
                    MessageBox.Show("ERROR: " + ex.Message, "ERROR", MessageBoxButtons.OK, MessageBoxIcon.Error);
                    return dtResults;
                }
            }
            else
            {
                return dtResults;
            }
        }

这是“优化”(但速度较慢)的代码:

DataTable dt= Utilities.ExecuteQueryMysqlString(connectionString, "SELECT name, month_year FROM my_table", null);

for (int index = 0; index < dtSightings.Rows.Count; index++)
{
  DataRow row = dt.AsEnumerable().Where(r => r.Field<string>("name").Equals(name, StringComparison.InvariantCultureIgnoreCase) && r.Field<DateTime>("month_year") == new DateTime(billYear, billMonth, 1)).FirstOrDefault();

  if (hasResidentBeenDiscoveredPreviously == null)
  {
    //Continue
  }
}

我不明白为什么第一种方法要快得多。是否有更优化的方法取代第二种方法?

2 个答案:

答案 0 :(得分:1)

您的代码示例都具有此基本结构。

1. For each row in one bunch of rows ...
  2. read the rows in another bunch of rows ...
    3. to identify a particular situation.

这就是 O(n-squared)算法的定义。它的性能很差,并且随着您获得更多数据而变得非常糟糕。

您的第一个示例更快,因为您使用SELECT来读取步骤2中的行,而您的DMBS可能会对其进行优化。在第二个示例中,在步骤2中,对步骤1中的每一行迭代所有行。

诀窍是要把事情搞砸,所以你只需要通过第2步表。

很难确切地说明你在使用dtSightings做了什么:你运行索引变量,但似乎没有在任何地方使用它。无论如何,这个算法大纲应该从 O(n-squared) O(n log n)

1. make a HashSet ready to hold results for matching in dtSightings.
2. for each row in dtSightings ...
   a. populate that row into the HashSet
3. for each row in your query..
   b. Look it up in your HashSet
   c. If you get a hit (a non-match, I believe) report it.

步骤2和3分别采用 O(n)时间:它们与您正在处理的行数成比例。子步骤b每次运行时都会带 O(log n)。这就是 O(n log n)的来源。

任何处理数据megarows的程序员都需要围绕计算复杂性 - O(n)的东西 - 来成功。

答案 1 :(得分:1)

LINQ方法很慢,因为Where基本上是线性搜索,并且在循环内执行时,实际上可以减慢进程。

您真正需要的是基于快速哈希的查找数据结构。我建议你使用HashSet这样的自定义数据(主要是为了支持不区分大小写的名称查找):

public struct NameDatePair : IEquatable<NameDatePair>
{
    public readonly string Name;
    public readonly DateTime Date;
    public NameDatePair(string name, DateTime date) { Name = name; Date = date; }
    static IEqualityComparer<string> NameComparer {  get { return StringComparer.InvariantCultureIgnoreCase; } }
    public override int GetHashCode() { return NameComparer.GetHashCode(Name) ^ Date.GetHashCode(); }
    public override bool Equals(object obj) { return obj is NameDatePair && Equals((NameDatePair)obj); }
    public bool Equals(NameDatePair other) { return NameComparer.Equals(Name, other.Name) && Date == other.Date; }
}

以下是你如何在你的情况下使用它(它应该比你的方法快得多):

var dt = Utilities.ExecuteQueryMysqlString(connectionString, "SELECT name, month_year FROM my_table", null);
var nameDatePairSet = new HashSet<NameDatePair>(dt.AsEnumerable().Select(
    r => new NameDatePair(r.Field<string>("name"), r.Field<DateTime>("month_year"))));

for (int index = 0; index < dtSightings.Rows.Count; index++)
{
    var dr = dtSightings.Rows[index];
    var name = dr.Field<string>("name");
    var billYear = dr.Field<int>("billYear");
    var billMonth = dr.Field<int>("billMonth");
    bool exists = nameDatePairSet.Contains(new NameDatePair(name, new DateTime(billYear, billMonth, 1)));
}

(由于您没有显示变量namebillYearbillMonth的来源,上面的代码有一些猜测,您可以根据需要进行调整)