2

问题描述:

一个标签(tags)可以通过联结表(tagged_as)与任意对象相关联。对于特定对象类型 (specific_object),选择与一系列标签关联的所有对象的并集或交集,按对象上的数字列对结果进行排序,并限制结果以用于分页目的。

人为的架构:

CREATE TABLE tags (
    id INT NOT NULL AUTO_INCREMENT,
    name VARCHAR(45) NOT NULL,
    PRIMARY KEY (id)
);

CREATE TABLE specific_object(
    id INT NOT NULL AUTO_INCREMENT,
    name VARCHAR(45) NOT NULL,
    vote_sum INT NOT NULL DEFAULT 0,
    PRIMARY KEY (id)
);

CREATE TABLE tagged_as(
    id INT NOT NULL AUTO_INCREMENT,
    tag_id INT NOT NULL,
    content_type_id INT NOT NULL,
    object_id INT NOT NULL,
    PRIMARY KEY (id)
);

出于本示例的目的,我省略了 specific_object 表中的许多其他列。

表行数:

标签: 12,297

标记为:46,642,064

特定对象:2,444,944

天真的 MySQL 解决方案:

SELECT
    specific_object.*
FROM
    specific_object
JOIN
    tagged_as
ON
    specific_object.id = tagged_as.object_id
    AND
    tagged_as.content_type_id = <SPECIFIC_OBJECT_CONTENT_TYPE_ID>
WHERE
    tagged_as.tag_id = <TAG_ONE_ID>
    AND
    tagged_as.tag_id = <TAG_TWO_ID>
    ...
ORDER BY specific_object.vote_sum DESC
LIMIT 50

此解决方案的问题是 MySQL 无法利用索引来解析 ORDER BY 子句,因为“用于获取行的键与 ORDER BY 中使用的键不同”(http://dev.mysql.com /doc/refman/5.0/en/order-by-optimization.html)。执行时间:20+秒

天真的 Redis 解决方案:

for each specific object: SET specfic_object:<ID> <ID>
for each tagged as: SADD tag:<TAG ID> specific_object:<ID>

specific_object_ids = SUNION tag:<TAG_ONE_ID> tag:<TAG_TWO_ID> ...
specific_object_ids = SINTER tag:<TAG_ONE_ID> tag:<TAG_TWO_ID> ...

SELECT * FROM specific_object WHERE id IN (<specific_object_ids>) ORDER BY vote_sum DESC

这个解决方案的问题是 ORDER BY 仍然必须由 MySQL 完成。此外,一个标签可能与数十万个特定对象相关联,这些对象需要移动大量数据。执行时间:较大的标签需要 20 多秒

我还没有尝试过的可能解决方案

非规范化

也许将 vote_sum 列移动到 tagged_as 表中。不再需要加入来执行订单。这可能与天真的解决方案具有相同的问题。

Redis 排序集

for each specific object: SET specific_object:<ID> <ID>
for each specific object: SET specific_object_weight:<ID> <VOTE_SUM>
for each tagged as: SADD tag:<TAG_ID> specific_object:<ID>

SINTERSTORE result:<timestamp> <TAG_ONE_ID> <TAG_TWO_ID> ...
SORT result:<timestamp> BY specific_object_weight_* LIMIT 0 50 
specific_object_ids = SMEMBERS result:<timestamp>
DEL result:<timestamp>

SELECT * FROM specific_object WHERE id IN (<specific_object_ids>)

将所有排序移至 Redis。这增加了额外的复杂性,因为现在您还必须在 Redis 中维护 vote_sum 值。不确定这是否足够快。

问题:

任何一种可能的解决方案都可行吗?是否有其他解决方案或不同的技术可以提供帮助?我愿意接受相当大的改变来解决这个问题。

4

1 回答 1

0

当问题是 DESC 排序的性能时,我过去解决问题的方法是将 的值存储-1*vote_sum在单独的列中,然后按该列 ASC 排序。我已经能够让 MySQL 使用索引对该列进行排序。

您可以存储一个冗余列(vote_sumneg_vote_sum,或者您可以只存储负值,并在需要将其作为正值返回时将其乘以 -1。

但我怀疑您的性能问题的根源是排序操作。作为测试,当您执行 ? 时,语句的性能如何比较ORDER BY vote_sum ASC

于 2012-12-17T22:38:39.823 回答