关于数据库:SQLite插入速度随着索引数量的增加而减慢

SQLite insert speed slows as number of records increases due to an index

原始问题背景

众所周知,需要对sqlite进行微调,以达到50k个插入/秒的插入速度。这里有许多关于插入速度慢以及大量建议和基准的问题。

也有人声称,sqlite可以处理大量数据,50+GB的报告不会对正确的设置造成任何问题。

为了达到这些速度,我遵循了这里和其他地方的建议,我对35K-45K插入/秒很满意。我遇到的问题是,所有的基准测试都只显示插入速度快且记录小于1米。我看到的是插入速度似乎与表的大小成反比。

问题

我的用例需要在一个链接表中存储500m到1b个元组([x_id, y_id, z_id]个),保存时间为几年(一百万行/天)。这些值都是介于1和2000000之间的整数ID。z_id上只有一个索引。

前10M行的性能非常好,~35K个插入/s,但到表有~20M行时,性能开始下降。我现在看到大约100个插入/秒。

桌子的大小不是特别大。有20米的行,磁盘上的大小约为500兆字节。

这个项目是用Perl编写的。

问题

这是sqlite中大型表的现实情况,还是有什么秘密可以为行数大于10米的表保持较高的插入率?

如果可能,我想避免的已知解决方法

  • 删除索引,添加记录,然后重新索引:这是一个很好的解决方法,但是当数据库在更新过程中仍然需要可用时不起作用。使数据库在X分钟/天内完全不可访问是行不通的。
  • 将表分成更小的子表/文件:这在短期内有效,我已经对此进行了试验。问题是,当查询时,我需要能够从整个历史记录中检索数据,这意味着最终我将达到62个表的附件限制。在一个临时表中附加、收集结果,以及每个请求分离数百次似乎是一项繁重的工作和开销,但是如果没有其他选择,我将尝试它。
  • 设置:我不知道C(?!)所以我不想为了完成这件事而学习它。不过,我看不出使用Perl设置这个参数的任何方法。

更新

按照蒂姆的建议,一个指数正在导致尽管sqlite声称它具有在处理大型数据集时,我对以下内容进行了基准比较设置:

  • 插入行:1400万
  • 提交批大小:50000条记录
  • cache_size杂注:10000
  • page_size杂注:4096
  • temp_store杂注:内存
  • journal_mode杂注:删除
  • synchronous杂注:关

在我的项目中,和下面的基准测试结果一样,创建了一个基于文件的临时表,并且SQLite的内置支持用于导入csv数据。然后附加临时表在接收数据库中插入50000行集合insert-select报表。因此,插入时间不反映文件到数据库的插入时间,而不是表到表的插入时间速度。考虑到csv导入时间会降低速度25-50%(非常粗略的估计,进口CSV数据)。

显然,索引会导致插入速度随着表大小的增加而减慢。

Plot of SQLite insert speed and table size

从上面的数据可以很清楚地看出,正确的答案可以分配给Tim的答案,而不是断言sqlite不能处理它。显然,如果索引数据集不是用例的一部分,它可以处理大型数据集。我使用sqlite只是为了这个目的,作为日志系统的后端,有一段时间了,它不需要索引,所以我对我所经历的放缓感到非常惊讶。

结论

如果有人发现自己想要使用sqlite存储大量数据并对其进行索引,那么使用shard可能是解决方法。我最终决定使用MD5哈希的前三个字符(z中的唯一列)来确定对4096个数据库之一的分配。因为我的用例本质上主要是存档的,所以模式不会改变,查询也不会需要分片行走。由于极旧的数据将被减少并最终丢弃,所以数据库大小有一个限制,因此这种切分、pragma设置甚至一些非规范化的组合会给我一个很好的平衡,根据上面的基准测试,插入速度将保持在至少10 K次插入/秒。


如果您的要求是找到一个特定的z_id和与之链接的x_id和y_id(不同于快速选择z_id的范围),您可以查看一个无索引哈希表嵌套的关系数据库,该数据库允许您立即找到一个特定z_id的方法,以便获得其y_id和x_id——而不需要索引开销和t随着索引的增长,插入时的性能也随之下降。为了避免聚集,也就是说桶碰撞,选择一个关键哈希算法,将最大的权重放在具有最大变化(右加权)的z_id的数字上。

P.S.例如,使用B树的数据库最初可能比使用线性散列的数据库看起来更快,但随着B树性能开始下降,插入性能将与线性散列保持水平。

P.P.S.回答Kawing Chiu的问题:与此相关的核心特性是,这样的数据库依赖所谓的"稀疏"表,其中记录的物理位置由一个哈希算法确定,该算法将记录键作为输入。这种方法允许直接搜索记录在表中的位置,而不需要索引的中介。由于不需要遍历索引或重新平衡索引,因此插入时间保持不变,因为表的填充更加密集。相比之下,对于B树,插入时间随着索引树的增长而降低。具有大量并发插入的OLTP应用程序可以从这种稀疏表方法中获益。这些记录散布在整个桌子上。分散在稀疏表的"苔原"上的记录的缺点是,收集具有共同值的大型记录集(如邮政编码)可能会比较慢。哈希稀疏表方法被优化为插入和检索单个记录,以及检索相关记录的网络,而不是具有共同字段值的大型记录集。

嵌套的关系数据库允许行的列中有元组。


很好的问题和非常有趣的后续行动!

我只想简单地说一句:您提到过,将表拆分为较小的子表/文件,稍后再附加它们不是一个选项,因为您将很快达到62个附加数据库的硬限制。虽然这是完全正确的,但我认为您没有考虑过中间的选择:将数据分为多个表,但继续使用相同的单个数据库(文件)。

我做了一个非常粗略的基准测试,只是为了确保我的建议确实会对绩效产生影响。

Schema:

1
2
3
4
5
CREATE TABLE IF NOT EXISTS"test_$i"
(
   "i" integer NOT NULL,
   "md5" text(32) NOT NULL
);

数据-200万行:

  • i=1..2000000
  • md5=i的md5十六进制摘要

每笔交易=50000 INSERTs。

数据库:1;表:1;索引:0

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
0..50000 records inserted in 1.87 seconds
50000..100000 records inserted in 1.92 seconds
100000..150000 records inserted in 1.97 seconds
150000..200000 records inserted in 1.99 seconds
200000..250000 records inserted in 2.19 seconds
250000..300000 records inserted in 1.94 seconds
300000..350000 records inserted in 1.94 seconds
350000..400000 records inserted in 1.94 seconds
400000..450000 records inserted in 1.94 seconds
450000..500000 records inserted in 2.50 seconds
500000..550000 records inserted in 1.94 seconds
550000..600000 records inserted in 1.94 seconds
600000..650000 records inserted in 1.93 seconds
650000..700000 records inserted in 1.94 seconds
700000..750000 records inserted in 1.94 seconds
750000..800000 records inserted in 1.94 seconds
800000..850000 records inserted in 1.93 seconds
850000..900000 records inserted in 1.95 seconds
900000..950000 records inserted in 1.94 seconds
950000..1000000 records inserted in 1.94 seconds
1000000..1050000 records inserted in 1.95 seconds
1050000..1100000 records inserted in 1.95 seconds
1100000..1150000 records inserted in 1.95 seconds
1150000..1200000 records inserted in 1.95 seconds
1200000..1250000 records inserted in 1.96 seconds
1250000..1300000 records inserted in 1.98 seconds
1300000..1350000 records inserted in 1.95 seconds
1350000..1400000 records inserted in 1.95 seconds
1400000..1450000 records inserted in 1.95 seconds
1450000..1500000 records inserted in 1.95 seconds
1500000..1550000 records inserted in 1.95 seconds
1550000..1600000 records inserted in 1.95 seconds
1600000..1650000 records inserted in 1.95 seconds
1650000..1700000 records inserted in 1.96 seconds
1700000..1750000 records inserted in 1.95 seconds
1750000..1800000 records inserted in 1.95 seconds
1800000..1850000 records inserted in 1.94 seconds
1850000..1900000 records inserted in 1.95 seconds
1900000..1950000 records inserted in 1.95 seconds
1950000..2000000 records inserted in 1.95 seconds

数据库文件大小:89.2 mib。

数据库:1;表:1;索引:1(md5)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
0..50000 records inserted in 2.90 seconds
50000..100000 records inserted in 11.64 seconds
100000..150000 records inserted in 10.85 seconds
150000..200000 records inserted in 10.62 seconds
200000..250000 records inserted in 11.28 seconds
250000..300000 records inserted in 12.09 seconds
300000..350000 records inserted in 10.60 seconds
350000..400000 records inserted in 12.25 seconds
400000..450000 records inserted in 13.83 seconds
450000..500000 records inserted in 14.48 seconds
500000..550000 records inserted in 11.08 seconds
550000..600000 records inserted in 10.72 seconds
600000..650000 records inserted in 14.99 seconds
650000..700000 records inserted in 10.85 seconds
700000..750000 records inserted in 11.25 seconds
750000..800000 records inserted in 17.68 seconds
800000..850000 records inserted in 14.44 seconds
850000..900000 records inserted in 19.46 seconds
900000..950000 records inserted in 16.41 seconds
950000..1000000 records inserted in 22.41 seconds
1000000..1050000 records inserted in 24.68 seconds
1050000..1100000 records inserted in 28.12 seconds
1100000..1150000 records inserted in 26.85 seconds
1150000..1200000 records inserted in 28.57 seconds
1200000..1250000 records inserted in 29.17 seconds
1250000..1300000 records inserted in 36.99 seconds
1300000..1350000 records inserted in 30.66 seconds
1350000..1400000 records inserted in 32.06 seconds
1400000..1450000 records inserted in 33.14 seconds
1450000..1500000 records inserted in 47.74 seconds
1500000..1550000 records inserted in 34.51 seconds
1550000..1600000 records inserted in 39.16 seconds
1600000..1650000 records inserted in 37.69 seconds
1650000..1700000 records inserted in 37.82 seconds
1700000..1750000 records inserted in 41.43 seconds
1750000..1800000 records inserted in 49.58 seconds
1800000..1850000 records inserted in 44.08 seconds
1850000..1900000 records inserted in 57.17 seconds
1900000..1950000 records inserted in 50.04 seconds
1950000..2000000 records inserted in 42.15 seconds

数据库文件大小:181.1 mib。

数据库:1;表:20(每100000条记录一个);索引:1(md5)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
0..50000 records inserted in 2.91 seconds
50000..100000 records inserted in 10.30 seconds
100000..150000 records inserted in 10.85 seconds
150000..200000 records inserted in 10.45 seconds
200000..250000 records inserted in 10.11 seconds
250000..300000 records inserted in 11.04 seconds
300000..350000 records inserted in 10.25 seconds
350000..400000 records inserted in 10.36 seconds
400000..450000 records inserted in 11.48 seconds
450000..500000 records inserted in 10.97 seconds
500000..550000 records inserted in 10.86 seconds
550000..600000 records inserted in 10.35 seconds
600000..650000 records inserted in 10.77 seconds
650000..700000 records inserted in 10.62 seconds
700000..750000 records inserted in 10.57 seconds
750000..800000 records inserted in 11.13 seconds
800000..850000 records inserted in 10.44 seconds
850000..900000 records inserted in 10.40 seconds
900000..950000 records inserted in 10.70 seconds
950000..1000000 records inserted in 10.53 seconds
1000000..1050000 records inserted in 10.98 seconds
1050000..1100000 records inserted in 11.56 seconds
1100000..1150000 records inserted in 10.66 seconds
1150000..1200000 records inserted in 10.38 seconds
1200000..1250000 records inserted in 10.24 seconds
1250000..1300000 records inserted in 10.80 seconds
1300000..1350000 records inserted in 10.85 seconds
1350000..1400000 records inserted in 10.46 seconds
1400000..1450000 records inserted in 10.25 seconds
1450000..1500000 records inserted in 10.98 seconds
1500000..1550000 records inserted in 10.15 seconds
1550000..1600000 records inserted in 11.81 seconds
1600000..1650000 records inserted in 10.80 seconds
1650000..1700000 records inserted in 11.06 seconds
1700000..1750000 records inserted in 10.24 seconds
1750000..1800000 records inserted in 10.57 seconds
1800000..1850000 records inserted in 11.54 seconds
1850000..1900000 records inserted in 10.80 seconds
1900000..1950000 records inserted in 11.07 seconds
1950000..2000000 records inserted in 13.27 seconds

数据库文件大小:180.1 mib。

如您所见,如果将数据分为多个表,插入速度将保持几乎不变。


不幸的是,我认为这是SQLite中大型表的一个限制。它不是为在大规模或大容量数据集上运行而设计的。虽然我知道这可能会大大增加项目的复杂性,但您最好还是研究更复杂的数据库解决方案,以满足您的需要。

从您链接的所有内容来看,表大小与访问速度之间的关系似乎是一种直接权衡。不能两者兼得。


在我的项目中,我无法共享数据库,因为它在不同的列上建立了索引。为了加快插入速度,我在/dev/shm(=linux ramdisk)上创建数据库,然后将其复制到本地磁盘。这显然只适用于一次写入、多次读取的数据库。


我怀疑索引的哈希值冲突会导致插入速度变慢。

当一个表中有许多行时,索引列哈希值冲突将更频繁地发生。这意味着SQLite引擎需要计算哈希值两次或三次,甚至可能四次,才能得到不同的哈希值。

所以我想这就是当表有很多行时,SQLite插入速度慢的根本原因。

这一点可以解释为什么使用碎片可以避免这个问题。谁是sqlite领域的真正专家来证实或否认我的观点?