函数还原sql语句

时间:2014-02-01 05:54:16

标签: php mysql sql database undo

如何删除/移动/标记电子邮件时,如何向Gmail数据库实施撤消更改功能。

到目前为止,我有一个系统日志表,其中包含用户执行的确切sql语句。

例如,我正在尝试改造:

INSERT INTO table (id, column1, column2) VALUES (1,'value1', 'value2')

成:

DELETE FROM table WHERE id=1, column1='value1', column2='value2'

是否有内置函数来执行此操作,如cisco路由器命令,如

(NO|UNDO|REVERT) INSERT INTO table (id, column1, column2) VALUES (1,'value1', 'value2')

也许我的方法不正确,我应该保存我的行的当前状态和更改的行以恢复它的原始状态吗?

类似的东西:

original_query = INSERT INTO table (id, column1, column2) VALUES (1,'value1', 'value2')

executed_query = INSERT INTO table (id, column1, column2) VALUES (1,'change1', 'change2')

以后转换为:

INSERT INTO table (id, column1, column2) VALUES (1,'value1', 'value2') ON DUPLICATE KEY UPDATE
 column1=VALUES(column1), column2=VALUES(column2)

但也许它不适用于新插入的行,或者如果我修改主键会导致麻烦,所以我宁愿让它们保持不变。

这是我的日志表:

CREATE TABLE `log` (
 `id` int(11) unsigned NOT NULL AUTO_INCREMENT ,
 `date` datetime NOT NULL ,
 `user` int(11) NOT NULL,
 `client` text COMMENT ,
 `module` int(11) unsigned NOT NULL ,
 `query` text NOT NULL ,
 `result` tinyint(1) NOT NULL ,
 `comment` text,
 PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=2 DEFAULT CHARSET=utf8

目标就像我说的那样,根据语句执行的日期从某段时间撤消更改,例如(可以在php中)

function_undo(startdate, enddate)
{
    RESULT = SELECT query FROM log WHERE date BETWEEN startdate AND endate
    FOR EACH RESULT AS KEY - query
       REVERT query
}

或撤消按钮以恢复单个操作(单个记录查询)。

这是我对“增量备份更改”的概念是正确的还是我过度复杂了? 考虑到一个明显的事实,即如果我存储完整的查询,我的数据库的大小将是双倍或可能是三倍。我应该将它存储在不同的数据库中吗?或者只是在我编写完整备份后才擦除日志表以仅保留最近的更改?

欢迎任何建议......

7 个答案:

答案 0 :(得分:1)

它始终存在问题,SQL 2012解决了这个问题。 时态模型很简单:添加区间列(valid_from,valid_to)但实现约束非常复杂。 模型操作也很简单:

1. insert -  new version valid_from=now, valit_to=null
2. update -  new version valid_from=now, valit_to=null, update previous version valit_to=now
3. delete -  update current version valit_to=now
4. undo delete - update last version valit_to=null 
5. undo update/insert - delete current version if you do not need redo and update valit_to=null if previous version exits 

重做更复杂但是它很相似,通常这个模型用于数据仓库来跟踪更改而不是重做函数,但它也应该适用于重做。它也被称为数据仓库中缓慢变化的维度。

答案 1 :(得分:0)

您是否考虑将旧值作为XML值传递到单独的表中?然后,如果需要还原它们,可以从表中检索XML值。

答案 2 :(得分:0)

对于这种系统,可以使用日志表。是的,这张桌子很可能很大,但这一切都取决于你想要的距离。正如您所说,您可以使用时间限制,并在6个月前删除所有日志。您还可以创建某种类型的回收站,并且不允许用户拥有超过100个“项目” - 总是为每个用户保留最近的100个日志条目。

关于要在日志表中保留哪些查询的问题,没有内置函数可以让您执行所需的操作。但由于您只记录更新和删除(不需要记录插入,因为用户通常可以选择删除它们的东西),您可以轻松构建自己的功能。

在任何UPDATE或DELETE语句之前,您从数据库中获取整行,并为其创建REPLACE语句 - 它既可以作为UPDATE也可以作为INSERT。唯一要记住的是,您需要为所有表格使用PRIMARY KEY或UNIQUE索引。

这是关于函数应该如何的概念:

function translateStatement($table, $primaryKey, $id)
{
    $sql = "SELECT * FROM `$table` WHERE `$primaryKey` = '$id'"; //should always return one row
    $result = mysql_query($sql) or die(mysql_error());
    $row = mysql_fetch_assoc($result);

    $columns = implode(',', array_map( function($item){ return '`'.$item.'`'; }, array_keys($row)) ); //get column names
    $values = implode(',', array_map( function($item){ return '"'.mysql_real_escape_string($item).'"'; }, $row) ); //get escaped column values

    return 'REPLACE INTO `$table` ('.$columns.') VALUES ('.$values.')';
}

答案 3 :(得分:0)

我认为您需要记录每个插入/更新/删除查询的反向,然后执行它们以执行撤消。这是一个适合您的解决方案,但这并不考虑外键关系(级联操作)。这只是一个简单的解决方案概念。希望它会给你更多的想法。在这里:

假设您有一个这样的表要撤消

create table if not exists table1 
(id int auto_increment primary key, mydata varchar(15));

这是记录反向查询的表

create table if not exists undoer(id int auto_increment primary key,
 undoquery text ,  created datetime );

为插入更新和删除操作创建触发器以保存反向/救援查询

create trigger  after_insert after insert on table1 for each row
    insert into undoer(undoquery,created) values 
(concat('delete from table1 where id = ', cast(new.id as char)), now());

create trigger after_update after update on table1 for each row
    insert into undoer(undoquery,created) values 
(concat('update table1 set mydata = \'',old.mydata,
        '\' where id = ', cast(new.id as char)), now());

create trigger after_delete after delete on table1 for each row
    insert into undoer(undoquery,created) values 
  (concat('insert into table1(id,mydata) 
   values(',cast(old.id as char), ', \'',old.mydata,'\') '), now());

要撤消,您可以在按日期排序的日期之间执行撤消表中的反向查询

答案 4 :(得分:0)

最佳解决方案是在数据库表中进行软删除,通常是名为“is_deleted”的列,以及“datetime_deleted”,在用户删除时自动填充。

当删除完成后,响应中包含记录的ID,其中填充了一个链接,该链接调用用户可以单击的撤消方法,只需通过再次更新数据库来取消删除记录。

然后,您可以操作由用户执行的作业或计划任务,以清除一段时间内标记为“is_deleted = 1”的所有数据。

答案 5 :(得分:0)

我认为这里需要一系列技术......

您可以实施一个队列系统,该系统在一段时间后执行作业(发送电子邮件等)。

例如如果用户删除某个对象,则将其发送到队列中大约30秒左右,只需用户单击“撤消”即可。如果用户单击撤消,则只需从队列中删除该作业即可。

这与软删除相结合可能是一个很好的选择。

我使用了Laravels Queue class,这真的很棒。



我不确定是否会有正确的答案,因为没有正确的方法。祝你好运:)

答案 6 :(得分:0)

我建议您使用类似下表的内容来记录对数据库的更改。

TABLE audit_entry_log
-- This is an audit entry log table where you can track changes and log them here.
( audit_entry_log_id    INTEGER         PRIMARY KEY
, audit_entry_type      VARCHAR2(10)    NOT NULL
     -- Stores the entry type or DML event - INSERT, UPDATE or DELETE.
, table_name            VARCHAR2(30)
    -- Stores the name of the table which got changed
, column_name           VARCHAR2(30)
    -- Stores the name of the column which was changed
, primary_key           INTEGER
    -- Stores the PK column value of the row which was changed.
    -- This is to uniquely identify the row which has been changed.
, ts                    TIMESTAMP
    -- Timestamp when the change was made.
, old_number            NUMBER(36, 2)
    -- If the changed field was a number, the old value should be stored here.
    -- If it's an INSERT event, this would be null.
, new_number            NUMBER(36,2)
    -- If the changed field was a number, the new value in it should be stored here.
    -- If it's a DELETE statement, this would be null.
, old_text              VARCHAR2(2000)
    -- Similar to old_number but for a text/varchar field.
, new_text              VARCHAR2(2000)
    -- Similar to new_number but for a text/varchar field.
, old_date              VARCHAR2(2000)
    -- Similar to old_date but for a date field.
, new_date              VARCHAR2(2000)
    -- Similar to new_number but for a date field.
, ...
, ... -- Any other data types you wish to include.
, ...
);

现在,假设你有一个这样的表:

TABLE user
( user_id       INTEGER         PRIMARY KEY
, user_name     VARCHAR2(50)
, birth_date    DATE
, address       VARCHAR2(50)
)

在此表中,我有一个触发器,用于填充audit_entry_log跟踪此表的更改。 我正在为Oracle提供这个代码示例,你肯定可以调整它以适应MySQL:

CREATE OR REPLACE TRIGGER user_id_trg
BEFORE INSERT OR UPDATE OR DELETE ON user
REFERENCING new AS new old AS old
FOR EACH ROW
BEGIN
    IF INSERTING THEN
        IF :new.user_name IS NOT NULL THEN
            INSERT INTO audit_entry_log (audit_entry_type,
                                         table_name,
                                         column_name,
                                         primary_key,
                                         ts,
                                         new_text)
            VALUES ('INSERT',
                    'USER',
                    'USER_NAME',
                    :new.user_id,
                    current_timestamp(),
                    :new.user_name);
        END IF;
        --
        -- Similar code would go for birth_date and address columns.
        --

    ELSIF UPDATING THEN
        IF :new.user_name != :old.user_name THEN
            INSERT INTO audit_entry_log (audit_entry_type,
                                         table_name,
                                         column_name,
                                         primary_key,
                                         ts,
                                         old_text,
                                         new_text)
            VALUES ('INSERT',
                    'USER',
                    'USER_NAME',
                    :new.user_id,
                    current_timestamp(),
                    :old.user_name,
                    :new.user_name);
        END IF;
        --
        -- Similar code would go for birth_date and address columns
        --

    ELSIF DELETING THEN
        IF :old.user_name IS NOT NULL THEN
            INSERT INTO audit_entry_log (audit_entry_type,
                                         table_name,
                                         column_name,
                                         primary_key,
                                         ts,
                                         old_text)
            VALUES ('INSERT',
                    'USER',
                    'USER_NAME',
                    :new.user_id,
                    current_timestamp(),
                    :old.user_name);
        END IF;
        --
        -- Similar code would go for birth_date and address columns
        --
    END IF;
END;
/

现在,考虑一下,作为一个简单示例,您可以在时间戳31-JAN-2014 14:15:30上运行此查询:

INSERT INTO user (user_id, user_name, birth_date, address)
VALUES (100, 'Foo', '04-JUL-1995', 'Somewhere in New York');

接下来,您对时间戳UPDATE运行31-JAN-2014 15:00:00查询:

UPDATE user
   SET username = 'Bar',
       address = 'Somewhere in Los Angeles'
 WHERE user_id = 100;

因此,您的user表将包含数据:

user_id user_name birth_date  address
------- --------- ----------- --------------------------
100     Bar       04-JUL-1995 Somewhere in Los Angeles

这导致audit_entry_log表中的以下数据:

audit_entry_type table_name column_name primary_key ts                   old_text              new_text                 old_date new_date
---------------- ---------- ----------- ----------- -------------------- --------------------- ------------------------ -------- -----------
INSERT           USER       USER_NAME   100         31-JAN-2014 14:15:30                       FOO
INSERT           USER       BIRTH_DATE  100         31-JAN-2014 14:15:30                                                         04-JUL-1992
INSERT           USER       ADDRESS     100         31-JAN-2014 14:15:30                       SOMEWHERE IN NEW YORK 
UPDATE           USER       USER_NAME   100         31-JAN-2014 15:00:00 FOO                   BAR
UPDATE           USER       ADDRESS     100         31-JAN-2014 15:00:00 SOMEWHERE IN NEW YORK SOMEWHERE IN LOS ANGELES

创建一个类似下面的过程,它接受我们必须恢复特定表名的表名和时间戳。 该表只能恢复到时间戳。时间戳不会有。它只是从当前到过去的时间戳。

CREATE OR REPLACE PROCEDURE restore_db (p_table_name varchar, p_to_timestamp timestamp)
AS
CURSOR cur_log IS
SELECT *
  FROM audit_entry_log
 WHERE table_name = p_table_name
   AND ts > p_to_timestamp;
BEGIN
    FOR i IN cur_log LOOP
        IF i.audit_entry_type = 'INSERT' THEN
            -- Delete the row that was inserted.
            EXEC ('DELETE FROM '||p_table_name||' WHERE '||p_table_name||'_id = '||i.primary_key);
        ELSIF i.audit_entry_type = 'UPDATE' THEN
            -- Put all the old data back into the table.
            IF i.old_number IS NOT NULL THEN
                EXEC ('UPDATE '||p_table_name||' SET '||i.column_name||' = '||i.old_number
                      ||' WHERE '||p_table_name||'_id = '||i.primary_key);
            ELSIF i.old_text IS NOT NULL THEN
                -- Similar statement as above EXEC for i.old_text
            ELSE
                -- Similar statement as above EXEC for i.old_text
            END IF;
        ELSIF i.audit_entry_type = 'DELETE' THEN
            -- Write an INSERT statement for the row that has been deleted.
        END IF;
    END LOOP;
END;
/

现在,如果您要将user表恢复为31-JAN-2014 14:30:00的状态 - 当INSERT被解雇且UPDATE未被触发时,就会发生这样的过程调用会做一个好的笑话:

restore_db ('USER', '31-JAN-2014 14:30:00');

我再次迭代 - 将上述所有代码视为伪代码,并在尝试运行它们时进行必要的更改。这是我在手动查询闪回中看到的防故障设计最多。