我有两个InnoDB表:

文章

id     | title    | sum_votes
------------------------------
1      | art 1    | 5
2      | art 2    | 8
3      | art 3    | 35

投票

id     | article_id    | vote
------------------------------
1      | 1             | 1
2      | 1             | 2
3      | 1             | 2
4      | 2             | 10
5      | 2             | -2
6      | 3             | 10
7      | 3             | 15
8      | 3             | 12
9      | 3             | -2

当新记录插入 votes 表,我想更新 sum_votes 字段中 articles 通过计算所有选票的总和来表。

问题

如果sum()计算本身是一个非常沉重的方法,哪种方式更有效(votes 表具有700K记录)。

1.创建触发器

CREATE TRIGGER `views_on_insert`
AFTER INSERT
ON `votes`
FOR EACH ROW
BEGIN
   UPDATE `articles` SET
       sum_votes = (
           SELECT SUM(`vote`)
           FROM `votes`
           WHERE `id` = NEW.article_id
       )
    WHERE `id` = NEW.article_id;
END;

2.在我的应用程序中使用两个查询

SELECT SUM(`vote`) FROM `votes` WHERE `article_id` = 1;
UPDATE `articles` 
   SET sum_votes = <1st_query_result> 
 WHERE `id` = 1;

第一条方法似乎更干净,但是 选择查询运行的整个时间都会锁定表吗?

有帮助吗?

解决方案

关于并发问题,您有一个 '简单的' 防止第二种方法中任何并发问题的方法,交易内部在文章行中执行选择( For update 现在是隐式的)。同一文章上的任何并发插入物将无法获得相同的锁,并将等待您。

使用新的默认隔离级别,甚至在交易中使用序列化级别的情况下,您在交易结束之前不会在投票表上看到任何并发插入。所以你的款项应该保持连贯 或看起来连贯. 。但是,如果并发交易对同一文章插入投票,并在您面前提交(这是第二个看待您的插入),那么要进行的最后一次交易将覆盖柜台,您将失去1票。 因此,请在文章上使用Select执行一行锁定 (当然,在交易中完成您的工作)。这很容易测试,在MySQL上打开2个交互式会话,然后开始交易。

如果您使用触发器,则默认情况下是在交易中。但是我认为您应该在文章表上执行选择,以使同时触发器的隐式行锁定(难以测试)。

  • 不要忘记删除触发器。
  • 不要忘记更新触发器。
  • 如果您不使用触发器并留在代码中,请小心,每次插入/删除/更新查询都应在交易之前对相应的文章执行一行锁定。忘记一个人并不难。

最后一点:在开始交易之前进行更艰难的交易:

SET TRANSACTION ISOLATION LEVEL SERIALIZABLE;

这样,您就不需要文章上的行锁,MySQL将发现可能在同一行上写出潜在的写入,并会阻止其他交易直到完成。 但是不要使用先前的请求计算的内容. 。更新查询将等待文章上的锁定版本,当锁定是第一个交易发布时 COMMIT 计算 SUM 应该再次进行计数。因此,更新查询应包含 SUM 或增加。

update articles set nb_votes=(SELECT count(*) from vote) where id=2; 

在这里,您会看到MySQL很聪明,如果在同时进行插入时,则检测到2笔交易试图执行此操作的僵局。在序列化级别中,我没有找到一种获得错误价值的方法:

   SET TRANSACTION ISOLATION LEVEL SERIALIZABLE;
   BEGIN;
       insert into vote (...
       update articles set nb_votes=(
         SELECT count(*) from vote where article_id=xx
       ) where id=XX;
    COMMIT;

但是,准备好处理必须重做的破坏交易。

其他提示

尝试这个:

PHP:星级评级系统概念?

编辑: 更改模式以允许用户多次投票给同一图像:

drop table if exists image;
create table image
(
image_id int unsigned not null auto_increment primary key,
caption 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 image_vote;
create table image_vote
(
vote_id int unsigned not null auto_increment primary key,
image_id int unsigned not null,
user_id int unsigned not null,
score tinyint unsigned not null default 0,
key (image_id, user_id)
)
engine=innodb;

delimiter #

create trigger image_vote_after_ins_trig after insert on image_vote
for each row
begin
 update image set 
    num_votes = num_votes + 1,
    total_score = total_score + new.score,
    rating = total_score / num_votes  
 where 
    image_id = new.image_id;
end#

delimiter ;

insert into image (caption) values ('image 1'),('image 2'), ('image 3');

insert into image_vote (image_id, user_id, score) values
(1,1,5),(1,2,4),(1,3,3),(1,4,2),(1,5,1),(1,5,2),(1,5,3),
(2,1,2),(2,2,1),(2,3,4),(2,3,2),
(3,1,4),(3,5,2);

select * from image;
select * from image_vote;
许可以下: CC-BY-SA归因
不隶属于 StackOverflow
scroll top