2010-08-31 59 views
0

下午大家。我不熟悉SQLite,所以我沒有搞砸數據庫的所有設置。我對SQL Server,Oracle甚至一些Access和mySQL非常熟悉。那麼,目前,我正在接收一個包含110,000多條記錄的文件,並逐行讀取文件,解析數據並在表中插入一條語句。該表取決於第一行的記錄類型。那麼,我現在正在加載它,它已經運行了12分鐘(正如我寫的那樣),並且只導入了14,000條記錄。進行數學計算,這意味着它需要1小時15分鐘到1小時30分鐘之間的時間。取決於我的系統其餘部分當時的行爲。由於存在不同的記錄類型,因此如果有SQLite選項(不確定是否存在),則無法進行批量插入。這是作爲後臺工作人員運行的。下面是拉取和分析數據的函數,以及將其插入數據庫的函數。請記住,這是MVC格式的C#應用​​程序(就像當我控制了它,並沒有足夠的時間來重組它):有沒有更快的方式來讀取文件並插入到SQLite中

MainForm.cs後臺輔助功能

#region Background Worker Functions 

    #region private void InitializeBackgroundWorker() 
    /************************************************************************************* 
    *************************************************************************************/ 
    private void InitializeBackgroundWorker() 
    { 
     backgroundWorker.DoWork += 
      new DoWorkEventHandler(backgroundWorker1_DoWork); 
     backgroundWorker.RunWorkerCompleted += 
      new RunWorkerCompletedEventHandler(
     backgroundWorker1_RunWorkerCompleted); 
     backgroundWorker.ProgressChanged += 
      new ProgressChangedEventHandler(
     backgroundWorker1_ProgressChanged); 
    } 
    #endregion 

/*****************************************************************************************************************************************************************************************************/ 

    #region private void backgroundWorker1_DoWork(object sender, DoWorkEventArgs e) 
    /************************************************************************************* 
    *************************************************************************************/ 
    private void backgroundWorker1_DoWork(object sender, DoWorkEventArgs e) 
    { 
     // Get the BackgroundWorker that raised this event. 
     BackgroundWorker worker = sender as BackgroundWorker; 

     // Assign the result of the computation 
     // to the Result property of the DoWorkEventArgs 
     // object. This is will be available to the 
     // RunWorkerCompleted eventhandler. 

     //Creates a static singleton file list. Remains on the stack and can be accessed anywhere without 
     // reinstatiating 
     object[] obj = (object[])e.Argument; 
     string fileName = obj[0].ToString(); 
     DataController controller = new DataController(worker, e); 
     controller.FileName = fileName; 
     try 
     { 
      if (strProcess == "Import") 
      { 
       controller.Import(); 
      } 
      else if (strProcess == "Export") 
      { 
       controller.ExportToExcel(); 
      } 
     } 
     catch (Exception ex) 
     { 
      MessageBox.Show(ex.Message.ToString()); 
     } 
    } 
    #endregion 

/*****************************************************************************************************************************************************************************************************/ 

    #region private void backgroundWorker1_RunWorkerCompleted(object sender, RunWorkerCompletedEventArgs e) 
    /************************************************************************************* 
    *************************************************************************************/ 
    private void backgroundWorker1_RunWorkerCompleted(object sender, RunWorkerCompletedEventArgs e) 
    { 
     if (e.Error != null) 
     { 
      MessageBox.Show(e.Error.StackTrace); 
     } 
     else 
     { 
      this.toolStripStatusLabel1.Text = "Import complete"; 
      generateReport(); 
      treeViewFigure.Nodes.Clear(); 
      BuildTree(); 
      treeViewFigure.TopNode.ExpandAll(); 
      labelIPBNumber.Text = controller.IPBNumber; 
      this.Text += "IPB: " + labelIPBNumber.Text; 

      cmbIndentureLevel.Items.Clear(); 
     } 
    } 
    #endregion 

/*****************************************************************************************************************************************************************************************************/ 

    #region private void backgroundWorker1_ProgressChanged(object sender, ProgressChangedEventArgs e) 
    /************************************************************************************* 
    *************************************************************************************/ 
    private void backgroundWorker1_ProgressChanged(object sender, ProgressChangedEventArgs e) 
    { 
     string stat = e.UserState.ToString(); 
     this.toolStripStatusLabel1.Text = ""; 
     this.toolStripStatusLabel1.Text = stat; 
     this.toolStripProgressBar1.Value = e.ProgressPercentage; 
    } 
    #endregion 

#endregion 

Importer.cs導入功能

#region public void Import(string fileName) 
    /************************************************************************************* 
    *************************************************************************************/ 
    public void Import(string fileName) 
    { 
     if (!File.Exists(fileName)) 
     { 
      throw new FileNotFoundException(); 
     } 

     StreamReader read = File.OpenText(fileName); 
     List<RecordBase> List = new List<RecordBase>(); 
     DataFactory factory = DataFactory.BuildFactory(); 

     int nLines = 0; 

     while (!read.EndOfStream) 
     { 
      read.ReadLine(); 
      nLines++; 
     } 

     read.Close(); 
     read = File.OpenText(fileName); 

     factory.lstObservers = _observers; 
     factory.ClearDB(); 

     int count = 1; 

     while (!read.EndOfStream) 
     { 
      string[] fields = read.ReadLine().Split('|'); 
      List<string> lstStr = new List<string>(); 
      foreach (string str in fields) 
      { 
       lstStr.Add(str); 
      } 

      lstStr.RemoveAt(fields.Length - 1); 
      fields = lstStr.ToArray(); 

      string strValues = string.Join("','", fields); 
      strValues = "'" + strValues + "'"; 
      if (fields.Length >= 39 && fields[0] == "03") 
      { 
       factory.ImportTaggedRecord(fields[38], count); 
       int nIndex = strValues.IndexOf(fields[38]); 
       strValues = strValues.Substring(0, nIndex - 2); 
      } 

      factory.ImportIPB(strValues, fields[0], count); 

      progress.ProgressComplete = (count * 100)/nLines; 
      progress.Message = "Importing Record: " + count++.ToString(); 
      Notify(); 
     } 
    } 
    #endregion 

DataFactory.cs ImportIPB功能

#region public void ImportIPB(string strValues, string strType) 
    /************************************************************************************* 
    *************************************************************************************/ 
    public void ImportIPB(string strValues, string strType, int nPosition) 
    { 
     string strCommand = string.Empty; 

     switch (strType) 
     { 
      case "01": 
       strCommand = Queries.strIPBInsert; 
       break; 
      case "02": 
       strCommand = Queries.strFigureInsert; 
       break; 
      case "03": 
       strCommand = Queries.strPartInsert; 
       break; 
     } 

     ExecuteNonQuery(strCommand + strValues + ", " + nPosition.ToString() + ")"); 
    } 
    #endregion 

Database.cs ExecuteNonQuery方法

#region public void ExecuteNonQuery(string strSQL) 
    /************************************************************************************* 
    *************************************************************************************/ 
    public void ExecuteNonQuery(string strSQL) 
    { 
     DbCommand dbCommand = _dbConnection.CreateCommand(); 
     dbCommand.CommandText = strSQL; 
     dbCommand.Prepare(); 
     dbCommand.ExecuteNonQuery(); 
    } 
    #endregion 

任何人都可以看到提供的任何東西可以改進嗎?是否有設置可以設置爲更快工作的後臺工作人員?是否有後臺工作人員的默認設置?數據庫文件上有哪些可以更改的設置(使用SQLite專用個人版)以使插入更快?它只是我的文件的大小?現在,當我完成這個時候,它剛剛過去了22分鐘,它完成了24,000條記錄。這不是一個時間敏感的問題,所以你需要時間。謝謝。

更新:另外,我想我應該提到在其中一個表中我有一個整數主鍵(作爲標識字段)。可以有任何性能問題嗎?

回答

5

在整個插入物周圍使用單個SQLiteTransaction。因爲它會在每次插入後強制沖洗文件以保持ACID合規性。與任何DbConnectionDbTransaction一樣,您使用BeginTransaction,那麼當您完成時,Commit。整個插入將成功或失敗,並且它將具有更好的性能。

+0

好的,我會研究一下。 – XstreamINsanity 2010-08-31 18:21:34

+0

現在我必須找出實現它的最佳方式,因爲我在一個文件中建立了連接,並且將插入逐一創建到另一個文件中。我有可能在我的數據庫文件中創建一個BeginTransaction函數,在我開始讀取文件之前調用它,然後在我的數據庫文件中創建一個Commit函數並在讀完文件後調用它?或者那會不理想? – XstreamINsanity 2010-08-31 18:30:24

+0

我認爲這很好。 'BeginTransaction'返回'DbTransaction',所以你可以將它存儲在'Database'實例字段中,然後在完成時調用'Commit'。 – 2010-08-31 18:36:25

1

增加插入性能的第一件事是隻開始一個事務。它會導致插入的數量級提高。

請參閱here瞭解描述此現象的FAQ條目。

1

FWIW,SQLite的命令行客戶端有一個數據加載內置命令。但是如果你閱讀了SQLite客戶端的C代碼,你會發現它沒有做任何特別的事情。它只是逐行讀取數據文件並在循環中執行INSERT。

其他答案建議使用顯式事務,這樣可以避免每行之後的I/O刷新開銷。我同意這個建議,它肯定會有很大的好處。

您也可以禁用rollback journal

PRAGMA journal_mode = OFF 

也可以設置寫入asynchronous,允許操作系統緩衝I/O:

PRAGMA synchronous = OFF 

這些編譯變化應該保存顯著I/O高架。但是沒有回滾日誌,ROLLBACK命令將不起作用,並且如果您的應用程序在正在進行的事務中崩潰,那麼您的數據庫可能已損壞。沒有同步寫入操作系統故障也可能導致數據丟失。

沒有試圖嚇倒你,但你應該知道在性能和保證I/O完整性之間有一個權衡。我建議大多數情況下使用安全模式進行操作,並且在您需要像執行大數據操作時那樣暫時禁用它們,然後記住重新啓用安全模式!

+0

在這種情況下,看起來他可以在不使用犧牲完整性的選項的情況下獲得滿意的性能。 – 2010-08-31 18:43:41

+1

是的,使用標準事務語義是更好的選擇,這就是我99%的時間使用的。在極少數情況下,可能需要額外措施,因此值得了解它們是否可用。 – 2010-08-31 18:47:06

+0

而且,我爲你們提供了難得的案例,因爲我總是試圖避開它們,無論多麼罕見。謝謝。 – XstreamINsanity 2010-08-31 18:51:37

0

我一直在嘗試,發現導入大型數據庫SQLite的成與C#的最快方法其實是轉儲到csv然後使用命令行工具sqlite3.exe

含約25大文件百萬行插入我的筆記本電腦

優化插入與.NET包裝:30分鐘

轉儲到CSV(2分鐘),然後導入CSV與sqllite3(與交易,參數化的命令,關閉雜誌等優化) .exe(5分鐘):7分鐘

相關問題