这通常使用OLAP cube 处理。这里的想法是添加一个自然时间维度。对于此应用程序来说,它们可能太重了,但这里有一个摘要,以防其他人需要它。
OLAP 多维数据集从时间的基本概念开始。您必须知道自己关心什么时间才能理解数据。
您从“时间”表开始:
Time {
timestamp long (PK)
created datetime
last_queried datetime
}
这基本上可以跟踪您的数据快照。我已经包含了一个 last_queried 字段。每当用户根据此特定时间戳请求数据时,都应使用当前时间进行更新。
现在我们可以开始讨论“线程”了:
Threads {
id long (PK)
identifier long
last_modified datetime
title string
body string
score int
}
id 字段是一个自动递增键;这是永远不会暴露的。 identifier 是线程的“唯一”ID。我说“唯一”是因为没有唯一性约束,而且就数据库而言,它 是唯一的。那里的所有其他内容都非常标准... 除了... 当您写入时,您不会更新此条目。在 OLAP 多维数据集中,您几乎从不修改数据。更新和插入在最后解释。
现在,我们如何查询这个?你不能直接查询Threads。您需要包含一个星表:
ThreadStar {
timestamp long (FK -> Time.timestamp)
thread_id long (FK -> Threads.id)
thread_identifier long (matches Threads[thread_id].identifier)
(timestamp, thread_identifier should be unique)
}
该表为您提供了从现在的时间到所有线程的状态的映射。给定一个特定的时间戳,您可以通过以下方式获取线程的状态:
SELECT Thread.*
FROM Thread
JOIN ThreadStar ON Thread.id = ThreadStar.thread_id
WHERE ThreadStar.timestamp = {timestamp}
AND Thread.identifier = {thread_identifier}
这还不算太糟糕。我们如何获得线程流?首先我们需要知道现在几点。基本上你想从Time 中获取最大的timestamp 并将Time.last_queried 更新到当前时间。您可以在其前面放置一个仅每隔几秒更新一次的缓存,或者您想要的任何内容。一旦你有了它,你就可以获得所有线程:
SELECT Thread.*
FROM Thread
JOIN ThreadStar ON Thread.id = ThreadStar.thread_id
WHERE ThreadStar.timestamp = {timestamp}
ORDER BY Thread.score DESC
很好。我们有一个线程列表,随着实际分数的变化,排序是稳定的。你可以在闲暇时翻阅这个……有点。最终数据将被清理,您将丢失快照。
所以这很好,但现在你需要创建或更新一个线程。创建和修改几乎相同。两者都使用INSERT 处理,唯一的区别是您是使用现有的identifier 还是创建一个新的。
所以现在您已经插入了一个新线程。您需要更新 ThreadStar。这是疯狂昂贵的部分。基本上,您使用最新的timestamp 复制所有 ThreadStar 条目,除非您为刚刚修改的线程更新 thread_id。这是一个疯狂的重复数量。幸运的是,它几乎只是外键,但仍然如此。
你也不做DELETEs;更新 ThreadStar 时将行标记为已删除或仅将其排除。
现在您正在努力工作,但您的数据量正在疯狂增长。你可能想要清理它,除非你有很多存储预算,但即便如此,事情也会开始变慢(除此之外:这实际上会表现得非常好,即使数据量很大)。
清理非常简单。这只是一些级联删除和清理孤立数据的问题。随时从 Time 中删除条目(例如,它不是最新条目,并且 last_queried 为 null 或比任何截止值更早)。将这些删除级联到 ThreadStar。然后找到任何不在 ThreadStar 中的带有id 的线程并清理它们。
如果您有更多的嵌套数据,这种通用机制也适用,但您的查询会变得更加困难。
最后一点:由于数据量太大,您会发现插入速度非常慢。大多数地方在开发和测试环境中使用适当的约束来构建它,但随后在生产中禁用约束!
是的。确保您的测试可靠。
但至少您对分页中重新排序的数据不敏感。