18

我正在寻找在我的应用程序中减少 SQLite3 内存消耗的方法。

在每次执行时,它都会创建一个具有以下架构的表:

(main TEXT NOT NULL PRIMARY KEY UNIQUE, count INTEGER DEFAULT 0)

之后,数据库每秒填充 50k 次操作。只写。

当一个项目已经存在时,它会使用更新查询更新“计数”(我认为这称为 UPSERT)。这些是我的疑问:

INSERT OR IGNORE INTO table (main) VALUES (@SEQ);
UPDATE tables SET count=count+1 WHERE main = @SEQ;

这样,每个事务有 500 万次操作,我可以非常快速地写入数据库。

我真的不关心这个问题的磁盘空间,但我的 RAM 空间非常有限。因此,我不能浪费太多的内存。

sqlite3_user_memory() 通知它的内存消耗在执行期间增长到几乎 3GB。如果我通过 sqlite3_soft_heap_limit64() 将其限制为 2GB,那么当达到 2GB 时,数据库操作的性能几乎下降到零。

我必须将缓存大小提高到 1M(默认页面大小)才能达到理想的性能。

我可以做些什么来减少内存消耗?

4

4 回答 4

16

看起来内存消耗高可能是因为太多的操作集中在一个大事务中。尝试提交较小的事务(如每 1M 操作)可能会有所帮助。每个事务 5M 操作消耗太多内存。

但是,我们会平衡操作速度和内存使用量。

如果较小的交易不是一种选择,PRAGMA shrink_memory可能是一种选择。

使用sqlite3_status()withSQLITE_STATUS_MEMORY_USED跟踪动态内存分配并定位瓶颈。

于 2013-03-09T00:24:57.633 回答
9

我会:

  • 准备陈述(如果你还没有这样做)
  • 降低每笔交易的 INSERT 数量(10 秒 = 500,000 听起来合适)
  • PRAGMA locking_mode = EXCLUSIVE;如果可以,请使用

另外,(我不确定你是否知道)PRAGMA cache_size是在页面中,而不是在 MB 中。确保在 as 或 SQLite >= 3.7.10 中定义目标内存,PRAGMA cache_size * PRAGMA page_size您也可以这样做PRAGMA cache_size = -kibibytes;。将其设置为 1 M(百万)将导致 1 或 2 GB。

我很好奇如何cache_size 在 INSERT 中提供帮助......

PRAGMA temp_store = FILE;如果有所不同,您也可以尝试进行基准测试。

当然,只要您的数据库没有被写入:

  • PRAGMA shrink_memory;
  • VACUUM;

根据您对数据库的操作,这些也可能有所帮助:

  • PRAGMA auto_vacuum = 1|2;
  • PRAGMA secure_delete = ON;

我使用以下编译指示进行了一些测试:

busy_timeout=0;
cache_size=8192;
encoding="UTF-8";
foreign_keys=ON;
journal_mode=WAL;
legacy_file_format=OFF;
synchronous=NORMAL;
temp_store=MEMORY;

测试#1:

INSERT OR IGNORE INTO test (time) VALUES (?);
UPDATE test SET count = count + 1 WHERE time = ?;

每秒达到峰值约 109k 更新。

测试#2:

REPLACE INTO test (time, count) VALUES
(?, coalesce((SELECT count FROM test WHERE time = ? LIMIT 1) + 1, 1));

达到每秒约 120k 更新的峰值。


我也尝试过PRAGMA temp_store = FILE;,更新下降了约 1-2k 每秒。


对于事务中的 7M 更新,journal_mode=WAL它比其他所有更新都慢。


我用 35,839,987 条记录填充了一个数据库,现在我的设置每批 65521 更新需要近 4 秒 - 但是,它甚至没有达到 16 MB 的内存消耗。


好的,这是另一个:

INTEGER PRIMARY KEY 列上的索引(不要这样做)

当您使用 INTEGER PRIMARY KEY 创建列时,SQLite 使用该列作为表结构的键(索引)。这是此列上的隐藏索引(因为它未显示在 SQLite_Master 表中)。不需要在列上添加另一个索引,也永远不会使用。此外,它还会降低 INSERT、DELETE 和 UPDATE 操作的速度。

您似乎将您的 PK 定义为 NOT NULL + UNIQUE。PK 隐含地是唯一的。

于 2013-05-12T20:47:08.043 回答
4

假设一个事务中的所有操作都分布在整个表中,因此需要访问表的所有页,那么工作集的大小为:

  • 大约 1 GB 用于表的数据,加上
  • 列上的索引大约 1 GB main,加上
  • 大约 1 GB 用于事务中更改的所有表页面的原始数据(可能全部)。

count您可以尝试通过将列移动到单独的表中来减少每个操作更改的数据量:

CREATE TABLE main_lookup(main TEXT NOT NULL UNIQUE, rowid INTEGER PRIMARY KEY);
CREATE TABLE counters(rowid INTEGER PRIMARY KEY, count INTEGER DEFAULT 0);

然后,对于每个操作:

SELECT rowid FROM main_lookup WHERE main = @SEQ;
if not exists:
    INSERT INTO main_lookup(main) VALUES(@SEQ);
    --read the inserted rowid
    INSERT INTO counters VALUES(@rowid, 0);
UPDATE counters SET count=count+1 WHERE rowid = @rowid;

在 C 中,使用sqlite3_last_insert_rowidrowid读取插入的内容。

做一个单独的SELECT并且INSERT不比INSERT OR IGNORE; SQLite 在这两种情况下都做同样的工作。

仅当大多数操作更新已存在的计数器时,此优化才有用。

于 2013-03-06T21:13:24.130 回答
2

本着头脑风暴的精神,我将冒险回答。我没有像这个家伙做过任何测试:

提高 SQLite 的每秒插入性能?

我的假设是文本主键上的索引可能比两个整数列上的几个索引更占用 RAM(您需要模拟哈希表)。

编辑:实际上,您甚至不需要主键:

      create table foo( slot integer, myval text, occurrences int);
      create index ix_foo on foo(slot);  // not a unique index

整数主键(或插槽上的非唯一索引)将使您无法快速确定您的文本值是否已在文件中。因此,为了满足该要求,您可以尝试实现我向另一张海报提出的建议,模拟散列键:

数百万条目的 SQLite 优化?

散列键函数将允许您确定文本值是否存在时的存储位置。

http://www.cs.princeton.edu/courses/archive/fall08/cos521/hash.pdf http://www.fearme.com/misc/alg/node28.html http://cs.mwsu.edu/ ~griffin/courses/2133/downloads/Spring11/p677-pearson.pdf

于 2013-03-15T11:41:48.597 回答