2

假设我有以下数据结构:

DROP TABLE IF EXISTS `A`;
DROP TABLE IF EXISTS `B`;
DROP TABLE IF EXISTS `C`;

CREATE TABLE IF NOT EXISTS `C` (
    `ID_C`
        INT UNSIGNED
        NOT NULL
        AUTO_INCREMENT,

    PRIMARY KEY (`ID_C`)
)
ENGINE=InnoDB;

CREATE TABLE IF NOT EXISTS `B` (
    `ID_B`
        INT UNSIGNED
        NOT NULL
        AUTO_INCREMENT,

    `REF_C`
        INT UNSIGNED
        NOT NULL,

    PRIMARY KEY (`ID_B`),

    INDEX `FK_C` (`REF_C` ASC),

    CONSTRAINT `FK_C`
        FOREIGN KEY (`REF_C`)
        REFERENCES `C` (`ID_C`)
            ON DELETE CASCADE
            ON UPDATE CASCADE
) ENGINE=InnoDB;

CREATE TABLE IF NOT EXISTS `A` (
    `ID_A`
        INT UNSIGNED
        NOT NULL
        AUTO_INCREMENT,

    `REF_B`
        INT UNSIGNED
        NOT NULL,

    PRIMARY KEY (`ID_A`),

    INDEX `FK_B` (`REF_B` ASC),

    CONSTRAINT `FK_B`
        FOREIGN KEY (`REF_B`)
        REFERENCES `B` (`ID_B`)
            ON DELETE CASCADE
            ON UPDATE CASCADE
) ENGINE=InnoDB;

INSERT INTO `C`
    (`ID_C`)
VALUES
    (NULL),
    (NULL);

INSERT INTO `B`
    (`ID_B`, `REF_C`)
VALUES
    (NULL, 1),
    (NULL, 1),
    (NULL, 2),
    (NULL, 2);

INSERT INTO `A`
    (`ID_A`, `REF_B`)
VALUES
    (NULL, 1),
    (NULL, 2),
    (NULL, 3),
    (NULL, 4);

B可能有超过 3000 条记录:大约 600 条记录,引用到 table 中的不同行C。我的服务器上启用了两个设置:

SELECT
    @@SQL_SAFE_UPDATES as `safe mode`, -- result: 1
    @@LOG_BIN as `binary log`;         -- result: 1

问题: 如何有效地删除 table 中的所有记录,通过 tableA引用 tableC的记录B而不发出警告?


我尝试了什么:

DELETE
    `A`.*
FROM
    `A` INNER JOIN `B` ON `REF_B` = `ID_B`
WHERE
    `B`.`REF_C` = 1;

DBMS 服务器发出safe_mode错误:

错误代码:1175。您正在使用安全更新模式,并且您尝试更新没有使用 KEY 列的 WHERE 的表 要禁用安全模式,请切换 Preferences -> SQL Queries 中的选项并重新连接。

我已删除B.别名:

DELETE
    `A`.*
FROM
    `A` INNER JOIN `B` ON `REF_B` = `ID_B`
WHERE
    `REF_C` = 1;

是的,它成功了,但我有这个:

2 行受影响,1 个警告:1592 使用语句格式将不安全语句写入二进制日志,因为 BINLOG_FORMAT = STATEMENT。从另一个表中选择后写入具有自动增量列的表的语句是不安全的,因为检索行的顺序决定了将写入哪些(如果有)行。此顺序无法预测,并且可能在主从服务器上有所不同。

另外,我试图强制使用 PRIMARY KEY:

DELETE
    `A`.*
FROM
    `A` INNER JOIN `B` ON `REF_B` = `ID_B`
WHERE
    `A`.`ID_A` > 0
    AND
    `REF_C` = 1;

但这也没有帮助。我对我的服务器做了什么坏事或邪恶的事吗?什么方法是正确的?我错过了什么吗?

提前致谢。任何帮助将不胜感激。


PS:我知道如何使用谷歌和搜索栏。这是我发现的:

  1. https://stackoverflow.com/questions/12275864/multiple-delete-not-working-with-inner-join

  2. http://tech-solutions4u.blogspot.ru/2012/09/mysql-multi-delete-issue-in-safe-mode.html

等等。我已经尝试过,但最后,我不喜欢禁用服务器功能(不是由我设置的)的想法,甚至“暂时......”。


编辑:

我知道,有一种解决方法可以保存GROUP_CONCAT(ID_B)在临时变量中并通过它的“标量”值执行删除:

SELECT GROUP_CONCAT(`ID_B`) INTO @tmp FROM `B` WHERE `REF_C` = 1;

DELETE FROM
    `A`
WHERE
    FIND_IN_SET(`REF_B`, @tmp)
    AND
    `ID_A` > 0;

但这将是近似600 * 5 = 3000字符,所以这个想法也不受欢迎。我的意思是,如果没有其他可能,这将是最后的选择。

4

1 回答 1

2

似乎有两个问题:

  1. safe updates开启了模式。这是--safe-update来自MySQL 文档的示例:

    对于初学者来说,一个有用的启动选项是 --safe-updates(或 --i-am-a-dummy,具有相同的效果)。这对于您可能已经发出 DELETE FROM tbl_name 语句但忘记了 WHERE 子句的情况很有帮助。通常,这样的语句会从表中删除所有行。

    如您所见,这种模式意味着您根本不知道 SQL,并随时准备好自取其辱。因此,此模式会在每个复杂查询上触发警告。如果您不想关闭模式,您唯一的选择是选择要删除的所有行的 id,然后在第二个查询中将 id 显式传递给WHEREdelete 语句的子句。同时,这种方法将解决第二个问题。

  2. 第二个错误代码为 1592的警告与将更改记录到二进制日志中完全不同。

    错误:1592 SQLSTATE:HY000 (ER_BINLOG_UNSAFE_STATEMENT)

    消息:以报表格式登录的报表可能不安全。

    我建议的上一个命令确实有一个问题,我没有指定ORDER BY子句而是使用了LIMIT. 这种情况下的顺序在理论上是未定义的,因此您可以删除该LIMIT子句,或显式指定顺序:

    DELETE FROM A USING
    table_a as A
    JOIN (
      SELECT a_id
      FROM table_a as A
      JOIN table_b as B ON A.ref_b = A.b_id
      WHERE B.ref_c = 1
      ORDER BY a_id
      LIMIT 1000
    ) a_ids ON A.a_id = a_ids.a_id;
    
于 2013-11-05T15:24:18.003 回答