抽象的
在与一些同事交谈时,我们遇到了“从大型数据库表中提取随机行”问题。这是一个经典的方法,我们知道天真的方法(也在 SO 上)通常是这样的:
SELECT * FROM mytable ORDER BY RAND() LIMIT 1
问题
我们也知道这样的查询是完全低效的,并且实际上只能用于很少的行。可以采取一些方法来获得更高的效率,例如这些仍然在 SO 上的方法,但它们不适用于任意主键,并且一旦数字主键中有孔,随机性就会出现偏差。最后一个引用问题的答案链接到这篇文章,该文章有一个很好的解释和一些聪明的解决方案,涉及一个额外的“平均分布”表,每当“主数据”表发生变化时必须维护该表。但话又说回来,如果你在一张大桌子上经常删除,你可能会被不断更新添加的桌子搞砸了。另请注意,许多解决方案依赖于COUNT(*)
这在 MyISAM 上速度快得离谱,但在 InnoDB 上却“很快”(我不知道它在其他平台上的表现如何,但我怀疑 InnoDB 案例可能代表其他事务性数据库系统)。
除此之外,即使是我能找到的最好的解决方案也很快,但不是Ludicrous Speed快。
理念
一个单独的服务可能负责生成、缓冲和分发随机行 ID 甚至整个随机行:
- 它可以根据原始 PK 的结构选择最佳方法来提取随机行 ID。服务可以在 ram 中维护有序的键列表(除了 PK 的实际大小之外,每行不应占用太多字节,标准 PC 可能最多 100~1000M 行,最多 1~ 100 亿行和强大的服务器)
- 一旦密钥在内存中,每个密钥都有一个隐含的“行号”,并且其中没有漏洞,因此只需选择一个随机数并直接获取相应的密钥
- 可以维护准备好使用的随机密钥缓冲区,以快速响应传入请求中的峰值
- 服务的消费者将连接并从缓冲区请求 N 个随机行
- 行作为简单键返回,或者服务可以维护一个(池)数据库连接以获取整行
- 如果缓冲区为空,则请求可能会阻塞或返回类似 EOF
- 如果将数据添加到主表中,则必须向服务发送信号以将相同的数据也添加到其副本中,刷新随机选择的缓冲区并从那里继续
- 如果从主表中删除数据,则必须向服务发出信号以从“所有键”列表和“随机选择”缓冲区中删除该数据
- 如果数据在主表中更新,则必须向服务发出信号以更新键列表和随机选择中的相应行
为什么我们认为它很酷
- 不接触磁盘,除了在启动时或发出信号时初始加载键
- 适用于任何类型的主键,无论是否为数字
- 如果您知道要更新大量数据,您可以在完成时发出信号(即不是在原始数据的每一次插入/更新/删除时),这基本上就像拥有一个细粒度的锁仅阻止对随机行的请求
- 对原始数据中任何类型的更新都非常快
- 将一些工作从关系数据库卸载到另一个仅内存的进程:有助于可伸缩性
- 从缓冲区快速响应,无需等待任何查询、扫描、排序
- 可以很容易地扩展到 SQL 之外的类似用例
为什么我们认为这可能是一个愚蠢的想法
- 因为我们在没有任何第三方帮助的情况下有了这个想法
- 因为没有人(我们听说过)曾经费心做类似的事情
- 因为它增加了混合的复杂性,以便在原始数据更改时保持更新
问题是……
是否已经存在类似的东西?如果没有,是否可行?如果不是,为什么?