Как стать автором
Обновить

Комментарии 21

Любопытно, вроде то что надо, но у меня версия MariaDB 10.0.32
Притом MariaDB 10.3.4, в которой появился требуемый функционал, вышла относительно недавно, 18.01.2018
Сейчас вообще-то октябрь. Я бы сказал, что она уже старая :)
Решение на mysql разработано мной довольно давно, всё никак руки не доходили оформить в статью. А столкнулся с подобным решение на Oracle, 14 лет назад. На этом же протоколе была реализована репликация БД. Реплицировали удалённые точки через дискеты.
Внимательно посмотрел System-Versioned Tables. В нём можно посмотреть изменения БД, но нельзя узнать кто внёс эти изменения, это существенный минус.
Почему на триггерах? Потому что нет доступа к бинарному логу.

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

Каких системных возможностей? Где огромный оверхед? Где-то есть готовое решение с бинлогом?
Каких системных возможностей?

Хотя бы того же доступа к бинлогу.
А вообще выше уже дали ссылку на нативную реализацию, безо всяких скрипто-костылей. Её надо просто установить вместо вашей старой версии. Но на шаред хостингах вам естественно никто не даст ставить что хотите, потому и делаются костыли для эмуляции возможностей нужных версий на всех остальных.

И что Вы будете делать с бинлогом будь он у Вас?
Нативная System-Versioned Tables не позволяет видеть какой пользователь внёс изменения, а это существенный недостаток, который не позволяет решить ряд задач поставленных мной

есть более зрелое решение, тоже на триггерах php-audit

Выглядит забавно. Впрочем, ограничения конечно присутствуют:

0. Не отслеживается по понятным причинам DDL.

========
PhpAudit has the following limitations:

* A TRUNCATE TABLE will remove all rows from a table and does not execute triggers. Hence, the removing of those rows will not be logged in the audit table.
* A delete or update of a child row caused by a cascaded foreign key action of a parent row will not activate triggers on the child table. Hence, the update or deletion of those rows will not be logged in the audit table.
========

С транкейтом понятно. Неприятно, но жить можно. А вот несрабатывание триггеров на каскаде — это уже может быть непреодолимой проблемой. Впрочем — под какие задачи.
TRUNCATE это фактически DDL команда, как если бы вы сделали DROP TABLE, а потом CREATE, отсюда и не срабатывание триггеров.
Не срабатывание триггеров на удаление FOREIGN KEY это недоработка MySQL'я
dev.mysql.com/doc/refman/8.0/en/innodb-restrictions.html
Cascaded foreign key actions do not activate triggers.

Решается добавлением в BEFORE DELETE TRIGGER соответствующего
DELETE FROM <child_table> WHERE parent_id = :OLD.id;
> Решается добавлением в BEFORE DELETE TRIGGER соответствующего

Главное не забывать что нужно не забыть это сделать при добавлении/изменении очередной таблицы :)
Ну тут вроде алгоритм ясен:
1. отказываемся от фраз ON DELETE CASCADE. Фразу FOREIGN KEY ес-но оставляем, целостность БД превыше всего
2. При прогоне юнит-тестов или получения REFERENCE ошибки от пользователя при удалении записи вспоминаем что нужно вспоминать о триггере на удаление дочерних не значимых записей :)
Более зрелое по наличию на github, однако по функционалу у меня плюсом есть функция для работы с откатом протокола ;)

Интересно, как оно себя поведет хотя-бы на нормальном потоке операций вставки/изменения данных… Binary log в таком случае делает вставки в один поток и упирается в iops (ну если не предпринять нужных мер). Может проведете тесты? Вставка потоков в 50, операций по 1000 по одной строке? С включенными триггерами и без них...

В статье Реализация бизнес-логики в MySQL проводил тесты на свои «специальные» триггеры. Со слов «Хочу пояснить несколько моментов по реализации:
— такой подход вместо нативных триггеров, как в первом варианте даёт некоторый оверхед.»
Также ведение протокола может быть требованием бизнес-логики. Например, история изменения статуса документа. И тут уже реализация INSERT'а в триггере будет практически идентична реализации INSERT'а вне триггера.
Ещё нужно понимать, что ведение протокола не требуется для таблиц которые сами по себе являются логом-протоколом и работают только на вставку с большой интенсивностью. Например, данные с различных датчиков, gps, биометрия.
Протоколирование особенно полезно для Справочников и различных таблиц состояний, типа документов.
Я и хотел увидеть «некоторость» оверхеда. Впрочем ладно, сам попробую.
Вставка 10000 строк, 4 поля на строку
1. с триггерами и протоколированием — 6.89c
2. с триггерами, протоколирование отключено программно — 2.17с
3. без триггеров — 1.37c

SQL запросы
DELIMITER $
DROP PROCEDURE IF EXISTS speed_test$
CREATE PROCEDURE speed_test(n INT)
BEGIN
    DECLARE i INT DEFAULT 0;
    WHILE i < n DO
        INSERT INTO docs (num, date, warehouse, partner)
        VALUES (CONCAT('тест', i), DATE(NOW()), 'Склад Новосибирск', 'Радуга, ЗАО');
        SET i := i + 1;
    END WHILE;
END$

-- 1. с триггерами и протоколированием
DELIMITER ;
BEGIN;
CALL speed_test(10000);
ROLLBACK;
MariaDB [test-habr]> DELIMITER ;
MariaDB [test-habr]> BEGIN;
Query OK, 0 rows affected (0.00 sec)
MariaDB [test-habr]> CALL speed_test(10000);
Query OK, 1 row affected (6.89 sec)
MariaDB [test-habr]> ROLLBACK;
Query OK, 0 rows affected (0.88 sec)

-- 2. с триггерами, протоколирование отключено программно
DELIMITER ;
BEGIN;
SET @disable_all_prot_trg = 1;
CALL speed_test(10000);
SET @disable_all_prot_trg = NULL;
ROLLBACK;
MariaDB [test-habr]> DELIMITER ;
MariaDB [test-habr]> BEGIN;
Query OK, 0 rows affected (0.00 sec)
MariaDB [test-habr]> SET @disable_all_prot_trg = 1;
Query OK, 0 rows affected (0.00 sec)
MariaDB [test-habr]> CALL speed_test(10000);
Query OK, 1 row affected (2.17 sec)
MariaDB [test-habr]> SET @disable_all_prot_trg = NULL;
Query OK, 0 rows affected (0.00 sec)
MariaDB [test-habr]> ROLLBACK;
Query OK, 0 rows affected (0.12 sec)

-- 3. без триггеров
DELIMITER ;
DROP TRIGGER IF EXISTS docs_aft_ins_trg;
BEGIN;
CALL speed_test(10000);
ROLLBACK;
MariaDB [test-habr]> DELIMITER ;
MariaDB [test-habr]> DROP TRIGGER IF EXISTS docs_aft_ins_trg;
Query OK, 0 rows affected (0.00 sec)
MariaDB [test-habr]> BEGIN;
Query OK, 0 rows affected (0.00 sec)
MariaDB [test-habr]> CALL speed_test(10000);
Query OK, 1 row affected (1.37 sec)
MariaDB [test-habr]> ROLLBACK;
Query OK, 0 rows affected (0.12 sec)

Вот спасибо, сэкономили всем время :)
Понятно, что механизм применим не ко всем таблицам, а только к тем, где реально требуется протоколирование. В случае нашей ERP надёжнее оказалось вынести такое протоколирование на уровень приложения, где оно работает вместе с бизнес-логикой. Но на небольшом проекте с большим количеством ручного ввода данных и большой же ответственностью подобная штука работала и у меня. Спасибо.
в MySQL (5.6 вроде...) когда-то был баг — даже в случае если транзакция не прошла и был сделан ROLLBACK, то тригеры все-равно выполняются.
Может его уже пофиксили, а может в MariaDB его и нету?
Триггеры ес-но выполнятся, но ROLLBACK откатит изменения сделанные самой командой и командами выполненными из триггера.
Не знаю как насчёт 5.6, в 5.1 всё работало корректно.
Зарегистрируйтесь на Хабре, чтобы оставить комментарий

Публикации

Истории