我有两个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;
第一种方式看起来更干净,但会在SELECT查询运行的整个过程中锁定表吗?
答案 0 :(得分:5)
关于并发性问题,你有一个'easy'方法来防止第二种方法中的任何并发问题,在你的事务内部对文章行执行select(For update
是现在暗示)。同一篇文章中的任何并发插入都无法获得同样的锁定并等待你。
使用新的默认隔离级别,即使在事务中甚至没有使用序列化级别,在事务结束之前,您也不会在投票表上看到任何并发插入。所以你的SUM应保持连贯的或看起来像连贯的。但是如果一个并发事务在你之前插入对同一篇文章的投票并提交(并且第二个没有看到你的插入),那么最后提交的事务将覆盖该计数器并且你将放弃1票。 因此,使用之前的选择对文章执行行锁定(当然,在事务中完成工作)。它很容易测试,在MySQL上打开2个交互式会话并与BEGIN开始交易。
如果您使用触发器,则默认情况下您处于事务中。但我认为你应该在文章表上执行选择,以便为并发触发器运行创建一个隐式行锁(更难测试)。
最后一点:在开始使用交易之前进行更难的交易:
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;
但请准备好处理必须重做的违规交易。
答案 1 :(得分:1)
试试这个:
PHP: Star rating system concept?
编辑:更改架构以允许用户多次投票支持同一图片:
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;