【问题标题】:SQLite: efficient way to drop lots of rowsSQLite:删除大量行的有效方法
【发布时间】:2013-10-23 00:00:30
【问题描述】:

SQlite,Android,真实故事。我有一张表,用作缓存:

CREATE TABLE cache(key TEXT, ts TIMESTAMP, size INTEGER, data BLOB);
CREATE UNIQUE INDEX by_key ON cache(key);
CREATE INDEX by_ts ON cache(ts);

在应用程序生命周期中,我会填充缓存,有时我想将其清除并删除 N 记录。通常,此表将包含 ~25000 个 ~100-500Kb 的 blob,数据库中的总 blob 大小为 600-800Mb,但现在我测试 ~2000 个约为 60Mb(以下数字适用于这种情况)。 Clear 会删除 90% 的缓存条目。

我尝试了不同的方法,这里简要说明:

[1] 最糟糕和最简单的。首先选择,而不是一一删除,行走光标。太慢了。

[2] 让 SQLite 使用查询来完成(删除其中包含完全 N 字节的 blob):

DELETE FROM blobs WHERE
  ROWID IN (SELECT ROWID FROM blobs WHERE 
             (SELECT SUM(size) FROM blobs AS _ WHERE ts <= blobs.ts) <= N);

这更快,但仍然非常慢:~15 秒。似乎它也具有二次复杂度。

[3] 选择删除位置周围的行(使用平均 blob 大小进行计算)并使用简单的WHERE 子句删除:

-- Find row after which to delete, let it's time stamp is T0:
SELECT ts FROM cache ORDER BY ts LIMIT 1 OFFSET count;
-- Delete
DELETE FROM cache WHERE ts < T0;

这要好得多,但需要大约 7 秒。

[4] 创建新表,复制我需要保存并删除旧表。请注意,我在复制所有这些内容后在新表中创建索引:

  -- Insert only rows I want leave
  INSERT INTO temp(key, ts, size, data) SELECT key, ts, size, data 
    FROM cache ORDER BY ts LIMIT count;
  -- Drop table and indices.
  DROP INDEX by_key;
  DROP INDEX by_ts;
  DROP TABLE cache;
  -- Rename temp table and create indices...

对于 6Mb 的 blob,复制需要大约 300 毫秒。但是DROP TABLE 大约是 8 秒。

请注意,在所有情况下,我都会使用VACUUM,这需要大约 1 秒。我怎样才能使它快速?为什么DROP TABLE 和删除这么慢?我认为这可能是因为索引:当我在 DELETE 之前删除关键索引时,它的工作速度更快。如何让 SQLite 快速删除?

【问题讨论】:

  • 如果你加入一个线程怎么办。那么它不会影响用户..我想不理想。
  • 注意:在插入之前或之后创建索引所花费的时间大致相同。它仍然需要索引。
  • 其实,在交易中围绕DELETE很有帮助!

标签: android sql sqlite


【解决方案1】:

您正在使用“大”数据的数据库 - 即每个 blob 使用多个页面。

在接近最佳性能的某个时刻,您将达到无法改进的极限。

检查您的所有选择,我看到了不同的行为,而不仅仅是不同的算法。

[1] 只要您使用事务,这应该不会太慢。您一次需要两个操作,查询(获取 blob 大小)和删除。

[2] 这是一个很好的方法。由于两个查询和一个删除,都在一个命令中,因此 SQLite 引擎将进行优化。

[3] 这是与以往不同的行为。与DELETE FROM cache WHERE ts &lt; (SELECT ts FROM cache ORDER BY ts LIMIT 1 OFFSET count) 相同。查询比以前便宜,但我敢打赌,删除的行数远远少于前一个!查询/删除的昂贵部分将被删除!查询优化很重要,但删除总是会变慢。

[4] 这是一个非常糟糕的方法!!!将所有数据复制到新表(可能是另一个数据库)将非常昂贵。我只从中获得了一个好处:您可以将数据复制到新数据库并避免使用VACUUM,因为新数据库是从基础构建的并且很干净。

关于VACUUM... 最糟糕的是DELETEVACUUM。真空不应该在数据库中经常使用。我知道这个算法应该“清理”您的数据库,但清理不应该是一项频繁的操作 - 数据库已针对选择/插入/删除/更新进行了优化 - 不是为了将所有数据保持在最小大小。

我的选择是根据预定义的标准使用DELETE ... IN (SELECT ...) 单个操作。 VACUUM 不会被使用,至少不会经常使用。一个不错的选择是监控数据库大小 - 当这个大小超过限制时,运行假定的昂贵清理来修剪数据库。

最后,当使用多个命令时,永远不要忘记使用事务!

【讨论】:

  • 只有多个变化才需要多条语句的事务;只读事务没有很大的开销。
【解决方案2】:

显然,慢的不是找到要删除的记录,而是实际删除本身。

检查 PRAGMA secure_delete 是否在您的 Android 的 SQLite 中默认设置。 你应该禁用它,只是为了确定。

你不需要运行VACUUM; SQLite 自动重用已释放的页面。 仅当您确实知道数据库在未来不会再次增长时,您才需要VACUUM

【讨论】:

  • 这对我来说将一个很大的 DELETE 从几秒变为毫秒。
【解决方案3】:

您有两种选择来提高性能,尤其是第一种:

1) 像这样使用事务:

DbTransaction trans = conn.BeginTransaction(); // <-------------------
try 
{
   Any code to delete the items
}
catch
{
    trans.Rollback(); // <-------------------
    throw; // <-------------------
}

2) 否则,假设项是连续的,则

  • a) 获取第一项的ID;

  • b) 获取要删除的项目总数

  • c) 使用这样的命令:

    DELETE FROM blobs WHERE ID &gt; fistId LIMIT count;

祝你好运。

【讨论】:

  • Android 没有DbTransaction 对象。
  • 对不起,把它和.NET环境混为一谈是我的错。对于 Android,我会使用:SQLiteDatabase.beginTransaction() 和 SQLITEDatabase.endTransaction()。关键是demi尝试执行多个SQL命令,而SQLite总是将每个命令包装成一个单独的事务,从而大大滞后了性能。
猜你喜欢
  • 2011-07-22
  • 1970-01-01
  • 1970-01-01
  • 2020-12-28
  • 1970-01-01
  • 1970-01-01
  • 2011-06-04
  • 1970-01-01
相关资源
最近更新 更多