MySQL 5.5.30级联触发器无效

时间:2013-04-11 13:57:01

标签: mysql dml triggers

出于某种原因,在MySQL 5.5.30机器上,从第二个表中删除行的触发器不再触发第二个表上的删除触发器。

这完全适用于我们的本地MySQL版本5.5.25

我没有找到任何可以解释这种行为的文档,有人可能有同等的问题吗?

这可能是MySQL版本大于5.5.25时出现的错误,也可能是意外启用的“功能”。

UPDATE table1 => fires BEFORE UPDATE trigger ON table1
      table1 BEFORE UPDATE TRIGGER executes: DELETE FROM table2 => should fire BEFORE DELETE trigger on table2 ( but doesn't )
            table 2 BEFORE DELETE TRIGGER executes: DELETE FROM table3 (never happens)

好了,我的重现步骤:

数据库

CREATE DATABASE "triggerTest" DEFAULT CHARACTER SET utf8 COLLATE utf8_unicode_ci;

表格

CREATE TABLE "table1" (
  "id" int(11) NOT NULL AUTO_INCREMENT,
  "active" tinyint(1) NOT NULL DEFAULT '0',
  "sampleData" varchar(100) COLLATE utf8_unicode_ci NOT NULL DEFAULT '',
  PRIMARY KEY ("id")
) ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_unicode_ci ROW_FORMAT=DYNAMIC;


CREATE TABLE "table2" (
  "id" int(11) NOT NULL AUTO_INCREMENT,
  "table1_id" int(11) NOT NULL DEFAULT '0',
  PRIMARY KEY ("id"),
  CONSTRAINT "test2_fk_table1_id" FOREIGN KEY ("table1_id") REFERENCES "table1" ("id") ON DELETE CASCADE ON UPDATE CASCADE  
) ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_unicode_ci ROW_FORMAT=DYNAMIC;


CREATE TABLE "table3" (
  "id" int(11) NOT NULL AUTO_INCREMENT,
  "table1_id" int(11) NOT NULL DEFAULT '0',
  PRIMARY KEY ("id"),
  CONSTRAINT "test3_fk_table1_id" FOREIGN KEY ("table1_id") REFERENCES "table1" ("id") ON DELETE CASCADE ON UPDATE CASCADE  
) ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_unicode_ci ROW_FORMAT=DYNAMIC;

触发器

DELIMITER $$

CREATE TRIGGER "table1_rtrg_AI" AFTER INSERT ON "table1" FOR EACH ROW
BEGIN
    IF NEW."active" THEN
        INSERT INTO "table2" ( "table1_id" ) SELECT NEW."id";
    END IF;
END$$

CREATE TRIGGER "table1_rtrg_BU" BEFORE UPDATE ON "table1" FOR EACH ROW
BEGIN
    IF NOT NEW."active" AND OLD."active" THEN
        DELETE FROM "table2" WHERE "table1_id" = OLD."id";
    END IF;

    IF NEW."active" AND NOT OLD."active" THEN
        INSERT INTO "table2" ( "table1_id" ) SELECT NEW."id";
    END IF;
END$$

CREATE TRIGGER "table2_rtrg_AI" AFTER INSERT ON "table2" FOR EACH ROW
BEGIN
    INSERT INTO "table3" ( "table1_id" ) SELECT NEW."table1_id";
END$$

CREATE TRIGGER "table2_rtrg_BD" BEFORE DELETE ON "table2" FOR EACH ROW
BEGIN
    DELETE FROM "table3" WHERE "table1_id" = OLD."table1_id";
END$$

DELIMITER ;

问:为什么要使用双引号引用标识符? (而不是反叛)

因为我不喜欢“利基语法”

    mysql> show variables LIKE 'sql_mode';
+---------------+------------------------------------------------------------------------------------------------------------------------------------------------------+
| Variable_name | Value                                                                                                                                                |
+---------------+------------------------------------------------------------------------------------------------------------------------------------------------------+
| sql_mode      | PIPES_AS_CONCAT,**ANSI_QUOTES**,IGNORE_SPACE,NO_UNSIGNED_SUBTRACTION,STRICT_ALL_TABLES,NO_ZERO_IN_DATE,ERROR_FOR_DIVISION_BY_ZERO,NO_ENGINE_SUBSTITUTION |
+---------------+------------------------------------------------------------------------------------------------------------------------------------------------------+
1 row in set (0.00 sec)

测试用例1:预期行为(数据库版本5.2.20)

mysql> SELECT VERSION();
+-----------+
| VERSION() |
+-----------+
| 5.5.20    |
+-----------+
1 row in set (0.00 sec)

mysql> SET GLOBAL general_log := ON;

测试插入触发器

mysql> INSERT INTO "table1" ( "active", "sampleData" ) SELECT 0, 'sample data row 1';
Query OK, 1 row affected (0.00 sec)
Records: 1  Duplicates: 0  Warnings: 0

general_log: 
130423 12:51:27 78010 Query     INSERT INTO "table1" ( "active", "sampleData" ) SELECT 0, 'sample data row 1'


mysql> INSERT INTO "table1" ( "active", "sampleData" ) SELECT 1, 'sample data row 2';
Query OK, 1 row affected (0.00 sec)
Records: 1  Duplicates: 0  Warnings: 0

general_log:
130423 12:51:33 78010 Query     INSERT INTO "table1" ( "active", "sampleData" ) SELECT 1, 'sample data row 2'
                78010 Query     INSERT INTO "table2" ( "table1_id" ) SELECT NEW."id"
                78010 Query     INSERT INTO "table3" ( "table1_id" ) SELECT NEW."table1_id"

预期的表格内容:

mysql> SELECT * FROM "table1";
+----+--------+-------------------+
| id | active | sampleData        |
+----+--------+-------------------+
|  1 |      0 | sample data row 1 |
|  2 |      1 | sample data row 2 |
+----+--------+-------------------+
2 rows in set (0.00 sec)

mysql> SELECT * FROM "table2";
+----+-----------+
| id | table1_id |
+----+-----------+
|  1 |         2 |
+----+-----------+
1 row in set (0.00 sec)

mysql> SELECT * FROM "table3";
+----+-----------+
| id | table1_id |
+----+-----------+
|  1 |         2 |
+----+-----------+
1 row in set (0.00 sec)

测试更新触发器,设置有效

mysql> UPDATE "table1" SET "active" = 1 WHERE "id" = 1;
Query OK, 1 row affected (0.00 sec)
Rows matched: 1  Changed: 1  Warnings: 0

query_log:
130423 12:52:15 78010 Query     UPDATE "table1" SET "active" = 1 WHERE "id" = 1
                78010 Query     INSERT INTO "table2" ( "table1_id" ) SELECT NEW."id"
                78010 Query     INSERT INTO "table3" ( "table1_id" ) SELECT NEW."table1_id"

预期的表格内容:

mysql> SELECT * FROM "table1";
+----+--------+-------------------+
| id | active | sampleData        |
+----+--------+-------------------+
|  1 |      1 | sample data row 1 |
|  2 |      1 | sample data row 2 |
+----+--------+-------------------+
2 rows in set (0.00 sec)

mysql> SELECT * FROM "table2";
+----+-----------+
| id | table1_id |
+----+-----------+
|  2 |         1 |
|  1 |         2 |
+----+-----------+
2 rows in set (0.00 sec)

mysql> SELECT * FROM "table3";
+----+-----------+
| id | table1_id |
+----+-----------+
|  2 |         1 |
|  1 |         2 |
+----+-----------+
2 rows in set (0.00 sec)

测试更新触发器,设置无效

mysql> UPDATE "table1" SET "active" = 0 WHERE "id" = 2;
Query OK, 1 row affected (0.01 sec)
Rows matched: 1  Changed: 1  Warnings: 0

query_log:

130423 12:52:49 78010 Query     UPDATE "table1" SET "active" = 0 WHERE "id" = 2
                78010 Query     DELETE FROM "table2" WHERE "table1_id" = NEW."id"
                78010 Query     DELETE FROM "table3" WHERE "table1_id" = OLD."table1_id"

预期的表格内容:

mysql> SELECT * FROM "table1";
+----+--------+-------------------+
| id | active | sampleData        |
+----+--------+-------------------+
|  1 |      1 | sample data row 1 |
|  2 |      0 | sample data row 2 |
+----+--------+-------------------+
2 rows in set (0.00 sec)

mysql> SELECT * FROM "table2";
+----+-----------+
| id | table1_id |
+----+-----------+
|  2 |         1 |
+----+-----------+
1 row in set (0.00 sec)

mysql> SELECT * FROM "table3";
+----+-----------+
| id | table1_id |
+----+-----------+
|  2 |         1 |
+----+-----------+
1 row in set (0.00 sec)

Testcase2:意外行为(MySQL版本5.5.30)

神圣触发grml - 你知道吗?遗憾的是我没有先测试第二个案例 - 遗憾的是我无法重现错误..测试工作在5.5.30上,会让你更新:)

修改 触发器没有级联,因为一个未知的定义器保留在为生产而生成的sql转储中。在触发器转储中删除DEFINER =(替代解决方案是创建用户或将DEFINER =更改为现有的)解决了问题,解决了他的一部分问题。

未知定义者未导致任何日志文件输出

3 个答案:

答案 0 :(得分:8)

最后的结论:MySQL 5.5.30在这种情况下没有错误,也没有错误配置服务器本身。

一些自制的错误导致了这个问题:

错误I:DEFINER用户不存在

我不是仅仅在生产机器上生成数据库,而是懒惰并将测试数据库转储到生产机器上。如果您未在DEFINER语句中明确设置CREATE TRIGGER,则会将其设置为CURRENT_USER。不幸的是,我的测试机器上的这个确切CURRENT_USER在生产服务器上不存在。

错误二:懒惰

mysqldump使用DEFINER转储触发器定义并创建触发器应该生成警告但是再一次,我很懒,做了类似的事情......

mysqldump --triggers --routines -h test -p database | gzip -3 | ssh production "gunzip -c | mysql -h production_database_host -p production_database"

这个看起来很酷(omg geek)并为你节省了大量的转储文件,但是它会压缩你从控制台加载转储时可能会看到的警告

MySQL编写了以下关于触发器定义器的内容:

  

如果指定DEFINER子句,则这些规则将确定合法性   DEFINER用户值:

     

如果您没有SUPER权限,则唯一的合法用户值是   您自己的帐户,无论是字面指定还是使用CURRENT_USER。   您无法将定义者设置为其他帐户。

     

如果您拥有SUPER权限,则可以在语法上指定任何权限   合法帐户名称。如果帐户实际上不存在,则发出警告   是生成的。

     

尽管可以使用不存在的DEFINER创建触发器   帐户,直到激活此类触发器并不是一个好主意   该帐户确实存在。否则,尊重行为   特权检查未定义。

     

来源:http://dev.mysql.com/doc/refman/5.5/en/create-trigger.html

错误III:懒惰

我有一个非常酷的mysqldump包装器,它能够生成干净,可重用的转储文件。在没有DEFINER的情况下覆盖触发器时,我在生产服务器上打开了一个控制台事务(锁定表2),因此table2上的触发器根本没有更新,但是因为我的数据sql管道超过5台服务器我没有看到超时错误。

<强>结论:

没有错误,只是触发器没有正确创建..

有时候你应该停下来懒惰,让重要的事情更多的时间,注意可以节省你很多时间 !!

答案 1 :(得分:1)

MySQL中的触发器(与存储过程不同)始终在DEFINER的上下文中运行。触发器似乎不起作用,因为DEFINER没有执行部分或全部触发器的权限。特别是,在MySQL 5.1及更高版本中,DEFINER需要具有TRIGGER权限以及相关的SELECT和/或UPDATE权限。

当触发器似乎不起作用时,请检查权限。

答案 2 :(得分:0)

请注意,外键操作不会触发触发器,请参见Restrictions for Triggers

外键动作不会激活触发器。

这意味着级联删除外键将不会激活任何“ ON DELETE”触发器...