【问题标题】:How innodb tables are locked when ON INSERT trigger is processed?处理 ON INSERT 触发器时如何锁定 innodb 表?
【发布时间】:2011-01-19 09:49:07
【问题描述】:

我有两个 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 表中插入一条新记录时,我想通过计算所有投票的总和来更新articles 表中的sum_votes 字段。

问题

如果 SUM() 计算本身非常繁重(votes 表有 70 万条记录),哪种方式更有效。

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 查询运行的整个过程中锁定表吗?

【问题讨论】:

    标签: mysql sql triggers innodb table-locking


    【解决方案1】:

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

    使用新的默认隔离级别,即使在事务中不使用序列化级别,您也不会在事务结束之前看到投票表上的任何并发插入。因此,您的 SUM 应该保持一致或看起来一致。但是,如果一个并发事务对同一篇文章进行投票并在您之前提交(而第二个事务没有看到您的插入),最后提交的事务将覆盖计数器,您将失去 1 票。 所以在文章上使用 select before 来执行行锁定(当然是在事务中做你的工作)。它很容易测试,在 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;
    

    但要准备好处理必须重做的中断事务。

    【讨论】:

      【解决方案2】:

      试试这个:

      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;
      

      【讨论】:

      • 在给定的示例中,不需要每次都计算 SUM(),因为每条记录只有一票。他们只是在每个插入上都做了'+1'。我有不同的情况。
      • 您可以更改约束条件,以便用户可以对同一张图像进行多次投票 - 这不是问题。 (total_score = total_score + new.score...)
      • @silver - sum 是所有分数的总和, votes 是所有票的总和!然后根据这些值计算评级!
      • @silver,您为什么希望用户可以多次投票?这样你的系统就会损坏,排名不准确?
      • @Tricker,投票权重不同,取决于用户的排名。
      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 2011-07-15
      • 2018-07-05
      • 1970-01-01
      • 2013-04-09
      • 1970-01-01
      • 1970-01-01
      • 2017-01-17
      相关资源
      最近更新 更多