我正在寻找在我的应用程序中减少SQLite3内存消耗的方法。
在每次执行时,它都会创建具有以下模式的表:
(main TEXT NOT NULL PRIMARY KEY UNIQUE,count INTEGER DEFAULT 0)
之后,数据库每秒充满50k操作。只写。
当项目已经存在时,它会使用更新查询更新“count”(我认为这称为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(默认页面大小)才能达到理想的性能。
我可以做些什么来减少内存消耗?
答案 0 :(得分:11)
似乎高内存消耗可能是由于太多操作集中在一个大事务中。尝试像每1M操作一样提交较小的事务可能会有所帮助。每个事务5M操作将消耗许多内存。
但是,我们会平衡操作速度和内存使用情况。
由于较小的交易无效,PRAGMA shrink_memory
可能是一种选择。
使用sqlite3_status()
和SQLITE_STATUS_MEMORY_USED
来跟踪动态内存分配,并找到该点。
答案 1 :(得分:4)
假设一个事务中的所有操作都分布在整个表中,以便需要访问表的所有页面,工作集的大小为:
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中,插入的rowid
与sqlite3_last_insert_rowid一起阅读。
单独SELECT
和INSERT
并不比INSERT OR IGNORE
慢;在任何一种情况下,SQLite都会做同样的工作。
仅当大多数操作更新已存在的计数器时,此优化才有用。
答案 2 :(得分:3)
我会:
PRAGMA locking_mode = EXCLUSIVE;
另外,(我不知道你是否知道)PRAGMA cache_size
是在页面中,而不是在MB中。确保以PRAGMA cache_size * PRAGMA page_size
或SQLite> = 3.7.10定义目标内存,您也可以PRAGMA cache_size = -kibibytes;
。将其设置为1 M(illion)将导致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;
INSERT OR IGNORE INTO test (time) VALUES (?);
UPDATE test SET count = count + 1 WHERE time = ?;
每秒峰值~109k更新。
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会使用它 column作为表结构的(索引)键。这是隐藏的 此列上的索引(因为它不显示在SQLite_Master表中)。 不需要在列上添加另一个索引,也永远不会 用过的。此外,它将减慢INSERT,DELETE和UPDATE操作 下来。
您似乎将PK定义为NOT NULL + UNIQUE。 PK是隐含的UNIQUE。
答案 3 :(得分:1)
本着头脑风暴的精神,我会冒险回答。我还没有做过这样的测试:
Improve INSERT-per-second performance of SQLite?
我的假设是文本主键上的索引可能比两个整数列上的几个索引更加RAM密集(您需要模拟散列表)。
编辑:实际上,你甚至不需要主键:
create table foo( slot integer, myval text, occurrences int);
create index ix_foo on foo(slot); // not a unique index
整数主键(或插槽上的非唯一索引)将使您无法快速确定文本值是否已存档。因此,为了满足这一要求,您可以尝试将我建议的内容实施到另一张海报,模拟一个哈希键:
SQLite Optimization for Millions of Entries?
如果文本值存在,则可以使用散列键函数来确定文本值的存储位置。
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