问题描述:
一个标签(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 值。不确定这是否足够快。
问题:
任何一种可能的解决方案都可行吗?是否有其他解决方案或不同的技术可以提供帮助?我愿意接受相当大的改变来解决这个问题。