【问题标题】:SQLite DB Insert Very SlowSQLite 数据库插入非常慢
【发布时间】:2020-12-23 13:21:08
【问题描述】:

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

我做错了什么?

【问题讨论】:

  • 您是否尝试过直接对数据库运行插入命令(而不是通过 C#)?您要插入的表格中有多少项目?它的架构是什么样的(字段 + 键 + 索引)?这张桌子上有触发器吗?
  • 好的,我已插入标准 Chrome 历史数据库。我正在插入大约 2400 条记录。
  • @StriplingWarrior 向您提出了具体问题(其中 4 个),您在回复中均未提及。 “一个标准的 Chrome 历史数据库”没有回答“你试过直接针对数据库运行吗?”,“表中有多少项目?” (这与“您要插入多少项目”不同),或者向不熟悉“标准 Chrome 历史数据库”架构但熟悉运行 SQLite 插入语句的用户提供任何信息。
  • 对不起。我不能直接尝试,因为这些值是批量的,来自另一个数据库。在我插入数据库之前,我清除了表 url 和访问,所以这将是 0 值。这是我找到的用于创建 urls 表的代码:CREATE TABLE urls(id INTEGER PRIMARY KEY,url LONGVARCHAR,title LONGVARCHAR,visit_count INTEGER DEFAULT 0 NOT NULL, typed_count INTEGER DEFAULT 0 NOT NULL,last_visit_time INTEGER NOT NULL,hidden INTEGER DEFAULT 0 NOT NULL, favicon_id INTEGER DEFAULT 0 NOT NULL); CREATE INDEX urls_favicon_id_INDEX ON urls (favicon_id); CREATE INDEX urls_url_index ON urls (url);
  • 这是访问表的代码:CREATE TABLE visits(id INTEGER PRIMARY KEY,url INTEGER NOT NULL,visit_time INTEGER NOT NULL,from_visit INTEGER,transition INTEGER DEFAULT 0 NOT NULL,segment_id INTEGER,is_indexed BOOLEAN);CREATE INDEX visits_from_index ON visits (from_visit); CREATE INDEX visits_time_index ON visits (visit_time); CREATE INDEX visits_url_index ON visits (url);

标签: c# sqlite


【解决方案1】:

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

  1. 确保数据库没有与更新竞争(来自另一个线程、进程甚至计时器!)。写入者将获得锁,并且未关闭/运行时间过长的事务可能会以不良方式进行交互。 (对于需要“30 秒到 2 分钟”的更新,我想在获取锁时会出现问题。还要确保数据库所在的媒体足够,例如本地驱动器。)

  2. 事务未被使用 (??)。将事务移到计时器回调中,将其附加到适当的 SQLCommands,并在回调结束之前将其处理掉。 (使用using)。

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

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

(仅作为示例;以下代码可能存在错误。)

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

【讨论】:

  • 你能举个例子吗?
  • @CCInc 更新为“我刚刚输入”示例。
  • @CCInc 另外,验证 other 数据库用于确保长时间运行的事务不会导致 [minor] 死锁..
  • 效果很好,除了我收到此错误:The event 'System.Data.SQLite.SQLiteConnection.RollBack' can only appear on the left hand side of += or -= 提交也是如此
  • 奇怪,应该是这个Rollbackmethod)......该错误消息听起来像是在尝试附加到事件
【解决方案2】:

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

相反,请执行以下操作:

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

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

您可能还想看看this post

【讨论】:

    【解决方案3】:

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

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

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

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2020-04-11
      • 2014-08-10
      • 2012-10-12
      • 1970-01-01
      相关资源
      最近更新 更多