2016-10-19 27 views
0

我有大約700K行正在迭代。對於每一行,都會在數據庫上運行一條SELECT sql語句,以檢查當前記錄中的「名稱」字段是否存在於相應的表中。查詢數據庫的每條記錄比使用LINQ更快

700K次的數據庫讀取效率非常低,所以我選擇在循環前讀取所有數據,將其存儲在DataTable中,並檢查相應的記錄是否通過LINQ包含在DataTable中,on每次迭代。

在做到這一點時,性能下降了不少。這個過程大約需要兩倍的時間才能完成(通過基準測試多次證明)。

這是原來的(快)代碼:

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

我不明白爲什麼第一種方法速度非常快。有沒有更好的方法來取代第二種方法?

+0

它看起來像你're cross ref在同一個數據庫中創建兩個表,爲什麼不讓SQL處理這個工作並讓它返回一個不在任何表中的記錄列表。一個「左連接」或「不存在的地方」會創造奇蹟。 – jessehouwing

+0

實際上只有一個表 - 「my_table」 –

+0

如果需要,您可以將表加入自己。 – jessehouwing

回答

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來自上面的代碼有一些猜測,你可以調整它爲您的需求)

+0

這看起來很棒 - 謝謝! –

+0

如果可行的話,SQL自連接會快很多。 –

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平方)算法的準確定義。當你獲得更多的數據時,它的性能很差,並且變得更糟。

您的第一個示例更快,因爲您使用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)來自哪裏。任何處理數據的程序員都需要圍繞計算的複雜性 - O(n)的東西 - 才能成功。

+0

感謝您的解釋! +1 –

相關問題