原始问题
背景
众所周知,需要对 SQLite 进行微调以实现 50k 插入/秒的插入速度。这里有很多关于插入速度慢的问题以及大量的建议和基准。
还有人声称 SQLite 可以处理大量数据,50+ GB 的报告不会导致正确设置出现任何问题。
我已按照此处和其他地方的建议来实现这些速度,我对 35k-45k 插入/秒感到满意。我遇到的问题是,所有基准测试都只展示了 < 1m 记录的快速插入速度。我看到的是插入速度似乎与表大小成反比。
问题
我的用例需要在链接表中存储 500m 到 1b[x_id, y_id, z_id]
个元组 ( ) 几年(1m 行/天)。这些值都是介于 1 和 2,000,000 之间的整数 ID。上只有一个索引z_id
。
前 10m 行的性能非常好,大约 35k 插入/秒,但是当表有大约 20m 行时,性能开始受到影响。我现在看到大约 100 次插入/秒。
桌子的大小不是特别大。20m 行,磁盘大小约为 500MB。
该项目是用 Perl 编写的。
问题
这是 SQLite 中大型表的现实,还是有任何秘诀可以为超过 10m 行的表保持高插入率?
如果可能的话,我想避免的已知解决方法
- 删除索引,添加记录,然后重新索引:这作为一种解决方法很好,但当数据库在更新期间仍然需要可用时不起作用。使数据库在x分钟/天内完全无法访问是行不通的
- 将表格分成更小的子表格/文件:这将在短期内起作用,我已经尝试过。问题是我需要能够在查询时从整个历史记录中检索数据,这意味着最终我将达到 62 个表附件的限制。在临时表中附加、收集结果以及每次请求分离数百次似乎是很多工作和开销,但如果没有其他选择,我会尝试的。
- Set
SQLITE_FCNTL_CHUNK_SIZE
: 我不知道 C (?!),所以我宁愿不学习它只是为了完成它。不过,我看不到任何使用 Perl 设置此参数的方法。
更新
根据Tim 的建议,尽管 SQLite 声称它能够处理大型数据集,但索引导致插入时间越来越慢,我对以下设置进行了基准比较:
- 插入行数:1400 万
- 提交批量大小:50,000 条记录
cache_size
杂注:10,000page_size
编译指示:4,096temp_store
语用:记忆journal_mode
杂注:删除synchronous
杂注:关闭
在我的项目中,如下面的基准测试结果所示,创建了一个基于文件的临时表,并使用了 SQLite 对导入 CSV 数据的内置支持。然后将临时表附加到接收数据库,并使用
insert-select
语句插入 50,000 行的集合。因此,插入时间并不反映
文件到数据库的插入时间,而是表到表的插入速度。考虑到 CSV 导入时间会降低 25-50% 的速度(一个非常粗略的估计,导入 CSV 数据不需要很长时间)。
显然,随着表大小的增加,拥有索引会导致插入速度变慢。
从上面的数据中可以清楚地看出,正确的答案可以分配给Tim 的答案,而不是 SQLite 无法处理的断言。显然,如果索引该数据集不是您的用例的一部分,它可以处理大型数据集。为此,我一直在使用 SQLite 作为日志系统的后端,现在它不需要被索引,所以我对我所经历的减速感到非常惊讶。
结论
如果有人发现自己想要使用 SQLite 存储大量数据并对其进行索引,那么使用分片可能是答案。我最终决定使用 MD5 哈希的前三个字符作为唯一列z
来确定分配给 4,096 个数据库中的一个。由于我的用例本质上主要是存档,因此模式不会改变,查询也永远不需要分片遍历。数据库大小是有限制的,因为非常旧的数据将被减少并最终被丢弃,所以这种分片、杂注设置甚至一些反规范化的组合给了我一个很好的平衡,基于上面的基准测试,保持插入速度至少 10k 次插入/秒。