如何删除/移动/标记电子邮件时,如何向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
}
或撤消按钮以恢复单个操作(单个记录查询)。
这是我对“增量备份更改”的概念是正确的还是我过度复杂了? 考虑到一个明显的事实,即如果我存储完整的查询,我的数据库的大小将是双倍或可能是三倍。我应该将它存储在不同的数据库中吗?或者只是在我编写完整备份后才擦除日志表以仅保留最近的更改?
欢迎任何建议......
答案 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');
我再次迭代 - 将上述所有代码视为伪代码,并在尝试运行它们时进行必要的更改。这是我在手动查询闪回中看到的防故障设计最多。