简体   繁体   English

在 MySQL 中模拟 DELETE CASCADE?

[英]Simulate a DELETE CASCADE in MySQL?

Is it possible to predict the operations that follow a DELETE CASCADE automatically?是否可以自动预测 DELETE CASCADE 之后的操作? In my software I would like to give the user a warning with details about the data that would be deleted then.在我的软件中,我想向用户发出警告,其中包含有关将被删除的数据的详细信息。

You can make a copy of the database and put triggers on the after delete您可以复制数据库并after delete放置触发器

DELIMITER $$

CREATE TRIGGER ad_table1_each AFTER DELETE ON table1 FOR EACH ROW
BEGIN
  INSERT INTO log VALUES (null                 /*autoinc id*/
        , 'table1'                             /*tablename*/
        , old.id                               /*tableid*/
        , concat_ws(',',old.field1,old.field2  /*CSV's of fields*/
        , NOW()                                /*timestamp*/
        , 'delete');                           /*what action*/


  REPLACE INTO restore_table1 VALUES (old.id,
        , old.field1
        , old.field2
        , ... );

END $$

DELIMITER ;

The log table is just a table with the following fields:日志表只是一个包含以下字段的表:

id            integer autoincrement primary key
tablename     varchar(45)
table_id      integer
fields        varchar(6000)
delete_time   timestamp
action        enum('insert','update','delete')

If you do a SELECT @last_id:= max(id) FROM log before the delete cascade on the copy.如果在副本上的删除级联之前执行SELECT @last_id:= max(id) FROM log
Then you can do a SELECT * FROM log WHERE id > @last_id然后你可以做一个SELECT * FROM log WHERE id > @last_id
and get all the rows that will be deleted in the cascade.并获取将在级联中删除的所有行。

After that you can use the restore_table1 to recreate the rows that were deleted in the cascade in the copy database.之后,您可以使用 restore_table1 在副本数据库中重新创建在级联中删除的行。

I think you could use Johan's trigger solution in combination with a transaction that you roll back.我认为您可以将 Johan 的触发器解决方案与您回滚的事务结合使用。 This avoids both the need for a second database and for the manual restore of the deleted entries.这既避免了对第二个数据库的需要,也避免了手动恢复已删除条目的需要。

  • add the trigger and the log table添加触发器和日志表
  • for each attempted deletion start a transaction and delete the entries为每次尝试删除启动一个事务并删除条目
  • present the information from the log to your user for approval将日志中的信息呈现给您的用户以供批准
  • if the user agrees commit the transaction, otherwise rollback如果用户同意提交事务,否则回滚

I wrote a very quick hack that does exactly what you need in PHP, since I wanted to do the exact same thing and haven't found any resources for that online.我写了一个非常快速的 hack,它完全可以满足您在 PHP 中的需求,因为我想做完全相同的事情并且还没有在网上找到任何相关资源。

It might be too late for you, but it may help others.对你来说可能为时已晚,但它可能会帮助其他人。

function get_referencing_foreign_keys ($database, $table) {
    $query = 'SELECT TABLE_SCHEMA, TABLE_NAME, COLUMN_NAME, REFERENCED_COLUMN_NAME FROM INFORMATION_SCHEMA.KEY_COLUMN_USAGE WHERE REFERENCED_TABLE_SCHEMA = "'.$database.'" AND REFERENCED_TABLE_NAME = '.esc($table);
    $result = rquery($query);
    $foreign_keys = array();
    while ($row = mysql_fetch_row($result)) {
        $foreign_keys[] = array('database' => $row[0], 'table' => $row[1], 'column' => $row[2], 'reference_column' => $row[3]);
    }

    return $foreign_keys;
}

function get_foreign_key_deleted_data_html ($database, $table, $where) {
    $data = get_foreign_key_deleted_data ($database, $table, $where);

    $html = '';
    foreach ($data as $key => $this_data) {
        $html .= "<h2>$key</h2>\n";

        $html .= "<table>\n";
        $i = 0;
        foreach ($this_data as $value) {
            if($i == 0) {
                $html .= "\t<tr>\n";
                foreach ($value as $column => $column_value) {
                    $html .= "\t\t<th>".htmlentities($column)."</th>\n";
                }
                $html .= "\t</tr>\n";
            }
            $html .= "\t<tr>\n";
            foreach ($value as $column => $column_value) {
                $html .= "\t\t<td>".htmlentities($column_value)."</td>\n";
            }
            $html .= "\t</tr>\n";
            $i++;
        }
        $html .= "</table>\n";
    }

    return $html;
}

function get_foreign_key_deleted_data ($database, $table, $where) {
    $GLOBALS['get_data_that_would_be_deleted'] = array();
    $data = get_data_that_would_be_deleted($database, $table, $where);
    $GLOBALS['get_data_that_would_be_deleted'] = array();
    return $data;
}

function get_data_that_would_be_deleted ($database, $table, $where, $recursion = 100) {
    if($recursion <= 0) {
        die("Deep recursion!");
    }

    if($recursion == 100) {
        $GLOBALS['get_data_that_would_be_deleted'] = array();
    }

    if($table) {
        if(is_array($where)) {
            $foreign_keys = get_referencing_foreign_keys($database, $table);
            $data = array();

            $query = 'SELECT * FROM `'.$table.'`';
            if(count($where)) {
                $query .= ' WHERE 1';
                foreach ($where as $name => $value) {
                    $query .= " AND `$name` = ".esc($value);
                }
            }
            $result = rquery($query);

            $to_check = array();

            while ($row = mysql_fetch_row($result)) {
                $new_row = array();
                $i = 0;
                foreach ($row as $this_row) {
                    $field_info = mysql_fetch_field($result, $i);
                    $new_row[$field_info->name] = $this_row;
                    foreach ($foreign_keys as $this_foreign_key) {
                        if($this_foreign_key['reference_column'] == $field_info->name) {
                            $to_check[] = array('value' => $this_row, 'foreign_key' => array('table' => $this_foreign_key['table'], 'column' => $this_foreign_key['column'], 'database' => $this_foreign_key['database']));
                        }
                    }
                    $i++;
                }
                $GLOBALS['get_data_that_would_be_deleted'][$table][] = $new_row;
            }
            foreach ($to_check as $this_to_check) {
                if(isset($this_to_check['value']) && !is_null($this_to_check['value'])) {
                    get_data_that_would_be_deleted($database, $this_to_check['foreign_key']['table'], array($this_to_check['foreign_key']['column'] => $this_to_check['value']), $recursion - 1);;
                }
            }

            $data = $GLOBALS['get_data_that_would_be_deleted'];

            return $data;
        } else {
            die("\$where needs to be an array with column_name => value pairs");
        }
    } else {
        die("\$table was not defined!");
    }
}

Imagine I have a table called "table" in the database "db" and I want to delete the one with the id 180, then I'd call:想象一下,我在数据库“db”中有一个名为“table”的表,我想删除 ID 为 180 的表,然后我会调用:

print(get_foreign_key_deleted_data_html('db', 'table', array('id' => 180)));

and it prints a full table with all the rows and all the values that would be deleted.它会打印一个包含所有行和所有将被删除的值的完整表格。

But as I've said, this is a very, very quick and dirty hack.但正如我所说,这是一个非常非常快速和肮脏的黑客。 I'd be glad for any bug-report (and there surely are a lot of them!).我很高兴收到任何错误报告(而且肯定有很多!)。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM