【问题标题】:SQLite insert speed slows as number of records increases due to an index由于索引导致记录数增加,SQLite 插入速度变慢
【发布时间】:2013-03-24 14:37:39
【问题描述】:

原问题

背景

众所周知,SQLite needs to be fine tuned 可以实现大约 50k 插入/秒的插入速度。这里有很多关于插入速度慢的问题以及大量的建议和基准。

还有claims that SQLite can handle large amounts of data,报告 50+ GB 并没有导致正确设置出现任何问题。

我已按照此处和其他地方的建议来实现这些速度,我对 35k-45k 插入/秒感到满意。我遇到的问题是,所有基准测试都只展示了 插入速度似乎与表格大小成反比。

问题

我的用例需要在链接表中存储 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'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 数据。然后附加临时表 到接收数据库并插入 50,000 行的集合 insert-select 声明。因此,插入时间不反映 文件到数据库插入时间,而是表到表插入 速度。考虑 CSV 导入时间会降低速度 降低 25-50%(一个非常粗略的估计,进口不需要很长时间 CSV 数据)。

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

从上面的数据中可以很清楚地看出,正确答案可以分配给Tim's answer,而不是SQLite 无法处理的断言。显然它可以处理大型数据集如果索引该数据集不属于您的用例。为此,我一直在使用 SQLite 作为日志系统的后端,现在 不需要 需要索引,所以我对我所经历的减速感到非常惊讶。

结论

如果有人发现自己想要使用 SQLite 存储大量数据将其编入索引,using shards 可能是答案。我最终决定使用 MD5 哈希的前三个字符作为 z 中的唯一列来确定分配给 4,096 个数据库中的一个。由于我的用例本质上主要是归档,因此模式不会改变,查询也永远不需要分片遍历。数据库大小是有限制的,因为非常旧的数据将被减少并最终被丢弃,因此这种分片、杂注设置甚至一些de规范化的组合给了我一个很好的平衡,这将基于上述基准测试,保持插入速度至少为 10k 插入/秒。

【问题讨论】:

  • 您是如何使用事务的?你配置了多大的页面缓存?
  • @CL。 cache_size 设置为 10000。所有插入都在一个事务中,每 50k 插入提交一次。这些值是在广泛测试后选择的,以找到最快的插入速度(35k-45k 插入/秒),但随着记录数量增加超过约 10m,速度会下降。
  • 在这里超出您的具体问题:请问,您是使用 z_id 上的索引来查找单个记录还是选择范围?
  • @Tim 这是一个链接表,因此从table_z 中选择项目,然后通过此表通过z_id 链接到table_ytable_x。因此,要回答您的问题,它有助于查找个人记录。
  • @veryhungrymike:在这种情况下,您可以查看一个非索引哈希表嵌套关系数据库,它可以让您立即找到指向 z_id 行以及链接到特定的 z_id 没有索引开销,并且随着索引的增长,在插入期间伴随的性能下降。对 z_id 具有最大变化(右加权)的数字赋予更大权重的方法会给您带来良好的分布。

标签: database optimization sqlite insert


【解决方案1】:

如果您的要求是找到特定的 z_id 和链接到它的 x_idsy_ids(与快速选择 z_ids 的范围不同),您可以查看非索引哈希表嵌套关系数据库,它可以让您立即找到通往特定z_id 的方法,以便获得其y_idsx_ids——没有索引开销,并且随着索引的增长,在插入过程中不会伴随性能下降。为了避免聚集(也称为桶冲突),请选择一种密钥哈希算法,该算法将最大权重放在具有最大变化(右加权)的 z_id 的数字上。

附:例如,使用 b-tree 的数据库起初可能比使用线性散列的 db 更快,但是随着 b-tree 的性能开始下降,插入性能将保持与线性散列相同。

附言回答@kawing-chiu 的问题:这里相关的核心特征是这样的数据库依赖于所谓的“稀疏”表,其中记录的物理位置由以记录键作为输入的散列算法确定。这种方法允许直接查找表中记录的位置而无需索引的中介。由于不需要遍历索引或重新平衡索引,因此随着表变得更加密集,插入时间保持不变。相比之下,对于 b 树,插入时间会随着索引树的增长而降低。具有大量并发插入的 OLTP 应用程序可以从这种稀疏表方法中受益。记录分散在整个表中。记录分散在稀疏表的“苔原”中的缺点是收集具有共同值的大型记录集(例如邮政编码)可能会更慢。散列稀疏表方法经过优化,可以插入和检索单个记录,以及检索相关记录的网络,而不是具有某些共同字段值的大型记录集。

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

【讨论】:

  • 感谢您的进一步解释。但是我所知道的大多数数据库都使用 b-tree 或 LSM-tree。你能说出一些使用线性哈希的数据库吗?
  • 在阅读更多关于线性散列的内容后,我认为它仍然不是真正大数据的解决方案。因为要么你有一个较小的哈希图,但有很多冲突,或者你的哈希图几乎和你的数据一样大。 lmdb的作者prefers b tree也是。
  • 不需要“哈希图”;文件空间被划分为可寻址的页面。我有没有说过 LH 是“真正的大数据”的解决方案?正如我明确指出的,LH 针对插入和检索单个记录的 OLTP 应用程序进行了优化。 LH 非常适合在社交网站上提取档案或个人医疗记录或用户资料。这种检索不需要索引,而且检索是即时的。 LH 它没有针对“大数据”分析进行优化,其中提取的数据子集具有一些公共字段。
【解决方案2】:

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

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


我做了一个非常粗略的基准测试,以确保我的建议确实对性能产生影响。

架构:

CREATE TABLE IF NOT EXISTS "test_$i"
(
    "i" integer NOT NULL,
    "md5" text(32) NOT NULL
);

数据 - 200 万行:

  • i = 1..2,000,000
  • md5 = i 的 md5 十六进制摘要

每笔交易 = 50,000 INSERTs。


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

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)

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 个(每 100,000 条记录一个);索引:1 (md5)

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。


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

【讨论】:

  • 我确实最终将数据分片(到 4096 个表中),每个文件一个表。我在几个地方读到每个数据库一个表是使用 SQLite 的更好方法,并且在我最初的基准测试中,在我发现上述内容之前,似乎将表放在一个数据库中是问题的一部分。最后,我要做的是在内存中准备 100k 左右的记录,然后根据需要附加/分离数据库进行写入。如果您可以为每个执行数千次写入,我没有意识到附加/分离的开销非常小。不过,我会在下一次迭代中尝试您的建议!
  • 另外,2M 行不足以看到速度的下降。如上图所示,您需要至少使用 10M 进行基准测试。最终我将处理大约十亿行。
  • @veryhungrymike:每个文件有 1 个表有几个优点,但也更不切实际。瓶颈是索引,由于它们与表相关联,因此我非常有信心,只要您保持每个单独的表简短,您就可以使用一个或多个文件获得几乎相同的改进。
  • @veryhungrymike:哦,至于记录的数量,我知道它们越多,它就越慢(我只是没有太多时间等待基准测试结尾)。无论如何,每个测试都有 200 万行,您会注意到第二个测试在 800k 行时开始变得相当慢,而第三个测试保持一致直到结束。这应该说明什么。
  • 也许我需要调整描述,因为重点是如何保持许多(百万)记录的速度。这是一种日志记录类型的用例,其中不断添加数据,但一旦添加,就永远不会更改,因此保持数百万行的 10 秒或 100 秒的速度是主要目标。
【解决方案3】:

不幸的是,我想说这是 SQLite 中大型表的限制。在大规模或大容量数据集上操作是not designed。虽然我知道这可能会大大增加项目的复杂性,但您最好还是研究适合您需求的更复杂的数据库解决方案。

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

【讨论】:

  • 您提供的链接提到了 TB 级的数据库大小,这里不是这种情况。目前数据库的大小约为 500MB。从长远来看,我计划在达到 20-50GB 时丢弃历史数据。
  • 请参阅上面的基准测试,它表明您确实可以拥有一个大表快速插入。但是,您不能拥有的是该表上的索引。
【解决方案4】:

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

【讨论】:

    【解决方案5】:

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

    当我们在一张表中有很多很多行时,索引列的哈希值冲突会更频繁地发生。这意味着 Sqlite 引擎需要计算 2 次或 3 次,甚至 4 次的 hash 值,才能得到不同的 hash 值。

    所以我猜这是当表有很多行时 SQLite 插入缓慢的根本原因。

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

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 2014-08-10
      • 1970-01-01
      • 1970-01-01
      • 2012-09-24
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多