mysql - 处理 ON INSERT 触发器时如何锁定 innodb 表?

标签 mysql sql triggers innodb table-locking

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

最佳答案

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

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

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

关于mysql - 处理 ON INSERT 触发器时如何锁定 innodb 表?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/4733773/

相关文章:

mysql - 如何创建用于插入/更新的数据库级触发器

SQL 使用触发器进行约束

MySQL (10.1.24-MariaDB) ORDER BY 条件自动转换为字符串

php - 在php中将多维数组中的数据插入MySQL

mysql - SQL:单个评论者留下的评论数

SQL INSERT(选择)使用基于另一列值的 CASE

mysql - 通过在第三个表上插入触发将行从一个表复制到另一个表

php - IMG SRC inside PHP with more PHP inside 如何

sql - 多对多关系中的最大值

php - SQL 中的插入查询花费了很多时间