【问题标题】:Column of increasing integers: why is an index needed to speed up the query? [duplicate]递增整数列:为什么需要索引来加快查询速度? [复制]
【发布时间】:2021-04-01 15:00:57
【问题描述】:

在以下示例中,t 是一个递增序列,在 100 万行中大致从 0 到 5,000,000。

import sqlite3, random, time
t = 0
db = sqlite3.connect(':memory:')
db.execute("CREATE TABLE IF NOT EXISTS data(id INTEGER PRIMARY KEY, t INTEGER, label TEXT);")
for i in range(1000*1000):
    t += random.randint(0, 10)
    db.execute("INSERT INTO data(t, label) VALUES (?, ?)", (t, 'hello'))

使用索引选择一个范围(假设 t = 1,000,000 ... 2,000,000):

db.execute("CREATE INDEX t_index ON data(t);")
start = time.time()
print(list(db.execute(f"SELECT COUNT(id) FROM data WHERE t BETWEEN 1000000 AND 2000000")))
print("index: %.1f ms" % ((time.time()-start)*1000))  # index: 15.0 ms

比不使用索引快 4-5 倍

db.execute("DROP INDEX IF EXISTS t_index;")
start = time.time()
print(list(db.execute(f"SELECT COUNT(id) FROM data WHERE t BETWEEN 1000000 AND 2000000")))
print("no index: %.1f ms" % ((time.time()-start)*1000))  # no index: 73.0 ms

但数据库大小至少比索引大 30%。

问题:总的来说,我了解索引是如何大幅加速查询的,但是在t 是整数+递增的这种情况下,为什么还需要索引来加速查询?

毕竟,我们只需要找到 t=1,000,000 的行(这在 O(log n) 中是可能的,因为序列在增加),找到 t=2,000,000 的行,然后我们就有了范围.

TL;DR:当一列是一个递增的整数序列时,有没有办法对一个范围进行快速查询, 不必增加 30% 的数据库大小索引?

例如通过在创建表时设置参数,通知Sqlite该列正在增加/已经排序?

【问题讨论】:

    标签: python database sqlite indexing database-performance


    【解决方案1】:

    简短的回答是 sqlite 不知道您的表是按列 t 排序的。这意味着它必须扫描整个表才能提取数据。

    当您在列上添加索引时,列 t 在索引中排序,因此它可以跳过前一百万行,然后将感兴趣的行流式传输给您。您正在提取 20% 的行,并在 15 毫秒/73 毫秒 = 21% 的时间内返回。如果该分数越小,您从索引中获得的收益就越大。

    如果列 t 是唯一的,则考虑使用该列作为主键,因为您将获得“免费”的索引。如果您可以使用相同的 t 绑定行数,则使用 (t, offset) 作为主键,其中 offset 可能是 tinyint。关键是 size(primary key index) + size(t index) 会大于 size(t+offset index)。如果 t 的单位是 ms 或 ns 而不是 s,它在实践中可能是唯一的,或者你可以在它不是时摆弄它(并且在你需要数据时截断到第二个分辨率)。

    如果您不需要主键(作为唯一索引),请将其省略,只在 t 上设置非唯一索引。如果没有主键,您可以通过 rowid 标识唯一行,或者如果所有列共同创建唯一行。如果你创建没有rowid的表,你仍然可以使用limit对相同的行进行操作。

    您可以使用数据库仓库技术,如果您不需要每条记录的数据,则以较小粒度的方式存储它(每分钟记录,或每小时记录和 group_concat 文本列)。

    最后,还有针对时间序列数据进行了优化的数据库。例如,它们可能只允许您删除最旧的数据或附加新数据,但不能进行任何更改。这将允许诸如系统存储预先排序的数据(mysql,顺便说一句,将此功能称为索引有序表)。由于数据不能改变,这样的数据库我的运行长度或增量按列压缩数据,因此它只存储行之间的差异。

    【讨论】:

    • 感谢@AllanWind 的回答。在我的真实情况下,t 是一个(整数)unix 时间戳(日期时间),因此不能保证它是唯一的(多个事件可以在同一秒发生),所以我认为我不能将此列用作 PRIMARY KEY。
    • 当你在列上添加索引时,列 t 在索引中排序:因为taleady排序的,做一个索引是对磁盘空间的浪费。有没有办法通知 Sqlite t 排序,并且它可以使用此信息来加速范围“范围”查找?所以我们可以保存额外的索引。
    • 说,有办法告诉数据库“我保证如果 id > id' 然后 t > t'”。数据库将如何检查(提示:使用索引来快速检查)?如果您不检查并违反承诺,您的数据现在已“损坏”。
    • @Basj 一种方法可以通知 Sqlite t 已经排序。这称为添加索引。如果可以通过另一种机制通知它,那么您总是有可能告诉它一些不是 100% 准确的东西,或者最初是准确的,但由于随后对表中数据的更改而变得不准确。
    猜你喜欢
    • 2017-10-19
    • 1970-01-01
    • 1970-01-01
    • 2016-12-22
    • 2011-01-04
    • 2021-03-20
    • 1970-01-01
    • 2014-01-14
    • 1970-01-01
    相关资源
    最近更新 更多