有没有更快的方法从文件读取并插入到SQLite中

时间:2010-08-31 18:14:30

标签: c# database sqlite insert streamreader

下午大家。我对SQLite并不熟悉所以我没有搞乱数据库的所有设置。我比较熟悉SQL Server,Oracle甚至一些Access和mySQL。好吧,目前,我正在获取一个包含110,000多条记录的文件并逐行读取文件,解析数据并对表运行insert语句。该表取决于作为行的第一个字段的记录类型。好吧,我现在正在加载它,并且它已经运行了12分钟(正如我写的那样)并且只导入了14,000条记录。算一算,这意味着它需要1小时到15分钟到1小时30分钟。取决于我的系统其余部分当时的行为方式。因为有不同的记录类型,如果SQLite有一个选项(不确定是否有),我无法进行批量插入。这是作为后台工作者运行的。下面是拉取和解析数据的函数,以及将其插入数据库的函数。请记住,这是一个MVC格式的C#应用​​程序(当我控制它并且没有时间重组它时就像这样):

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

任何人都可以从提供的内容中看到可以改进的内容吗?是否有可以设置为更快工作的后台工作程序的设置?是否有后台工作人员的默认设置? db文件中有哪些设置可以更改(使用SQLite Expert Personal)以使插入更快?这只是我文件的大小吗?现在,当我完成这个时,它只是超过了22分钟,它完成了24,000条记录。这不是一个时间敏感的问题,所以请花费你所需的时间。感谢。

更新:另外,我想我应该在其中一个表中提到我有一个整数主键(充当身份字段)。这会有任何性能问题吗?

4 个答案:

答案 0 :(得分:5)

在整个插页周围使用一个SQLiteTransaction。因此,它会在每次插入后强制刷新文件以保持ACID兼容性。与任何DbConnectionDbTransaction一样,您使用BeginTransaction,然后在完成后Commit。整个插入将成功或失败,并且它将具有更好的性能。

答案 1 :(得分:1)

增加插入性能的首要任务是仅开始单个事务。它将为您的插入物带来数量级的加速。

有关描述此现象的常见问题条目,请参阅here

答案 2 :(得分:1)

FWIW,SQLite的命令行客户端有一个数据加载内置命令。但是,如果您阅读该SQLite客户端的C代码,您会发现它没有做任何特殊的事情。它只是逐行读取您的数据文件并在循环中执行INSERT。

其他答案建议使用显式事务,这样可以避免每行后I / O刷新的开销。我同意这个建议,肯定会有很大的好处。

您还可以停用rollback journal

PRAGMA journal_mode = OFF

或设置写入asynchronous,允许操作系统缓冲I / O:

PRAGMA synchronous = OFF

这些pragma更改应该可以节省大量的I / O开销。但是如果没有回滚日志,则ROLLBACK命令将不起作用,如果您的应用程序在正在进行的事务期间崩溃,则您的数据库可能已损坏。如果没有同步写入,操作系统故障也可能导致数据丢失。

不要试图吓唬你,但你应该知道在性能和有保证的I / O完整性之间存在权衡。我建议在大多数情况下启用安全模式,并在需要像你一样做大数据负载时短暂禁用它们 - 然后记得重新启用安全模式!

答案 3 :(得分:0)

我一直在试验并发现用C#将大型数据库导入SQLite的最快方法实际上是转储到csv然后使用命令行sqlite3.exe工具

在我的笔记本电脑上插入一个包含大约2500万行的大文件

使用.NET包装器优化插入:30分钟 (针对事务,参数化命令,日志关闭等进行了优化)

转储为CSV(2分钟),然后使用sqllite3.exe导入CSV(5分钟):7分钟