如何优化该查询?

时间:2011-10-25 22:20:05

标签: mysql sql performance optimization

我的投票系统设计如下:

CREATE TABLE `vote` (
  `id` int(11) NOT NULL AUTO_INCREMENT,
  `weight` int(11) NOT NULL,
  `submited_date` datetime NOT NULL,
  `resource_type` varchar(255) NOT NULL,
  PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=2963832 DEFAULT CHARSET=latin1;

CREATE TABLE `article_preselection_vote` (
  `id` int(11) NOT NULL,
  `article_id` int(11) DEFAULT NULL,
  `user_id` int(11) DEFAULT NULL,
  PRIMARY KEY (`id`),
  KEY `IDX_9B145DEA62922701` (`article_id`),
  KEY `IDX_9B145DEAA76ED395` (`user_id`),
  CONSTRAINT `article_preselection_vote_ibfk_4` FOREIGN KEY (`article_id`) REFERENCES `article` (`id`),
  CONSTRAINT `article_preselection_vote_ibfk_5` FOREIGN KEY (`id`) REFERENCES `vote` (`id`) ON DELETE CASCADE,
  CONSTRAINT `article_preselection_vote_ibfk_6` FOREIGN KEY (`user_id`) REFERENCES `user` (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=latin1;

v.weight可以是+1或-1,我需要,给出一堆文章ID,以获得每篇正面投票的总和(+1)和每篇文章的负面投票总和(-1)标识。

然后我的结果应该是

article_id | vote_up | vote_down
-----------|---------|----------
    1      |    36   |     20
-----------|---------|----------
    68     |    12   |     56
-----------|---------|----------
    25     |    90   |     12
-----------|---------|----------

我可以通过执行以下请求来获得该结果,但是在2,000,000票的投票中这是非常沉重和缓慢的。

SELECT apv.article_id, COALESCE(SUM(up),0) as up, COALESCE(SUM(down),0) as down 
FROM article_preselection_vote apv 
LEFT JOIN(
    SELECT id, weight up FROM vote WHERE weight > 0 AND vote.resource_type = 'article') v1 ON apv.id = v1.id 
LEFT JOIN(
    SELECT id, weight down FROM vote WHERE weight < 0 AND vote.resource_type = 'article') v2 ON apv.id = v2.id 
WHERE apv.article_id IN (11702,11703,11704,11632,11652,11658)
GROUP BY apv.article_id

有什么想法吗?

提前致谢。

4 个答案:

答案 0 :(得分:2)

一个查询中的子选择IN (...)GROUP BY是杀手。

你应该重新设计一个更传统的解决方案:

  1. 拥有一张包含投票article_id, votes_up, votes_down, vote_date, ...
  2. 的表格
  3. 使用一个votes_up, votes_down, ...更新(cron)文章表UPDATE中的摘要字段。
  4. 这样,您可以更好地处理行/表锁并具有快速查询

答案 1 :(得分:1)

您可以尝试一次加入:

SELECT
    apv.article_id,
    SUM(COALESCE(weight, 0) > 0) AS up,
    SUM(COALESCE(weight, 0) < 0) AS down 
FROM article_preselection_vote apv 
LEFT JOIN vote
    ON apv.id = vote.id
    AND vote.resource_type = 'article'
WHERE apv.article_id IN (11702, 11703, 11704, 11632, 11652, 11658)
GROUP BY apv.article_id

如果您需要经常计算,可能值得对数据库进行非规范化并存储结果的缓存副本。

答案 2 :(得分:0)

为什么不创建两个表,一个用于投票而一个用于投票,而不是加权投票?它唯一复杂的是投票组合,它仍然只是两个不同查询计数的简单总和。

答案 3 :(得分:0)

坚果壳中的

做类似的事情:

select * from article where article_id in (1,2,3);
+------------+-----------+---------------+-----------------+
| article_id | title     | up_vote_count | down_vote_count |
+------------+-----------+---------------+-----------------+
|          1 | article 1 |             2 |               3 |
|          2 | article 2 |             2 |               1 |
|          3 | article 3 |             1 |               1 |
+------------+-----------+---------------+-----------------+
3 rows in set (0.00 sec)


drop table if exists article;
create table article
(
article_id int unsigned not null auto_increment primary key,
title varchar(255) not null,
up_vote_count int unsigned not null default 0,
down_vote_count int unsigned not null default 0
)
engine = innodb;

drop table if exists article_vote;
create table article_vote
(
article_id int unsigned not null,
user_id int unsigned not null,
score tinyint not null default 0,
primary key (article_id, user_id)
)
engine=innodb;

delimiter #

create trigger article_vote_after_ins_trig after insert on article_vote
for each row
begin
 if new.score < 0 then
  update article set down_vote_count = down_vote_count + 1 where article_id = new.article_id;
 else
  update article set up_vote_count = up_vote_count + 1 where article_id = new.article_id;
 end if;
end#

delimiter ;

insert into article (title) values ('article 1'),('article 2'), ('article 3');

insert into article_vote (article_id, user_id, score) values
(1,1,-1),(1,2,-1),(1,3,-1),(1,4,1),(1,5,1),
(2,1,1),(2,2,1),(2,3,-1),
(3,1,1),(3,5,-1);

select * from article where article_id in (1,2,3);