【问题标题】:SQLite Insert very slow?SQLite 插入很慢?
【发布时间】:2017-01-07 18:15:13
【问题描述】:

我最近阅读了有关 SQLite 的文章,并想尝试一下。当我插入一条记录时,它执行得很好。但是当我插入一百个时,它需要五秒钟,并且随着记录数的增加,时间也会增加。有什么问题?我正在使用 SQLite Wrapper (system.data.SQlite):

dbcon = new SQLiteConnection(connectionString);
dbcon.Open();

//---INSIDE LOOP

 SQLiteCommand sqlComm = new SQLiteCommand(sqlQuery, dbcon);

 nRowUpdatedCount = sqlComm.ExecuteNonQuery(); 

//---END LOOP

dbcon.close();

【问题讨论】:

    标签: c# sqlite system.data.sqlite


    【解决方案1】:

    BEGIN \ END 语句包装在您的批量插入周围。 Sqlite 针对事务进行了优化。

    dbcon = new SQLiteConnection(connectionString);
    dbcon.Open();
    
    SQLiteCommand sqlComm;
    sqlComm = new SQLiteCommand("begin", dbcon);
    sqlComm.ExecuteNonQuery(); 
    //---INSIDE LOOP
    
     sqlComm = new SQLiteCommand(sqlQuery, dbcon);
    
     nRowUpdatedCount = sqlComm.ExecuteNonQuery(); 
    
    //---END LOOP
    sqlComm = new SQLiteCommand("end", dbcon);
    sqlComm.ExecuteNonQuery(); 
    dbcon.close();
    

    【讨论】:

    • +1 SQLite FAQ, #19 中提到了这一点 - 当您在没有开始/结束的情况下执行此操作时,SQLite 将为每个插入创建一个事务。
    • 为什么你使用 3 ExecuteNonQuery 来完成这项工作
    • 3 ExecuteNonQuery 是因为 BEGIN 有 1 个,每个 INSERT 有 1 个(或更多),END 有 1 个。除非您将所有 SQL 语句添加到一个字符串(以分号分隔),否则您需要多次调用 ExecuteNonQuery
    • 我在我的代码中尝试了这个,它只是说“没有事务处于活动状态”,知道为什么会发生这种情况吗?
    • @Brian 确保每个连接都有一个 BEGIN 和一个 END。也不要嵌套事务。每个连接只有一个。如果你需要嵌套,那么要么打开另一个连接,要么使用SAVEPOINTS
    【解决方案2】:

    尝试将所有插入(也称为批量插入)包装到单个 transaction

    string insertString = "INSERT INTO [TableName] ([ColumnName]) Values (@value)";
    
    SQLiteCommand command = new SQLiteCommand();
    command.Parameters.AddWithValue("@value", value);
    command.CommandText = insertString;
    command.Connection = dbConnection;
    SQLiteTransaction transaction = dbConnection.BeginTransaction();
    try
    {
        //---INSIDE LOOP
        SQLiteCommand sqlComm = new SQLiteCommand(sqlQuery, dbcon);
        nRowUpdatedCount = sqlComm.ExecuteNonQuery(); 
        //---END LOOP
        
        transaction.Commit();
        return true;
    }
    catch (SQLiteException ex)
    {
        transaction.Rollback();
    }
    

    默认为SQLite wraps every inserts in a transaction,这会减慢进程:

    INSERT 真的很慢 - 我每秒只能执行几十次 INSERT

    实际上,SQLite 在普通台式计算机上每秒可以轻松执行 50,000 或更多的 INSERT 语句。但它每秒只会执行几十个事务。

    事务速度受磁盘驱动器速度的限制,因为(默认情况下)SQLite 实际上会等到数据真正安全地存储在磁盘表面上之后才会完成事务。这样,如果您突然断电或操作系统崩溃,您的数据仍然是安全的。有关详细信息,请阅读 SQLite 中的原子提交。

    默认情况下,每个 INSERT 语句都是它自己的事务。但是如果你用 BEGIN...COMMIT 包围多个 INSERT 语句,那么所有的插入都会被分组到一个事务中。提交事务所需的时间在所有包含的插入语句中分摊,因此每个插入语句的时间大大减少。

    【讨论】:

      【解决方案3】:

      请参阅 ADO.NET 帮助文件 SQLite.NET.chm 中的“优化 SQL 查询”。该页面的代码:

      using (SQLiteTransaction mytransaction = myconnection.BeginTransaction())
      {
        using (SQLiteCommand mycommand = new SQLiteCommand(myconnection))
        {
          SQLiteParameter myparam = new SQLiteParameter();
          int n;
      
          mycommand.CommandText = "INSERT INTO [MyTable] ([MyId]) VALUES(?)";
          mycommand.Parameters.Add(myparam);
      
          for (n = 0; n < 100000; n ++)
          {
            myparam.Value = n + 1;
            mycommand.ExecuteNonQuery();
          }
        }
        mytransaction.Commit();
      }
      

      【讨论】:

        【解决方案4】:

        我到处都读到,创建事务是减缓 SQLite 写入速度的解决方案,但重写代码并将所有 SQLite 写入包装在事务中可能会很长而且很痛苦。

        我找到了一个更简单、安全且非常有效的方法:我启用(默认禁用)SQLite 3.7.0 优化:Write-Ahead-Log (WAL)。 文档说它适用于所有 unix(即 Linux 和 OSX)和 Windows 系统。

        如何?初始化 SQLite 连接后,只需运行以下命令:

        PRAGMA journal_mode = WAL
        PRAGMA synchronous = NORMAL
        

        我的代码现在运行速度提高了约 600%:我的测试套件现在可以在 38 秒内运行,而不是 4 分钟 :)

        【讨论】:

        • 谢谢!顺便说一句,您可能可以使用内存中的 sqlite 模式进行测试。
        • 如果您有多个线程保存数据并且不想进行大量代码更改以便将所有插入/更新分组到 1 个单独调用中,这可能是最佳解决方案
        • 你拯救了我的一天:))谢谢!快 1000 倍!
        • 我看到 MacOS 和 Ubuntu 中相同的插入密集型程序之间的性能差异超过 10 倍。这些行使 Ubuntu 像 MacOS 一样运行。谢谢。
        猜你喜欢
        • 2015-01-05
        • 1970-01-01
        • 2013-08-03
        • 1970-01-01
        • 2013-05-12
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        相关资源
        最近更新 更多