2014-02-01 3 views
5

Как я могу реализовать функцию отмены для базы данных mysql, как Gmail, когда вы удаляете/перемещаете/помещаете электронное письмо.Функция для возврата выражения sql

До сих пор у меня есть таблица системных журналов, в которой содержатся точные операторы 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 
} 

или кнопка отмены, чтобы вернуть одно действие (один зарегистрированный запрос).

Это моя концепция этих «инкрементных изменений резервных копий», или я слишком сильно нарушаю ситуацию? Учитывая очевидный факт, что размер моей базы данных будет двойной или, возможно, триплексной, если я сохраню полные запросы. Должен ли я хранить его в другой базе данных? или просто стереть таблицу журналов, как только я сделаю запрограммированную полную резервную копию, чтобы сохранить последние изменения?

Любые советы приветствуются ...

+5

проверить этот СНВ-СТАВКА, КОМИТЕТ и ROLLBACK ... http://dev.mysql.com/doc/refman/5.0/en/commit.html – user1844933

+1

Спасибо, но это работает только во время выполнения, а не если я хочу к «откату» позже, когда пользователь уже совершил изменение даже время назад. –

+3

это зависит от логики программы, не удаляйте запись, просто удаляйте закладку. – user1844933

ответ

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 

Сложнее с повтора, но он похож, как правило, эта модель используется в хранилище данных для отслеживания изменений вместо функции повтора, но это должно быть хорошо для повтора тоже. Он также известен как медленно изменяющийся размер в хранилище данных.

+0

Я использовал именно это решение только для критических таблиц данных системы, смешанных с мягкими удалениями, которые являются лучшими. сохраняя мою первоначальную идею регистрации каждого отдельного запроса в течение определенного периода времени, когда клиент пытается обвинить систему в потере/изменении данных, я улавлю пользователя и время выполнения. –

0

Рассматривали ли вы проходя старые значения в отдельную таблицу в качестве значений XML? Затем, если вам нужно восстановить их, вы можете получить значения XML из таблицы.

0

Для такой системы журнальный столик - это путь. Да, таблица, скорее всего, будет большой, но все зависит от того, как далеко назад вы хотите уйти. Вы могли бы использовать ограничение по времени, как вы сказали, и удалить все журналы до 6 месяцев назад. Вы также можете создать своего рода корзину и не разрешать пользователям иметь больше, чем, скажем, 100 «элементов» в ней, - всегда хранить самые последние 100 записей журнала для каждого пользователя.

Что касается вопроса о том, какие запросы хранить в вашей таблице журналов, встроенная функция не позволяет вам делать то, что вы хотите. Но поскольку вы только регистрируете обновления и удаляются (нет необходимости вносить вставки в журнал, поскольку пользователи обычно имеют возможность удалять их материалы), вы можете легко создать свою собственную функцию.

Перед выполнением инструкции UPDATE или DELETE вы получите всю строку из базы данных, и для нее вы создаете для нее оператор REPLACE - он работает как как UPDATE, так и INSERT. Единственное, о чем нужно помнить, это то, что вам нужен индекс PRIMARY KEY или UNIQUE для всех ваших таблиц.

Вот ideea о том, как функция должна выглядеть следующим образом:

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.')'; 
} 
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()); 

Чтобы отменить, вы выполняете обратные запросы из таблицы отмены между вашими датами, отсортированными по дате в порядке desc

0

Лучшее решение - это мягкое удаление в таблице базы данных, обычно это столбец с именем «is_deleted» и «datetime_deleted», автоматически заполняется, когда пользователь удаляет.

Когда удаление завершено, ответ включает идентификатор записи, который заполняет ссылку, вызывающую метод отмены, который пользователь может щелкнуть, что просто отменяет запись, обновив базу данных еще раз.

Затем вы можете управлять заданием, которое либо выполняется пользователем, либо запланированной задачей, чтобы очистить все данные, отмеченные «is_deleted = 1» в течение определенного периода времени.

0

Я думаю, что сочетание методов необходимо будет здесь ...

Вы могли бы реализовать систему очереди, который выполняет работу (отправка сообщений электронной почты и т.д.) по истечении определенного времени.

E.g. Если пользователь удаляет объект, отправьте его в очередь в течение 30 секунд или просто просто отключите отключение пользователя. Если пользователь отменяет отмену, вы можете просто удалить задание из очереди.

Это сочетание с мягким удалением может быть хорошим вариантом для изучения.

Я использовал Laravels Queue class, который действительно хорош.



Я не совсем уверен, что когда-нибудь будет правильный ответ для этого, поскольку у вас нет правильного способа сделать это. Удачи, хотя :)

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 не был уволен, процедура вызова, как это будет делать хорошую joib:

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

Я повторяю это снова - рассматривайте весь приведенный выше код как псевдокод и вносите необходимые изменения, когда пытаетесь их запустить. Это самый отказоустойчивый дизайн, который я видел для ручных запросов ретроспективных запросов.

 Смежные вопросы

  • Нет связанных вопросов^_^