簡體   English   中英

SQLite DB 插入非常慢

[英]SQLite DB Insert Very Slow

我正在使用 SQLite 數據庫,並將記錄插入其中。 這需要很長時間! 我見過有人說他們可以在一分鍾內處理幾千個。 我有大約 2400 條記錄。 每條記錄需要 30s-2m 才能完成。 重新創建數據庫不是一種選擇。 我試圖以不同的方式創建一筆交易。 我需要使用計時器,因為我正在使用ProgressBar來顯示正在發生的事情。 這是我正在使用的代碼:

string con;
con = string.Format(@"Data Source={0}", documentsFolder);

SQLiteConnection sqlconnection = new SQLiteConnection(con);
SQLiteCommand sqlComm = sqlconnection.CreateCommand();
sqlconnection.Open();
SQLiteTransaction transaction = sqlconnection.BeginTransaction();

Timer timer2 = new Timer();
timer2.Interval = 1000;
timer2.Tick += (source, e) =>
                    {
                        URL u = firefox.URLs[count2];
                        string newtitle = u.title;
                        form.label1.Text = count2 + "/" + pBar.Maximum;
                        string c_urls = "insert or ignore into " + table + " (id,
 url, title, visit_count, typed_count, last_visit_time, hidden) values (" + dbID + ",'" + u.url + "','" 
    + newtitle + "',1,1, " + ToChromeTime(u.visited) + ", 0)";
                        string c_visited = "insert or ignore into " + table2 + " (id,
 url, 
    visit_time, transition) values (" + dbID2 + "," + dbID + "," + 
ToChromeTime(u.visited) + ",805306368)";
                        sqlComm = new SQLiteCommand(c_urls, sqlconnection);
                        sqlComm.ExecuteNonQuery();
                        sqlComm = new SQLiteCommand(c_visited, sqlconnection);
                        sqlComm.ExecuteNonQuery();

                        dbID++;
                        dbID2++;


                        pBar.Value = count2;
                        if (pBar.Maximum == count2)
                        {
                            pBar.Value = 0;
                            timer.Stop();
                            transaction.Commit();
                            sqlComm.Dispose();
                            sqlconnection.Dispose();
                            sqlconnection.Close();
                        }

                        count2++;
                    };
timer2.Start();

我究竟做錯了什么?

這就是我要解決的問題。 它可能會也可能不會解決問題,但看到它不會有什么壞處(它可能只是做一些魔術):

  1. 確保數據庫沒有被更新(來自另一個線程、進程甚至計時器!)。 寫入者將獲得鎖,並且未關閉/運行時間過長的事務可能會以糟糕的方式進行交互。 (對於需要“30 秒到 2 分鍾”的更新,我認為獲取鎖會出現問題。還要確保數據庫所在的媒體充足,例如本地驅動器。)

  2. 交易未被使用 (??)。 移動事務計時器回調,其附加到相應的SQLCommands,並且回調結束前處置它。 (使用using )。

  3. 並非所有 SQLCommand 都被正確處理。 處理每一個。 (使用using簡化了這一點。不要讓它流過回調。)

  4. 未使用占位符。 這不僅更簡單易用,而且對 SQLite 和適配器也更加友好。

(僅為示例;以下代碼中可能存在錯誤。)

// It's okay to keep long-running SQLite connections.
// In my applications I have a single application-wide connection.
// The more important thing is watching thread-access and transactions.
// In any case, we can keep this here.
SQLiteConnection sqlconnection = new SQLiteConnection(con);
sqlconnection.Open();

// In timer event - remember this is on the /UI/ thread.
// DO NOT ALLOW CROSS-THREAD ACCESS TO THE SAME SQLite CONNECTION.
// (You have been warned.)
URL u = firefox.URLs[count2];
string newtitle = u.title;
form.label1.Text = count2 + "/" + pBar.Maximum;

try {
   // This transaction is ONLY kept about for this timer callback.
   // Great care must be taken with long-running transactions in SQLite.
   // SQLite does not have good support for (long running) concurrent-writers
   // because it must obtain exclusive file locks.
   // There is no Table/Row locks!
   sqlconnection.BeginTransaction();
   // using ensures cmd will be Disposed as appropriate.
   using (var cmd = sqlconnection.CreateCommand()) {
     // Using placeholders is cleaner. It shouldn't be an issue to
     // re-create the SQLCommand because it can be cached in the adapter/driver
     // (although I could be wrong on this, anyway, it's not "this issue" here).
     cmd.CommandText = "insert or ignore into " + table
       + " (id, url, title, visit_count, typed_count, last_visit_time, hidden)"
       + " values (@dbID, @url, 'etc, add other parameters')";
     // Add each parameter; easy-peasy
     cmd.Parameters.Add("@dbID", dbID);
     cmd.Parameter.Add("@url", u.url);
     // .. add other parameters
     cmd.ExecuteNonQuery();
   }
   // Do same for other command (runs in the same TX)
   // Then commit TX
   sqlconnection.Commit();
} catch (Exception ex) {
   // Or fail TX and propagate exception ..
   sqlconnection.Rollback();
   throw;
}

if (pBar.Maximum == count2)
{
    pBar.Value = 0;
    timer.Stop();
    // All the other SQLite resources are already
    // cleaned up!
    sqlconnection.Dispose();
    sqlconnection.Close();
}

我不確定這是否是您的問題,但是您使用 ADO.NET 的一般模式是錯誤的 - 您不應該為每個插入創建新命令(並重復為查詢准備付費)。

相反,請執行以下操作:

  • 循環前:
    • 創建命令一次。
    • 創建適當的綁定參數。
  • 在循環:
    • 只需為綁定參數分配適當的值。
    • 並執行命令。

您還可以考慮使用細粒度的事務:嘗試在同一個事務中放置多個插入,以盡量減少為事務持久性付出的代價

您可能還想看看這篇文章

您可以嘗試以下方法之一來提高性能:

  • 將所有插入包裝在一個事務中- 有助於減少對數據庫的實際寫入。
  • 使用 WAL - Write-Ahead-Log是一種日志模式,可加速寫入並啟用並發。 (如果您的數據庫位於網絡位置,則不推薦)。
  • 同步正常- 同步模式規定了數據實際刷新到物理內存(fsync() 調用)的頻率。 這可能會在某些機器上花費時間,因此此刷新發生的頻率至關重要。 確保使用"Synchronous=NORMAL"明確打開連接,適用於大多數情況。 同步模式作為FULLNORMAL之間存在巨大差異(NORMAL 好大約 1000 倍)。

在類似帖子中查找更多詳細信息 => System.Data.SQLite 版本 1.0.74 和最新的 1.0.113 之間有何變化?

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM