如何提高投票系统的性能?

发布于 2024-11-07 13:31:34 字数 1482 浏览 0 评论 0原文

我有一个带有投票系统的网站(喜欢/不喜欢)。

该应用程序已由另一位开发人员开发,现在网站越来越大,性能受到认真考虑。

我有下表:

CREATE TABLE `vote` (
  `id` int(11) NOT NULL auto_increment,
  `article_id` int(11) NOT NULL,
  `token` varchar(64) collate utf8_unicode_ci NOT NULL,
  `type` int(1) NOT NULL,
  PRIMARY KEY  (`id`),
  KEY `article_id` (`article_id`)
) ENGINE=InnoDB;

令牌列用于标识每个用户/投票/日期,它是一个唯一的令牌,是用户指纹的一部分,允许他们投票一次并更改其投票类型。

最慢的查询之一如下:

SELECT count(*) AS `nb` FROM `vote` WHERE (token = '00123456789012345678901234567890');

当服务器未关闭时,有时需要近 10 秒才能返回。

我不能在这里使用缓存,因为我需要实时检查以允许或不允许投票并增加计数。

我无法更改太多应用程序逻辑,因为它依赖于应用程序中各处使用的太多依赖项(设计得很糟糕)。

因此,我正在寻找提高性能的选项,即使是一些性能。

编辑:我在令牌列上有一个索引

,大约有 2,000,000 行,并且所有令牌几乎都是唯一的


编辑

我根据您的所有建议运行了基准测试:

Top average queries
1. SELECT COUNT(*) AS nb FROM `vote` WHERE (`token` = '%s') completed in 2.19790604115 sec
2. SELECT COUNT(`id`) AS nb FROM `vote` WHERE (`token` = '%s') completed in 2.28792096376 sec 
3. SELECT COUNT(`id`) AS nb FROM `vote` WHERE (`token` = '%s') GROUP BY `token` completed in 2.3732401371 sec
4. SELECT COUNT(*) AS nb FROM `vote` WHERE (`token` = '%s') GROUP BY `token` completed in 2.57634830475 sec 

有时是第三个查询是最快的,但有时也是最差的。

我运行了 10 次,其中每个查询运行了 20 次

,我运行这个基准没有任何索引(id 上的索引除外),

这很奇怪,我认为 COUNT(id) 会加快了查询速度。

I've a website with a voting system (like/dislike).

The application has been developed by another developer, and now the website is getting bigger and bigger and performance is serious consideration.

i've the following table :

CREATE TABLE `vote` (
  `id` int(11) NOT NULL auto_increment,
  `article_id` int(11) NOT NULL,
  `token` varchar(64) collate utf8_unicode_ci NOT NULL,
  `type` int(1) NOT NULL,
  PRIMARY KEY  (`id`),
  KEY `article_id` (`article_id`)
) ENGINE=InnoDB;

The token column is used to identify each user/vote/date it is an unique token which is part of a user fingerprint to allow them to vote once and change their vote type.

One of the most slow query is the following:

SELECT count(*) AS `nb` FROM `vote` WHERE (token = '00123456789012345678901234567890');

It sometimes takes almost 10seconds to return when the server doesn't shutdown.

I can't use a cache here, because I need to check in a real time to allow or not the vote and increment the count.

I cannot change much application logic because it relies on too much dependancies used everywhere in the application (it was badly designed).

So I'm looking for options to improve, even a few, performance.

Edit: I've an index on token column

there are ~2,000,000 rows and all token are almost unique


EDIT:

I ran a benchmark with all your advises :

Top average queries
1. SELECT COUNT(*) AS nb FROM `vote` WHERE (`token` = '%s') completed in 2.19790604115 sec
2. SELECT COUNT(`id`) AS nb FROM `vote` WHERE (`token` = '%s') completed in 2.28792096376 sec 
3. SELECT COUNT(`id`) AS nb FROM `vote` WHERE (`token` = '%s') GROUP BY `token` completed in 2.3732401371 sec
4. SELECT COUNT(*) AS nb FROM `vote` WHERE (`token` = '%s') GROUP BY `token` completed in 2.57634830475 sec 

Sometimes the third query is the quickest but sometimes it's the worst.

I ran it 10 times where each query is run 20 times

I ran this benchmark WITHOUT any INDEXES (except one on id)

That's weird, I though the COUNT(id) would have speed up a bit the query.

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(4

绝不放开 2024-11-14 13:31:34

如果尚未对标记列建立索引,您应该考虑对其进行索引。

You should look at indexing the token column, if it isn't already indexed.

夜未央樱花落 2024-11-14 13:31:34

听起来您应该创建一个存储汇总数据的表。这样,查询不需要每次都进行完整计数,而只需从上次求和开始进行计数。 (根据您的完整系统,如果行永远不会被删除,您可能会有一个与以下非常相似的表)

CREATE TABLE `voteCounts` (
  `token` varchar(64) collate utf8_unicode_ci NOT NULL PRIMARY KEY,
  `count` int
) ENGINE=InnoDB;

然后,当您将一行插入投票时,您也可以调用

UPDATE voteCounts
set `count` = `count` +1
WHERE
token = '012345' ;

It sounds like you should create an table that stores the summed data. This way the query doesn't need to do a full count each time but just a count from the last time it was summed. (Depending on your full system and if rows are never deleted you could have a table very similar to the follow)

CREATE TABLE `voteCounts` (
  `token` varchar(64) collate utf8_unicode_ci NOT NULL PRIMARY KEY,
  `count` int
) ENGINE=InnoDB;

Then when you insert a row into vote you can also call

UPDATE voteCounts
set `count` = `count` +1
WHERE
token = '012345' ;
束缚m 2024-11-14 13:31:34

一般来说,您应该为大型表中经常运行的查询的 where 子句中使用的列添加索引。在您的示例查询中,您需要在标记列上添加一个。看起来您正在使用 MySQL 数据库,因此这是该数据库的创建表语句的重要部分:

CREATE TABLE `vote` (
..
  token varchar(64) collate utf8_unicode_ci NOT NULL,
  index token_ind (token),
..
) ENGINE=InnoDB;

In general, you should add indexes for columns in large tables that are used in where clauses of queries that are run often. In your example query, you'd need one on the token column. It looks like you are using MySQL database, so here's the important part of the create table statement for that database:

CREATE TABLE `vote` (
..
  token varchar(64) collate utf8_unicode_ci NOT NULL,
  index token_ind (token),
..
) ENGINE=InnoDB;
时间海 2024-11-14 13:31:34

我并没有真正关注您当前的实现,但是我对 99.99% 的投票系统使用的以下方法非常高效:

结果:

mysql> select * from article;
+------------+-----------+-----------+-------------+--------+
| article_id | title     | num_votes | total_score | rating |
+------------+-----------+-----------+-------------+--------+
|          1 | article 1 |         5 |          15 |   3.00 |
|          2 | article 2 |         3 |           7 |   2.33 |
|          3 | article 3 |         2 |           6 |   3.00 |
+------------+-----------+-----------+-------------+--------+
3 rows in set (0.00 sec)

mysql> select * from article_vote;
+------------+---------+-------+
| article_id | user_id | score |
+------------+---------+-------+
|          1 |       1 |     5 |
|          1 |       2 |     4 |
|          1 |       3 |     3 |
|          1 |       4 |     2 |
|          1 |       5 |     1 |
|          2 |       1 |     2 |
|          2 |       2 |     1 |
|          2 |       3 |     4 |
|          3 |       1 |     4 |
|          3 |       5 |     2 |
+------------+---------+-------+
10 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,
num_votes int unsigned not null default 0,
total_score int unsigned not null default 0,
rating decimal(8,2) 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 unsigned 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
 update article set 
    num_votes = num_votes + 1,
    total_score = total_score + new.score,
    rating = total_score / num_votes  
 where 
    article_id = new.article_id;
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,5),(1,2,4),(1,3,3),(1,4,2),(1,5,1),
(2,1,2),(2,2,1),(2,3,4),
(3,1,4),(3,5,2);

select * from article;
select * from article_vote;

希望它有所帮助: )

I havent really paid too much attention to your current implementation but the following method I use for 99.99% of voting systems is extremely performant:

Results:

mysql> select * from article;
+------------+-----------+-----------+-------------+--------+
| article_id | title     | num_votes | total_score | rating |
+------------+-----------+-----------+-------------+--------+
|          1 | article 1 |         5 |          15 |   3.00 |
|          2 | article 2 |         3 |           7 |   2.33 |
|          3 | article 3 |         2 |           6 |   3.00 |
+------------+-----------+-----------+-------------+--------+
3 rows in set (0.00 sec)

mysql> select * from article_vote;
+------------+---------+-------+
| article_id | user_id | score |
+------------+---------+-------+
|          1 |       1 |     5 |
|          1 |       2 |     4 |
|          1 |       3 |     3 |
|          1 |       4 |     2 |
|          1 |       5 |     1 |
|          2 |       1 |     2 |
|          2 |       2 |     1 |
|          2 |       3 |     4 |
|          3 |       1 |     4 |
|          3 |       5 |     2 |
+------------+---------+-------+
10 rows in set (0.00 sec)

Full script:

drop table if exists article;
create table article
(
article_id int unsigned not null auto_increment primary key,
title varchar(255) not null,
num_votes int unsigned not null default 0,
total_score int unsigned not null default 0,
rating decimal(8,2) 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 unsigned 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
 update article set 
    num_votes = num_votes + 1,
    total_score = total_score + new.score,
    rating = total_score / num_votes  
 where 
    article_id = new.article_id;
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,5),(1,2,4),(1,3,3),(1,4,2),(1,5,1),
(2,1,2),(2,2,1),(2,3,4),
(3,1,4),(3,5,2);

select * from article;
select * from article_vote;

Hope it helps :)

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文