如何解决递增整数列:为什么需要索引来加速查询?
在下面的示例中,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 ... 2,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 的行(这在 O(log n) 中是可能的,因为序列是递增的),找到 t=2,000 的行,然后我们就有了范围.
TL;DR:当一列是一个递增的整数序列时,有没有办法对一个范围进行快速查询, 不必增加 +30% 的数据库大小索引?
比如通过在创建表时设置一个参数,通知Sqlite该列正在增加/已经排序?
解决方法
简短的回答是 sqlite 不知道您的表是按列 t 排序的。这意味着它必须扫描整个表才能提取数据。
当您在列上添加索引时,列 t 在索引中进行排序,因此它可以跳过前一百万行,然后将您感兴趣的行流式传输。您正在提取 20% 的行,它在 15 ms / 73 ms = 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,顺便说一句,将此功能称为索引有序表)。由于数据无法更改,因此我的运行长度或增量按列压缩数据,因此它仅存储行之间的差异。
版权声明:本文内容由互联网用户自发贡献,该文观点与技术仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 dio@foxmail.com 举报,一经查实,本站将立刻删除。