MySQL仅在行已更改时才更新

jen*_*ens 63 mysql sql database triggers

是否有可能仅在数据被真正改变的情况下使用"更新后"触发器.我知道"新旧".但是在使用它们时我只能比较列.例如"NEW.count <> OLD.count".

但我想要的是:如果"NEW <> OLD"则运行触发器

一个例子:

create table foo (a INT, b INT);
create table bar (a INT, b INT);

INSERT INTO foo VALUES(1,1);
INSERT INTO foo VALUES(2,2);
INSERT INTO foo VALUES(3,3);

CREATE TRIGGER ins_sum
    AFTER UPDATE ON foo
    FOR EACH ROW
    INSERT INTO bar VALUES(NEW.a, NEW.b);

UPDATE foo SET b = 3 WHERE a=3;
Query OK, 0 rows affected (0.00 sec)
Rows matched: 1  Changed: 0  Warnings: 0


select * from bar;
+------+------+
| a    | b    |
+------+------+
|    3 |    3 |
+------+------+
Run Code Online (Sandbox Code Playgroud)

关键是,有一个更新,但没有任何改变.但无论如何都触发了触发器.恕我直言应该有一种方式它没有.

我知道我可以使用

如果NOW.b <> OLD.b

对于这个例子.

想象一下有一个变化列的大桌子.您必须比较每一列,如果数据库发生更改,则必须调整触发器.并且比较硬编码的行的每一列并不"感觉"好:)

加成

正如你所看到的那样

匹配的行数:1已更改:0警告:0

MySQL知道该行没有改变.但它不会与触发器分享这些知识.像"AFTER REAL UPDATE"之类的触发器或类似的东西会很酷.

Inc*_*nca 70

作为一种变通方法,您可以使用时间戳(旧的和新的)进行检查,但是当行没有更改时,不会更新时间戳.(可能这是混淆的原因?因为那个也被称为'更新'但是在没有发生变化时不执行)一秒内的变化将不会执行触发器的那一部分,但在某些情况下可能没问题(就像你有一个拒绝快速变化的应用程序一样.)

例如,而不是

IF NEW.a <> OLD.a or NEW.b <> OLD.b /* etc, all the way to NEW.z <> OLD.z */ 
THEN  
  INSERT INTO bar (a, b) VALUES(NEW.a, NEW.b) ;
END IF
Run Code Online (Sandbox Code Playgroud)

你可以用

IF NEW.ts <> OLD.ts 
THEN  
  INSERT INTO bar (a, b) VALUES(NEW.a, NEW.b) ;
END IF
Run Code Online (Sandbox Code Playgroud)

然后,每次更新方案时都不必更改触发器(问题中提到的问题.)

编辑:添加完整的示例

create table foo (a INT, b INT, ts TIMESTAMP);
create table bar (a INT, b INT);

INSERT INTO foo (a,b) VALUES(1,1);
INSERT INTO foo (a,b) VALUES(2,2);
INSERT INTO foo (a,b) VALUES(3,3);

DELIMITER ///

CREATE TRIGGER ins_sum AFTER UPDATE ON foo
    FOR EACH ROW
    BEGIN
        IF NEW.ts <> OLD.ts THEN  
            INSERT INTO bar (a, b) VALUES(NEW.a, NEW.b);
        END IF;
    END;
///

DELIMITER ;

select * from foo;
+------+------+---------------------+
| a    | b    | ts                  |
+------+------+---------------------+
|    1 |    1 | 2011-06-14 09:29:46 |
|    2 |    2 | 2011-06-14 09:29:46 |
|    3 |    3 | 2011-06-14 09:29:46 |
+------+------+---------------------+
3 rows in set (0.00 sec)

-- UPDATE without change
UPDATE foo SET b = 3 WHERE a = 3;
Query OK, 0 rows affected (0.00 sec)
Rows matched: 1  Changed: 0  Warnings: 0

-- the timestamo didnt change
select * from foo WHERE a = 3;
+------+------+---------------------+
| a    | b    | ts                  |
+------+------+---------------------+
|    3 |    3 | 2011-06-14 09:29:46 |
+------+------+---------------------+
1 rows in set (0.00 sec)

-- the trigger didn't run
select * from bar;
Empty set (0.00 sec)

-- UPDATE with change
UPDATE foo SET b = 4 WHERE a=3;
Query OK, 1 row affected (0.00 sec)
Rows matched: 1  Changed: 1  Warnings: 0

-- the timestamp changed
select * from foo;
+------+------+---------------------+
| a    | b    | ts                  |
+------+------+---------------------+
|    1 |    1 | 2011-06-14 09:29:46 |
|    2 |    2 | 2011-06-14 09:29:46 |
|    3 |    4 | 2011-06-14 09:34:59 |
+------+------+---------------------+
3 rows in set (0.00 sec)

-- and the trigger ran
select * from bar;
+------+------+---------------------+
| a    | b    | ts                  |
+------+------+---------------------+
|    3 |    4 | 2011-06-14 09:34:59 |
+------+------+---------------------+
1 row in set (0.00 sec)
Run Code Online (Sandbox Code Playgroud)

由于mysql处理时间戳的行为,它正在工作.只有在更新中发生更改时,才会更新时间戳.

文档在这里:https:
//dev.mysql.com/doc/refman/5.7/en/timestamp-initialization.html

desc foo;
+-------+-----------+------+-----+-------------------+-----------------------------+
| Field | Type      | Null | Key | Default           | Extra                       |
+-------+-----------+------+-----+-------------------+-----------------------------+
| a     | int(11)   | YES  |     | NULL              |                             |
| b     | int(11)   | YES  |     | NULL              |                             |
| ts    | timestamp | NO   |     | CURRENT_TIMESTAMP | on update CURRENT_TIMESTAMP |
+-------+-----------+------+-----+-------------------+-----------------------------+
Run Code Online (Sandbox Code Playgroud)

  • 谢谢。您的建议有效。我添加了一个完整的例子。 (2认同)
  • 如果更新频率低于一秒,这将不起作用。时间戳将会改变(但值相同)。您必须使用足够准确的时间戳(6)来跟踪所有更新 (2认同)
  • 从我的回答来看,不要忘记使用 **&lt;=&gt;** (null 感知运算符)来 **跟踪从 null 到 null 的更改** (2认同)

Den*_*rdy 15

想象一下有一个变化列的大桌子.您必须比较每一列,如果数据库发生更改,则必须调整触发器.并且比较每行硬编码并不"感觉"好:)

是的,但这是继续进行的方式.

作为旁注,在更新之前进行先发制人检查也是一种好习惯:

UPDATE foo SET b = 3 WHERE a=3 and b <> 3;
Run Code Online (Sandbox Code Playgroud)

在您的示例中,这将使它更新(并因此覆盖)行而不是三行.

  • @Johan:这是必需的,MySQL不这样做.它不会尊重SQL标准 - 如果它确实如此 - 并且OP首先不会问他的问题. (5认同)

Wax*_*age 12

我不能发表评论,所以请注意,如果你的列支持NULL值,那么OLD.x <> NEW.x就不够了,因为

SELECT IF(1<>NULL,1,0)

返回0与...相同

NULL<>NULL 1<>NULL 0<>NULL 'AAA'<>NULL
Run Code Online (Sandbox Code Playgroud)

因此它不会跟踪FROM和TO NULL的变化

这种情况下的正确方法是

((OLD.x IS NULL AND NEW.x IS NOT NULL) OR (OLD.x IS NOT NULL AND NEW.x IS NULL) OR (OLD.x<>NEW.x))
Run Code Online (Sandbox Code Playgroud)

  • 或者只是使用mysql null aware比较运算符`<=>`. (14认同)
  • 或者你可以使用`COALESCE()`,它返回它的第一个不是`NULL`的参数。所以你可以把它写成`IF COALESCE(OLD.X,'') &lt;&gt; COALESCE(NEW.X,'')` (2认同)

use*_*118 9

您可以通过使用NULL-safe equals运算符<=>比较每个字段然后使用否定结果来完成此操作NOT.

完整的触发器将变为:

DROP TRIGGER IF EXISTS `my_trigger_name`;

DELIMITER $$

CREATE TRIGGER `my_trigger_name` AFTER UPDATE ON `my_table_name` FOR EACH ROW 
    BEGIN
        /*Add any fields you want to compare here*/
        IF !(OLD.a <=> NEW.a AND OLD.b <=> NEW.b) THEN
            INSERT INTO `my_other_table` (
                `a`,
                 `b`
            ) VALUES (
                NEW.`a`,
                NEW.`b`
            );
        END IF;
    END;$$

DELIMITER ;
Run Code Online (Sandbox Code Playgroud)

(根据不同答案.)