背景
我有一个“用户”表、一个“内容”表和一个“content_likes”表。当用户“喜欢”内容项时,就会将关系添加到“content_likes”。简单。
现在我想做的是根据收到的点赞数对内容进行排序。这相对容易,但是,我只想一次检索 10 个项目,然后通过延迟加载检索接下来的 10 个项目,依此类推。如果选择按时间排序,则很容易在选择语句中进行偏移,但是,由于按“喜欢”数量排序,我需要另一列可以偏移。因此,我在结果集中添加了一个“排名”列,然后在下一次调用 10 个项目时,我可以用它来抵消。
这个查询有效并且做了我需要做的事情。但是,我担心性能。任何人都可以建议优化此查询。或者甚至可能是更好的方法。
数据库架构
CREATE TABLE `users` (
`id` int(11) NOT NULL AUTO_INCREMENT
PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=1 DEFAULT CHARSET=utf8
CREATE TABLE `content` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`owner_id` int(11) NOT NULL,
`added` int(11) NOT NULL,
`deleted` int(11) NOT NULL DEFAULT '0',
PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=1 DEFAULT CHARSET=utf8
CREATE TABLE `content_likes` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`content_id` int(11) NOT NULL,
`user_id` int(11) NOT NULL,
`added` int(11) NOT NULL,
PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=1 DEFAULT CHARSET=utf8
*为简单起见省略了列
查询分割
- 在 content_likes 关系表中对 content_id 进行分组,并按喜欢 desc 排序
- 向结果集中添加“排名”列(或行号)并按此排序
- 加入“内容”表,以便可以忽略带有已删除标记的任何内容
- 仅返回“排名”(或行号)大于变量的结果
- 将结果集限制为 10
MYSQL
SELECT
results.content_id, results.likes, results.rank
FROM
(
SELECT
t1.content_id, t1.likes, @rn:=@rn+1 AS rank
FROM
(
SELECT
cl.content_id,
COUNT(cl.content_id) AS likes
FROM
content_likes cl
GROUP BY
cl.content_id
ORDER BY
likes DESC,
added DESC
) t1, (SELECT @rn:=0) t2
ORDER BY
rank ASC
) results
LEFT JOIN
content c
ON
(c.id = results.content_id)
WHERE
c.deleted <> 1
AND
results.rank > :lastRank
LIMIT
10
MYSQL 替代品
SELECT
*
FROM
(
SELECT
results.*, @rn:=@rn+1 AS rank
FROM
(
SELECT
c.id, cl.likes
FROM
content c
INNER JOIN
(SELECT content_id, COUNT(content_id) AS likes FROM content_likes GROUP BY content_id ORDER BY likes DESC, added DESC) cl
ON
c.id = cl.content_id
WHERE
c.deleted <> 1
AND
c.added > :timeago
LIMIT
100
) results, (SELECT @rn:=0) t2
) final
WHERE
final.rank > :lastRank
LIMIT
5
“替代”mysql 查询也可以像我希望的那样工作。内容按用户喜欢的数量排序,我可以通过插入最后一行号来抵消。我在这里尝试做的是限制结果集,这样如果表变得很大,性能就不会受到太大影响。在此示例中,仅返回某个时间跨度内的内容,且限制为 100。然后我可以按行号偏移(延迟加载/分页)
任何帮助或建议总是值得赞赏。我相对来说是 mysql 的新手,所以请友善:)
最佳答案
您可以消除子查询:
SELECT results.content_id, results.likes, results.rank
FROM (SELECT cl.content_id, COUNT(cl.content_id) AS likes, @rn:=@rn+1 AS rank
FROMc content_likes cl cross join
(SELECT @rn:=0) t2
GROUP BY cl.content_id
ORDER BY likes DESC, added DESC
) results LEFT JOIN
content c
ON c.id = results.content_id
WHERE c.deleted <> 1 AND
results.rank > :lastRank
LIMIT 10;
但是,我认为这不会对性能产生明显影响。您可能应该做的是存储最后的点赞数和“添加”值,并使用它们来过滤数据。该查询需要稍微修正一下,因为 added
没有在 order by
子句中明确定义:
SELECT results.content_id, results.likes, results.rank, results.added
FROM (SELECT cl.content_id, COUNT(cl.content_id) AS likes, MAX(added) as added, @rn:=@rn+1 AS rank
FROMc content_likes cl cross join
(SELECT @rn := :lastRank) t2
WHERE likes < :likes or
likes = :likes and added < :added
GROUP BY cl.content_id
ORDER BY likes DESC, added DESC
) results LEFT JOIN
content c
ON c.id = results.content_id
WHERE c.deleted <> 1 AND
results.rank > :lastRank
LIMIT 10;
这至少会减少需要排序的行数。
关于mysql - 优化工作 MYSQL 语句,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/22118410/