【问题标题】:Is there a faster way to read from a file and insert into SQLite有没有更快的方法从文件中读取并插入 SQLite
【发布时间】:2010-08-31 18:14:30
【问题描述】:

大家下午。我对 SQLite 不是很熟悉,所以我没有弄乱数据库的所有设置。我对 SQL Server、Oracle 甚至一些 Access 和 mySQL 都比较熟悉。好吧,目前,我正在获取一个包含 110,000 多条记录的文件,并逐行读取文件,解析数据并对表运行插入语句。该表取决于作为行的第一个字段的记录类型。好吧,我现在正在加载它,它已经运行了 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 条记录。这不是一个时间敏感的问题,所以请花点时间。谢谢。

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

【问题讨论】:

    标签: c# database sqlite insert streamreader


    【解决方案1】:

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

    【讨论】:

    • 现在我必须找出实现它的最佳方法,因为我在一个文件中有连接,并且正在将插入一个一个地创建到另一个文件中。我是否可以在我的数据库文件中创建一个 BeginTransaction 函数,在我开始读取文件之前调用它,然后在我的数据库文件中创建一个提交函数并在我完成读取文件后调用它?或者那不理想?
    • 我认为这很好。 BeginTransaction 返回一个DbTransaction,因此您可以将其存储在Database 实例字段中,然后在完成后调用Commit
    • 非常感谢。除了由于文件中的一条记录而出现语法错误之外,它几乎在大约 15 秒内插入了 87000+ 条记录。插入整个文件后,我会通知你们。
    【解决方案2】:

    提高插入性能的第一件事是只开始一个事务。这将为您的插入带来数量级的加速。

    请参阅here 了解描述此现象的常见问题解答条目。

    【讨论】:

      【解决方案3】:

      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 完整性之间存在权衡。我建议在大多数时间启用安全模式进行操作,并且仅在您需要像现在这样进行大数据加载时暂时禁用它们——然后记得重新启用安全模式!

      【讨论】:

      • 在这种情况下,看起来他可以在不使用牺牲完整性的选项的情况下获得令人满意的性能。
      • 是的,使用标准事务语义是更好的选择,这也是我 99% 的时间使用的。在极少数情况下,可能需要额外的措施,因此值得知道它们是可用的。
      • 我为罕见的情况 +1 你,因为我总是尽量避免它们,无论多么罕见。谢谢。
      【解决方案4】:

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

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

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

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

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 2012-12-23
        • 1970-01-01
        • 2014-08-31
        • 2011-12-01
        • 2018-07-02
        • 1970-01-01
        • 2020-08-06
        • 2019-10-17
        相关资源
        最近更新 更多