我在扩展应用程序时遇到了一些困难,于是决定在这里提问。
考虑一个关系数据库(比如mysql)。假设它允许用户发帖,这些帖子存储在post
表中(包含字段:postid, posterid, data, timestamp
)。因此,当您按照新近度排序检索所有帖子时,您只需获得posterid = you
和order by date
的所有帖子。很简单。
此过程将使用时间戳作为索引,因为它具有最高基数且正确如此。因此,除了查看索引之外,它还需要从磁盘中逐行获取一行来完成此任务。真棒!
但是,让我们说自上次发布以来,其他用户的帖子数量已超过100万(在系统中)。然后,为了得到你的最新帖子,数据库会再次在时间戳上挂起索引,而且我们不知道自那时以来发生了多少帖子(或者我们至少应该手动估计并设置首选键)?然后我们浪费了一下百万行和一行行来获取一行。
此外,来自多个任意用户的一组帖子将是其中一个用例,因此我无法创建像userid_timestamp这样的字段来创建子索引。
我看到这个错了吗?或者必须从应用程序中根本改变什么才能使这种操作至少在某种程度上有效地发生?
答案 0 :(得分:3)
如果您有查询:... WHERE posterid = you ORDER BY timestamp [DESC]
,则需要 {posterid,timestamp} 上的综合索引。
要了解原因,请查看Anatomy of an SQL Index。
“普通”B-Tree索引的叶子将“指针”(物理地址)保存到索引行,而行本身驻留在称为“表堆”的单独数据结构中。可以通过直接在B-Tree的叶子中存储行来消除堆,这称为clustering。这有其优点和缺点,但如果您有一种主要的查询类型,通过群集消除表堆访问肯定是需要考虑的事情。
在这种特殊情况下,可以像这样创建表:
CREATE TABLE T (
posterid int,
`timestamp` DATETIME,
data VARCHAR(50),
PRIMARY KEY (posterid, `timestamp`)
);
MySQL / InnoDB clusters all its tables并使用主键作为群集密钥。我们没有使用代理键(postid
),因为群集表中的二级索引可能很昂贵,而且我们已经拥有了自然键。如果您really need代理键,请考虑将其作为备用密钥并通过自然密钥保持群集建立。
答案 1 :(得分:1)
对于像
这样的查询where posterid = 5
order by timestamp
或
where posterid in (4, 578, 222299, ...etc...)
order by timestamp
在(posterid, timestamp)
上建立索引,数据库应该自行选择。
编辑 - 我只是用mysql
尝试了这个CREATE TABLE `posts` (
`id` INT(11) NOT NULL,
`ts` INT NOT NULL,
`data` VARCHAR(100) NULL DEFAULT NULL,
INDEX `id_ts` (`id`, `ts`),
INDEX `id` (`id`),
INDEX `ts` (`ts`),
INDEX `ts_id` (`ts`, `id`)
)
ENGINE=InnoDB
我填写了大量数据,
explain
select * from posts where id = 5 order by ts
选择id_ts
索引
答案 2 :(得分:0)
假设您使用哈希表来实现数据库 - 是的。散列表不是有序的,除了迭代所有元素以找到最大值之外别无其他方法。
但是,如果你使用一些有序的DS,例如B+ tree(实际上它非常适合磁盘和数据库),那就是另一个故事。
您可以将元素存储在用户(主要订单/比较器)和日期(次要比较器,降序)排序的B +树中。拥有此DS后,通过查找与主要条件(user-id)匹配的第一个元素,可以在O(log(n))
磁盘搜索中找到第一个元素。
我不熟悉数据库的实现,但AFAIK,其中一些确实允许您基于B +树创建索引 - 通过这样做,您可以实现查找用户的最后一篇文章更有效率。
P.S。
确切地说, Relational Algebra 中没有很好地定义“最大”元素或排序的概念。没有最大运营商。要使用单个列R
获取表a
的最大元素,应该实际创建该表的笛卡尔积并找到此条目。严格的关系代数中没有max和sort运算符(尽管它确实存在于SQL中)
(Assuming set, and not multiset semantics):
MAX = R \ Project(Select(R x R, R1.a < R2.a),R1.a)