原始问题
背景
众所周知,SQLite needs to be fine tuned实现插入速度大约为50k insert / s . 这里有很多关于缓慢插入速度和大量建议和基准的问题 .
还有claims that SQLite can handle large amounts of data,50 GB的报告没有导致正确设置出现任何问题 .
我已经按照这里和其他地方的建议来实现这些速度,我很高兴35k-45k插入/秒 . 我遇到的问题是,所有基准测试只能显示<1m记录的快速插入速度 . 我所看到的是插入速度似乎与表格大小成反比 .
问题
我的用例要求在链接表中存储500m到1b元组( [x_id, y_id, z_id]
)几年(1m行/天) . 值均为介于1和2,000,000之间的整数ID . z_id
上有一个索引 .
前10米行的性能很好,约35k插入/秒,但是当表有~20m行时,性能开始下降 . 我现在看到大约100个插入/秒 .
table 的大小不是特别大 . 对于20米行,磁盘上的大小约为500MB .
该项目是用Perl编写的 .
问题
这是SQLite中大型表的现实,还是有任何保密措施来维护> 10m行的表的高插入率?
如果可能的话,我想避免使用已知的解决方法
-
删除索引,添加记录并重新编制索引:这可以作为一种解决方法,但不能使数据库在x分钟/天内完全无法访问
-
将表分成较小的子表/文件:这将在短期内起作用,我已经对它进行了实验 . 问题是我需要能够在查询时从整个历史中检索数据,这意味着如果没有其他替代方案,最终我会尝试它 .
-
设置SQLITE_FCNTL_CHUNK_SIZE:我不想仅仅为了完成这项工作而学习它 . 我看不到使用Perl设置此参数的任何方法 .
更新
在Tim's suggestion之后,尽管SQLite声称它能够处理大型数据集,但索引导致插入时间越来越慢,我使用以下设置进行了基准比较:
-
插入行:1400万
-
提交批量大小:50,000条记录
-
cache_size
pragma:10,000 -
page_size
pragma:4,096 -
temp_store
pragma:记忆 -
journal_mode
pragma:删除 -
synchronous
pragma:关闭
在我的项目中,如下面的基准测试结果,创建了基于文件的临时表,并使用了SQLite对导入CSV数据的内置支持 . 然后将临时表附加到接收数据库,并使用 insert-select
语句插入50,000行的集合 . 因此,插入时间不会将文件反映到数据库插入时间,而是反映表到表的插入速度 . 考虑CSV导入时间会使速度降低25-50%(非常粗略估计,导入CSV数据不需要很长时间) .
显然,随着表格大小的增加,索引会导致插入速度减慢 .
从上面的数据可以清楚地看出,正确的答案可以分配给Tim's answer而不是SQLite无法处理的断言 . 显然,如果索引该数据集不是您的用例的一部分,它可以处理大型数据集 . 我一直在使用SQLite作为日志记录系统的后端,暂时不需要编入索引,所以我对我经历的减速感到非常惊讶 .
结论
如果有人发现自己想要使用SQLite存储大量数据并将其编入索引,那么using shards可能就是答案 . 我最终决定使用MD5哈希的前三个字符 z
中的一个唯一列来确定对4,096个数据库之一的分配 . 由于我的用例主要是归档,因此架构不会更改,查询也不会需要碎片遍历 . 数据库大小有限,因为极其旧的数据将被减少并最终被丢弃,因此这种分片,编译指示设置甚至一些非规范化的组合给了我一个很好的 balancer ,基于上面的基准测试,将保持插入速度至少10k插入/秒 .
5 回答
如果你的要求是找到一个特定的z_id以及与之链接的x_ids和y_ids(与快速选择一系列z_ids不同),你可以查看一个非索引的哈希表嵌套关系数据库,它可以让你立即找到通往特定z_id的方式,以获得其y_ids和x_ids - 在索引增长的过程中,在插入过程中没有索引开销和随之而来的降低性能 . 为了避免聚集桶冲突,请选择一个密钥散列算法,该算法最大限度地考虑z_id的数字,具有最大的变化(右加权) .
附:使用b树的数据库可以在例如,第一个看起来比使用线性散列的数据库更快,但随着b-tree上的性能开始降低,插入性能将保持与线性散列的水平 .
P.P.S.回答kawing-chiu的问题:这里的核心特征是这样的数据库依赖于所谓的"sparse"表,其中记录的物理位置由散列算法确定,该算法将记录键作为输入 . 这种方法允许直接搜索表中记录的位置,而不需要索引的中介 . 由于不需要遍历索引或重新 balancer 索引,因此随着表变得更加密集,插入时间保持不变 . 相比之下,使用b树,随着索引树的增长,插入时间会降低 . 具有大量并发插入的OLTP应用程序可以从这种稀疏表方法中受益 . 记录分散在整个表格中 . 分散在稀疏表的"tundra"上的记录的缺点是收集具有共同值的大量记录(例如邮政编码)可能会更慢 . 散列稀疏表方法经过优化,可以插入和检索单个记录,并检索相关记录的网络,而不是具有一些共同字段值的大型记录集 .
嵌套关系数据库是允许行的列中的元组的数据库 .
很棒的问题和非常有趣的后续行动!
我想简单地说一下:你提到将表分成较小的子表/文件并在以后附加它们不是一个选项,因为你认为你已经考虑过中途选项:将数据分成几个表但是继续使用相同的单个数据库(文件) .
我做了一个非常粗略的基准测试,以确保我的建议真的对性能产生影响 .
架构:
数据 - 200万行:
i
= 1..2,000,000md5
=i
的md5十六进制摘要每笔交易= 50,000
INSERT
s .数据库:1;表:1;指数:0
数据库文件大小:89.2 MiB .
数据库:1;表:1;指数:1(md5)
数据库文件大小:181.1 MiB .
数据库:1;表:20(每100,000个记录一个);指数:1(md5)
数据库文件大小:180.1 MiB .
如您所见,如果将数据分成几个表,则插入速度将保持不变 .
不幸的是,我在'd say this is a limitation of large tables in SQLite. It' s not designed上操作大型或大容量数据集 . 虽然我知道它可能会大大增加项目的复杂性,但您最好还是研究适合您需求的更复杂的数据库解决方案 .
从您链接的所有内容来看,表格大小与访问速度相似是直接的权衡 . 不能兼得 .
在我的项目中,我无法对数据库进行分片,因为它在不同的列上编制索引 . 为了加速插入,我在创建期间将数据库放在/ dev / shm(= linux ramdisk)上,然后将其复制到本地磁盘 . 这显然只适用于一次写入,多次读取的数据库 .
我怀疑索引的哈希值冲突导致插入速度变慢 .
当我们在一个表中有很多行,然后索引列哈希值冲突将更频繁地发生 . 这意味着Sqlite引擎需要计算哈希值两次或三次,或者甚至四次,以获得不同的哈希值 .
所以我猜这是表格有很多行时SQLite插入缓慢的根本原因 .
这一点可以解释为什么使用分片可以避免这个问题 . 谁是SQLite域名的真正专家,以确认或否认我的观点?