如何加速(或分解)这个MySQL查询?

时间:2011-09-14 03:45:42

标签: python mysql numpy

我正在python和MySQL中构建一个视频推荐网站(想想音乐视频的潘多拉)。我的数据库中有三个表:

视频 - 视频表。数据不会改变。列是:

CREATE TABLE `video` (
    id int(11) NOT NULL AUTO_INCREMENT,
    website_id smallint(3) unsigned DEFAULT '0',
    rating_global varchar(128) DEFAULT '0',
    title varchar(256) DEFAULT NULL,
    thumb_url text,
PRIMARY KEY (`id`),
KEY `websites` (`website_id`),
KEY `id` (`id`) USING BTREE
) ENGINE=InnoDB AUTO_INCREMENT=49362 DEFAULT CHARSET=utf8

video_tag - 与每个视频相关联的标签(属性)表。不会改变。

CREATE TABLE `video_tag` (
    id int(7) NOT NULL AUTO_INCREMENT,
    video_id mediumint(7) unsigned DEFAULT '0',
    tag_id mediumint(7) unsigned DEFAULT '0',
PRIMARY KEY (`id`),
KEY `video_id` (`video_id`),
KEY `tag_id` (`tag_id`)
) ENGINE=InnoDB AUTO_INCREMENT=562456 DEFAULT CHARSET=utf8

user_rating - 用户为每个代码指定的评级为好或坏的表格。数据总是在变化。

CREATE TABLE `user_rating` (
    id int(11) NOT NULL AUTO_INCREMENT,
    user_id smallint(3) unsigned DEFAULT '0',
    tag_id int(5) unsigned DEFAULT '0',
    tag_rating float(10,5) DEFAULT '0',
PRIMARY KEY (`id`),
KEY `video` (`tag_id`),
KEY `user_id` (`user_id`) USING BTREE
) ENGINE=InnoDB AUTO_INCREMENT=447 DEFAULT CHARSET=utf8

根据用户的偏好,我想对每个未观看的视频进行评分,并尝试预测他们最喜欢的内容。这导致了以下大量查询,大约需要2秒才能完成50,000个视频:

SELECT video_tag.video_id, 
       (sum(user_rating.tag_rating) * video.rating_global) as score 

FROM video_tag 
JOIN user_rating ON user_rating.tag_id = video_tag.tag_id
JOIN video ON video.id = video_tag.video_id 

WHERE user_rating.user_id = 1 AND video.website_id = 2 
AND rating_global > 0 AND video_id NOT IN (1,2,3) GROUP BY video_id 
ORDER BY score DESC LIMIT 20

我迫切需要提高效率,所以我只是在寻找最佳方向的建议。我考虑过的一些想法:

a)重写我的db表结构(不确定如何)

b)将更多的分组和聚合卸载到Python中(还没有找到一种方法来连接三个实际上更快的表)

c)将不变的表存储在内存中以尝试加快计算时间(之前的修补尚未产生任何收益......)

您如何建议提高效率?

谢谢!!

-

评论中的每个请求,EXPLAIN SELECT ..显示:

id  select_type table   type    possible_keys   key key_len ref rows    Extra
1   SIMPLE  user_rating ref      video,user_id  user_id 3   const   88  Using where; Using temporary; Using filesort
1   SIMPLE  video_tag   ref      video_id,tag_id    tag_id  4   db.user_rating.tag_id   92  Using where
1   SIMPLE  video       eq_ref  PRIMARY,websites,id PRIMARY 4   db.video_tag.video_id   1   Using where

1 个答案:

答案 0 :(得分:1)

  • 将* rating_global *的字段类型更改为数字类型(浮点数或整数),无需将其作为varchar。我个人会将所有评级字段更改为整数,我发现它们不需要浮动。

  • 将KEY放在id上,PRIMARY KEY已经编入索引。 video.id,rating_global,website_id

  • 观看引用的整数长度(例如video_id - > video.id),您可能会用完数字。这些尺寸应该相同。

我建议使用以下两步解决方案来替换您的查询:

CREATE TEMPORARY TABLE rating_stats ENGINE=MEMORY
SELECT video_id, SUM(tag_rating) AS tag_rating_sum 
FROM user_rating ur JOIN video_tag vt ON vt.id = ur.tag_id AND ur.user_id=1
GROUP BY video_id ORDER BY NULL

SELECT v.id, tag_rating_sum*rating_global AS score FROM video v 
JOIN rating_stats rs ON rs.video_id = v.id 
WHERE v.website_id=2 AND v.rating_global > 0 AND v.id NOT IN (1,2,3)
ORDER BY score DESC LIMIT 20

要使后一个查询执行速度非常快,您可以在视频表字段中加入主页KEY_id和rating_global(可能只有website_id就足够了)。

您还可以使用具有这些统计信息的其他表格,并根据用户登录/操作频率动态预先计算。我猜你可以显示缓存数据而不是显示实时结果,应该没有太大区别。