1

我有这张桌子

CREATE TABLE `votes` (
  `item_id` int(10) unsigned NOT NULL,
  `user_id` int(10) unsigned NOT NULL,
  `vote` tinyint(4) NOT NULL DEFAULT '0',
  PRIMARY KEY (`item_id`,`user_id`),
  KEY `FK_vote_user` (`user_id`),
  KEY `vote` (`vote`),
  KEY `item` (`item_id`),
  CONSTRAINT `FK_vote_item` FOREIGN KEY (`item_id`) REFERENCES `items` (`id`) ON UPDATE CASCADE,
  CONSTRAINT `FK_vote_user` FOREIGN KEY (`user_id`) REFERENCES `users` (`id`) ON UPDATE CASCADE
) ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_unicode_ci

我得到了这个简单的选择

SELECT 
  `a`.`item_id`, `a`.`sum`
FROM
  (SELECT 
    `item_id`, SUM(vote) AS `sum` 
  FROM
    `votes` 
  GROUP BY `item_id`) AS a 
ORDER BY `a`.`sum` DESC
LIMIT 10

现在,只有 250 行,没有问题,但它正在使用文件排序。该vote列具有-1或。但是当这个表有数百万或行时,这会有效吗?01

如果我使它成为一个没有子查询的更简单的查询,那么using temporary table就会出现。

解释给出(查询在 0.00170 秒内完成):

id select_type table      type  possible_keys key     key_len ref  rows Extra
1  PRIMARY     <derived2> ALL   NULL          NULL    NULL    NULL 33   Using filesort
2  DERIVED     votes      index NULL          PRIMARY 8       NULL 250
4

3 回答 3

2

不,这不会对数百万行有效。

您必须创建一个支持聚合表,该表将存储每个项目的投票:

CREATE TABLE item_votes
        (
        item_id INT NOT NULL PRIMARY KEY,
        votes UNSIGNED INT NOT NULL,
        upvotes UNSIGNED INT NOT NULL,
        downvotes UNSIGNED INT NOT NULL,
        KEY (votes),
        KEY (upvotes),
        KEY (downvotes)
        )

并在每次投票时更新它:

INSERT
INTO    item_votes (item_id, votes, upvotes, downvotes)
VALUES  (
        $item_id,
        CASE WHEN $upvote THEN 1 ELSE -1 END,
        CASE WHEN $upvote THEN 1 ELSE 0 END,
        CASE WHEN $upvote THEN 0 ELSE 1 END
        )
ON DUPLICATE KEY
UPDATE
SET     votes = votes + VALUES(upvotes) - VALUES(downvotes),
        upvotes = upvotes + VALUES(upvotes),
        downvotes = downvotes + VALUES(downvotes)

然后选择前 10 票:

SELECT  *
FROM    item_votes
ORDER BY
        votes DESC, item_id DESC
LIMIT   10

有效地使用索引。

于 2013-05-06T12:54:01.870 回答
1

但是当这个表有数百万或行时,这会有效吗?

不,不会的。

如果我使它成为一个没有子查询的更简单的查询,则会出现使用临时表。

可能是因为规划器会将其转换为您发布的查询:它需要计算总和才能以正确的顺序返回结果。

要快速获取投票最多的问题,您需要缓存结果。在您的项目表中添加一个分数字段,并对其进行维护(例如使用触发器)。并将其编入索引。然后,您将能够使用索引扫描获取前 10 个分数。

于 2013-05-06T12:57:31.127 回答
0

首先,您不需要子查询,因此您可以将查询重写为:

SELECT `item_id`, SUM(vote) AS `sum` 
FROM `votes`
GROUP BY `item_id`
ORDER BY `a`.`sum` DESC
LIMIT 10

其次,您可以在votes(item_id, vote). 然后group by将是索引扫描。随着表变大,这需要一些时间,但对于合理的数据大小,它应该是可管理的。

最后,使用这种查询结构,您需要对最终的order by. 这是否有效取决于您拥有的物品数量。如果每个项目平均有一个或两个投票,那么这可能需要一些时间。如果您有一组固定的项目并且只有几百或几千个,那么即使数据量增加,也不应该成为性能瓶颈。

如果此摘要确实是您快速需要的东西,那么带有摘要表的触发器(如另一个答案中所述)提供了一种更快的检索方法。

于 2013-05-06T13:09:16.280 回答