SQLite DB插入速度很慢

时间:2012-07-19 22:55:02

标签: c# sqlite

我正在使用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();

我做错了什么?

3 个答案:

答案 0 :(得分:2)

这是我要按顺序解决的问题。它可能会或可能不会解决问题,但它看起来并没有什么坏处(它可能只是做一些魔术):

  1. 确保数据库与更新竞争(来自其他线程,进程,甚至是计时器!)。作家将获得锁定,未封闭/过长的交易可以以不良方式进行交互。 (对于需要“30秒到2分钟”的更新,我认为获取锁定存在问题。同时确保数据库所在的介质已足够,例如本地驱动器。)

  2. 交易未被使用(??)。将事务移动到里面的定时器回调,将其附加到相应的SQLCommands,并在回调结束前处置它。 (使用using)。

  3. 并非所有SQLCommand都正确处理。处理每一个。 (using的使用简化了这一点。不要让它流过回调。)

  4. 未使用占位符。这不仅更简单易用,而且对SQLite和适配器也更加友好。

  5. (仅限示例;以下代码中可能存在错误。)

    // 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();
    }
    

答案 1 :(得分:2)

我不确定这是不是你的问题,但你使用ADO.NET的一般模式是错误的 - 你不应该为每个插入创建新的命令(并反复支付查询准备)。

相反,请执行以下操作:

  • 循环之前:
    • 创建一次命令。
    • 创建适当的绑定参数。
  • 在循环中:
    • 只需为绑定参数指定适当的值。
    • 并执行命令。

您还可以考虑使用不太细粒度的事务:尝试在同一事务中放置几个​​插入以最小化paying for transaction durability

您可能还想查看this post

答案 2 :(得分:0)

您可以尝试以下方法之一来提高性能:

  • 将所有插入都封装在一个事务中 - 有助于减少对数据库的实际写入。
  • 使用 WAL - Write-Ahead-Log 是一种日志模式,可加快写入速度并启用并发。 (如果您的数据库位于网络位置,则不推荐使用)。
  • 同步正常 - 同步模式规定了数据实际刷新到物理内存(fsync() 调用)的频率。这可能会在某些机器上花费时间,因此此刷新发生的频率至关重要。确保显式打开与 "Synchronous=NORMAL" 的连接,适用于大多数情况。同步模式为 FULLNORMAL 之间存在巨大差异(NORMAL 好大约 1000 倍)。

在类似帖子中查找更多详细信息 => What changed between System.Data.SQLite version 1.0.74 and the most recent 1.0.113?