MySQL或Redis中的实时性能标签搜索

时间:2012-12-17 19:47:22

标签: mysql tags redis sql-order-by

问题描述:

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

Contrived Schema:

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

tagged_as:46,642,064

specific_object: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子句,因为用于获取行的&#34;键与ORDER BY&#34;中使用的键不同。 (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值。不确定这是否足够快。

问题:

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

1 个答案:

答案 0 :(得分:0)

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

您可以存储冗余列(vote_sumneg_vote_sum,也可以只存储负值,只需将其乘以-1即可将其作为正值返回

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