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

  1. Как сделать разный часовой пояс в разных базах данных на одном сервере.
  2. Как вести логи изменений данных пользователями в базе данных, сохраняя их в другой базе данных, для того чтобы основная база данных не забивалась мусором и не росла.
  3. Как создать свою файловую систему на основе blob полей в базе данных. Почему это удобно. Вопросы эффективности хранения файлов: как получить максимальное быстродействие и при этом минимальное занимаемое место.

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

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

Итак начнем.

База данных firebird 3.

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

Разрабатывался данный механизм для облачной платформы по автоматизации предприятия (https://erp-platforma.com), но впервые я столкнулся с данной проблемой работая в отделе биллинга, в телеком компании. Там была база данных биллинга (назовем ее Основная БД), с которой работали все системы, и база данных для личного кабинета клиентов (назовем ее БД ЛК), которая тоже должна была содержать большинство данных из основной БД. Это было сделано, потому что всем было когда-то стремно, что находящийся “снаружи” ЛК смотрит в главную базу данных компании, ибо мало ли…

Для того чтобы данные из основной базы, попадали в БД ЛК (и в некоторых случаях из БД ЛК в основную) разработчиками биллинга был реализован механизм репликации, делались триггеры на insert, update и delete, которые писали все изменения в некую “промежуточную” таблицу, внешний скрипт с определенной периодичностью выгружал новые записи из этой таблицы в текстовики, эти текстовики закидывались на сервер с БД ЛК и там исполнялись в той базе. Все вроде неплохо, НО, база росла как на дрожжах, достигая за несколько месяцев ГИГАНТСКИХ размеров за счет этой таблицы, куда сливались все логи. Устаревающие данные в ней оставались, а не сразу из нее не удалялись, ибо удаление – процесс долгий и способствует накоплению мусора и просто БД будет работать со временем медленнее. Так решили разработчики. В итоге доходило до того, что каждые 3 месяца, приходилось удалять эти данные, и делать бекап-ресторе базы данных, ибо место на сервере просто заканчивалось. Бекап-ресторе таких объемов и на таком сервере – чуть ли не сутки на всю работу. Это каждые 3 месяца остановка на сутки работы всей компании. И это приходилось делать. Хелп деск делал записи в это время на бумажке…

Разрабатывая свою облачную платформу, в виду опыта, я уже осознавал данную проблематику и решил сделать систему записи подробных логов лишенную данных недостатков. Тем более учитывая объемы, что может быть на один сервер не одна база данных, а 1000, и делать этой тысяче бекап-ресторе нереально.

Для начала хочу описать общую концепцию.

Чтобы структуиризировать ведение записей, они ведутся пакетным способом.
Есть две таблицы, таблица с информацией по пакету и таблица с данными этого пакета. Назовем их LOG_PACKET и LOG

LOG_PACKET

  1. Уникальный идентификатор
  2. Дата записи
  3. Автор записи (ид юзера)
  4. Признак обработки (0 или 1)
  5. Тип записи (ins,up,del)
  6. Номер таблицы (название таблицы из которой пишем логи)
  7. Номер таблицы с данными
  8. Идентификатор записи пакета
  9. Количество попыток обработки
  10. Дата последней обработки

LOG

  1. Уникальный идентификатор
  2. Блоб поле для данных OLD
  3. Блоб поле для данных NEW
  4. Идентификатор записи пакета
  5. Номер поля таблицы (название поля таблицы)

В триггере при изменении данных, должна заполняться запись в LOG_PACKET с информацией о чем эта запись и данные (до изменения и после изменения) в таблицу LOG

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

Далее нам надо избавиться от мусора в основной БД. Удалять данные из LOG – тормозить базу и копить мусор. Есть более быстрая и лучшая процедура DROP TABLE.

PS: Почему DROP предпочтительнее DELETE подробно описывать не буду. Все очень хорошо описано в данной статье.

Поэтому применяется следующее решение.

Таблиц LOG делается 3 штуки. LOG_1 – в первый день данные пишутся сюда, LOG_2 – в следующий день данные пишутся сюда, и т.д. происходит чередование, один день в LOG_1 второй день в LOG_2. Когда данные пишутся в LOG_2 и все пакеты ссылающиеся на LOG_1 обработанны — происходит DROP LOG_1 и CREATE TABLE LOG_1. Таким образом, в момент удаляются все записи и таблица становится чистой. На следующий день аналогично с LOG_2.

НО не так все просто. Т.к. ссылки на LOG_1 и LOG_2 прописаны в триггерах всех логируемых таблиц – удалить их не получится. Для этого надо применить хитрость. Сделать их VIEW. И прописать добавление данных в триггерах через это VIEW.

CREATE OR ALTER VIEW V_LOG_1(
    ID,
    BLOB_OLD,
    BLOB_NEW,
    PACKET,
    NUM_POLE)
AS
select p.id, p.blob_old, p.blob_new, p.packet, p.num_pole from LOG_1 p;

Но даже так удалить таблицу имея триггер не получится ибо VIEW в триггере будет ссылаться на нее.

Для этого надо сделать таблицу LOG_3, которая буде копией по структуре LOG_1 и LOG_2. Заменяем в VIEW LOG_1 на LOG_3 и вауля, можно удалить и пересоздать таблицу LOG_1. После этого меняем в VIEW назад LOG_3 на LOG_1 – и все работает дальше.

Эту операцию можно проводить без проблем, в таблицу LOG_1 в этот момент гарантировано не будет происходить записи, т.к. в этот день запись ведется в LOG_2. Такая операция очистки происходит всегда с таблицей, в которую в этот день записи нет. На следующий день такая же операция произойдет с таблицей LOG_2.

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

Концепцию мы разобрали, теперь разберем оптимальную схему работы.

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

Например

CREATE TABLE LOG_INFO (
    ZAPIS      SMALLINT,
    DATA  TIMESTAMP,
    ID_TABLE       SMALLINT
);

Во всех триггерах логируемых таблиц сначала проверяется, разрешена ли запись

select p.zapis from LOG_INFO p into: zapis;

Если она разрешена, то проверяется, в какую таблицу писать данные

select first 1 
case when p.data=current_date then p.id_table else 
case 
when p.id_table=1 then 2 
when p.id_table=2 then 1 
end 
end, 
case when p.data=current_date then 1 else -1 end 
from LOG_INFO p into: log_info, log_info_check;

PS: для скептиков, first 1 нужно для того, чтобы исключить вероятность вылета триггера если в таблице LOG_INFO вдруг случайно появится более одной записи. В этом случае будет ошибка записи данных во все логируемые таблицы (обычно ошибка multiple rows). А first 1 мы гарантировано исключаем данный вариант.

Данным запросом мы:

1) Проверяем, совпадает ли дата с текущей, если да то пишем в текущую таблицу, если нет, то меняем таблицу на другую (произошел переход дня на следующий);
2) Ставим признак того, что данную таблицу надо проапдейтить на другую при переходе дня.

Следующим шагом если нам необходим апдейт при переходя дня, апдейтим

if (log_info_check=-1) then
UPDATE LOG_INFO SET ID_TABLE = :log_info, DATA = current_date;

Т.е. первая же запись на следующий день апдейтит данные, что писать надо в следующую таблицу. И дальше на это уже ресурсы не тратятся.

Далее пишутся данные в LOG_PACKET и получаем его идентификатор

if (inserting) then TYPE_=1;
if (updating) then TYPE_=2;
if (deleting) then TYPE_=3;
if (TYPE_ in (1,2)) then
INSERT INTO LOG_PACKET (TYPE_, TABLE_, NUM_TABLE, AVTOR, ID_ZAPISI) VALUES (:TYPE_, 15, :log_info, new.avtor, new.id) RETURNING ID into: id_packet;
else
INSERT INTO LOG_PACKET (TYPE_, TABLE_, NUM_TABLE, AVTOR, ID_ZAPISI) VALUES (:TYPE_, 15, :log_info, old.avtor, old.id) RETURNING ID into: id_packet;

Далее в зависимости от полученного номера таблицы, данные должны писаться в V_LOG_1 или V_LOG_2.

Например, запись может выглядеть так:

if (log_info=1) then
begin
if (TYPE_=1) then
begin
INSERT INTO V_LOG_1 (BLOB_OLD, BLOB_NEW, PACKET, NUM_POLE) VALUES (NULL, new.n_687, :id_packet, 687);
INSERT INTO V_LOG_1 (BLOB_OLD, BLOB_NEW, PACKET, NUM_POLE) VALUES (NULL, new.n_688, :id_packet, 688);
INSERT INTO V_LOG_1 (BLOB_OLD, BLOB_NEW, PACKET, NUM_POLE) VALUES (NULL, new.n_689, :id_packet, 689);
INSERT INTO V_LOG_1 (BLOB_OLD, BLOB_NEW, PACKET, NUM_POLE) VALUES (NULL, new.n_690, :id_packet, 690);
INSERT INTO V_LOG_1 (BLOB_OLD, BLOB_NEW, PACKET, NUM_POLE) VALUES (NULL, new.n_691, :id_packet, 691);

end
                      
if (TYPE_=2) then
begin
if (new.n_687<>old.n_687) then  INSERT INTO V_LOG_1 (BLOB_OLD, BLOB_NEW, PACKET, NUM_POLE) VALUES (old.n_687, new.n_687, :id_packet, 687);
if (new.n_688<>old.n_688) then  INSERT INTO V_LOG_1 (BLOB_OLD, BLOB_NEW, PACKET, NUM_POLE) VALUES (old.n_688, new.n_688, :id_packet, 688);
if (new.n_689<>old.n_689) then  INSERT INTO V_LOG_1 (BLOB_OLD, BLOB_NEW, PACKET, NUM_POLE) VALUES (old.n_689, new.n_689, :id_packet, 689);
if (new.n_690<>old.n_690) then  INSERT INTO V_LOG_1 (BLOB_OLD, BLOB_NEW, PACKET, NUM_POLE) VALUES (old.n_690, new.n_690, :id_packet, 690);
if (new.n_691<>old.n_691) then  INSERT INTO V_LOG_1 (BLOB_OLD, BLOB_NEW, PACKET, NUM_POLE) VALUES (old.n_691, new.n_691, :id_packet, 691);

end
                      
if (TYPE_=3) then
begin
INSERT INTO V_LOG_1 (BLOB_OLD, BLOB_NEW, PACKET, NUM_POLE) VALUES (old.n_687, NULL, :id_packet, 687);
INSERT INTO V_LOG_1 (BLOB_OLD, BLOB_NEW, PACKET, NUM_POLE) VALUES (old.n_688, NULL, :id_packet, 688);
INSERT INTO V_LOG_1 (BLOB_OLD, BLOB_NEW, PACKET, NUM_POLE) VALUES (old.n_689, NULL, :id_packet, 689);
INSERT INTO V_LOG_1 (BLOB_OLD, BLOB_NEW, PACKET, NUM_POLE) VALUES (old.n_690, NULL, :id_packet, 690);
INSERT INTO V_LOG_1 (BLOB_OLD, BLOB_NEW, PACKET, NUM_POLE) VALUES (old.n_691, NULL, :id_packet, 691);

end
end

PS: Я себе немного упростил жизнь, тем, что у меня свой встроенный язык программирования в систему и все таблицы и поля у меня идут с номерами, а не текстом. Так же благодаря этому, такие триггера создаются абсолютно автоматически при появлении новой таблицы или пересоздаются при изменении ее полей. Т.е. пользователю достаточно княпнуть в интерфейсе кнопку в редакторе таблицы “Включить логи” и все дальше у него будет происходить автоматически. У читателей все выйдет посложнее, если это база данных простая, то такие триггера придется создавать вручную и типы данных в NUM_POLE и NUM_TABLE должны быть текстовые, т.е. туда надо записывать названия таблиц и полей. Или ввести некую таблицу, где таблицам и полям будут присваиваться номера и брать данные из нее.

Далее необходимо создать таблицу с флагом, произошла ли запись. Так мы будем экономить ресурсы системы, ибо сделать запрос по таблице с одним полем намного быстрее, чем перебирать LOG_PACKET в поисках есть ли флаги новых записей.

Например так:

CREATE TABLE LOG_PACKET_FLAG (ID  SMALLINT); 

И поставить на таблицу LOG_PACET в триггер при добавлении записи нового пакета

UPDATE LOG_PACKET_FLAG SET ID = 1;

При запуске скрипта переноса данных в базу логов надо делать проверку есть ли новые записи (если их нет то ничего не делать больше).

select first 1 p.id from LOG_PACKET_FLAG p

Это будет работать намного быстрее чем, например:

select count(*) from LOG_PACKET p where p.check_=0

По окончанию переноса записей надо проверить, не появились ли новые записи пока мы вели перенос тех что были и сделать:

UPDATE LOG_PACKET_FLAG SET ID = 0;

В общем, экономьте ресурсы своих систем.

Далее рассмотрим сам процесс переноса записей.

Сначала селектом получаем все новые записи LOG_PACKET из нашей основной БД, и в цикле делаем INSERT этих данных в БД логов.

После создания записи пакета в БД логов, нам надо перенести данные этого пакета из таблиц LOG_1(2).

Извлечение данных из блоб полей и добавление их в другое место – это определенный геморой и танцы с бубном. После многих экспериментов я пришел к выводу, что самый простой и эффективных путь, просто из скрипта запустить запрос в котором будет запрос в базу данных (в общем, сделать перетягивание блоб поля прямым запросом из БД логов в основную БД.

Процедура стягивания данных пакета, которую надо дернуть скриптом, после добавления пакета.

сreate or alter procedure BLOB_INS (
    SELECT_ varchar(250),
    BASE_ varchar(100),
    USER_ varchar(50),
    PASS_ varchar(50),
    PACKET integer)
AS
declare variable BLOB_OLD  BLOB SUB_TYPE 0 SEGMENT SIZE 80;
declare variable BLOB_NEW  BLOB SUB_TYPE 0 SEGMENT SIZE 80;
declare variable PACKET    BIGINT;
declare variable NUM_POLE  INTEGER;
begin
    FOR EXECUTE STATEMENT (:select_)

      ON EXTERNAL :base_
      AS USER :user_ PASSWORD :pass_
      INTO :BLOB_OLD,:BLOB_NEW,:NUM_POLE
    DO
        INSERT INTO LOG (BLOB_OLD, BLOB_NEW, PACKET, NUM_POLE)
               VALUES (:BLOB_OLD,:BLOB_NEW, :pacet, :NUM_POLE);
End

:select должен быть подаваться примерно такого вида из скрипта:

select p.blob_old, p.blob_new, p.num_pole from log_'.<номер таблицы>.' p where p.packet='.<ид пакета>

После успешной обработки пакета, надо в основной базе поставить флаг, что этот пакет обработан.

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

Так же небольшое напутствие: сделайте для базы данных логов отдельную вирутальную машину. Можно этой машине выделить меньше ядер и памяти. Так же можно выделить ей похуже дисковую систему, нет необходимости хранить логи на SSD например, как основные базы с которыми ведется основная работа.

Тут нет повышенного требования к быстродействию, т.к. это отложенная операция. Нам не важно запишутся логи через минуту или через 1.5 минуты, главное, что они запишутся. Пользователи к этим данным обращаются очень редко, только в случае каких-то проблем, и ничего страшного если страница с логами будет грузиться на 200 ms дольше.

В общем, этим вы просто будите экономить ресурсы, лучше эти ресурсы выделите нагруженным машинам.
Поделиться с друзьями
-->

Комментарии (49)


  1. tsklab
    18.12.2016 16:36

    1. mail-online
      18.12.2016 16:51

      Это firebird. Я сейчас сделаю уточнение в статье.


  1. lair
    18.12.2016 17:33
    +1

    … а в это время другие люди используют Event Sourcing, да.


    (или, по крайней мере, очереди событий)


    1. mail-online
      18.12.2016 23:03

      Признаюсь, о таком подходе не слышал. Почитал про суть подхода ES, действительно интересно. Я так понял, что там нет update и delete, а при каждом изменении создается новая копия данных. Т.е. в базе всегда храниться вся цепочка истории.
      Первое что приходит на ум, это огромнейшая избыточность. Но мне конечно надо переварить такой подход, поэтому пока не берусь что-то говорить по этому поводу. Возможно такой подход в каких-то случаях действительно можно эффективно использовать.


      1. lair
        19.12.2016 00:02

        Первое что приходит на ум, это огромнейшая избыточность.

        Не больше, чем в "традиционном" логировании всего и вся.


  1. Tortortor
    18.12.2016 23:03
    +2

    «LOG_PACET»
    — вы серьёзно?

    «Таблиц LOG делается 3 штуки. LOG_1 – в первый день данные пишутся сюда, LOG_2 – в следующий день данные пишутся сюда, и т.д. происходит чередование, один день в LOG_1 второй день в LOG_2.»
    — точно серьёзно?

    удалите статью пока не поздно.


    1. mail-online
      18.12.2016 23:03

      Tortortor — что Вас смущает?


  1. yelloffduck
    18.12.2016 23:16

    А разве в firebird нельзя писать логи во внешние таблицы? И дропать ничего не придется.


    1. mail-online
      18.12.2016 23:17

      Изначально пробовал. Но оказалось, что они вне транзакций. Запись происходит через раз. В общем запись не гарантирована.


      1. rico_spb
        19.12.2016 19:43

        Запись происходит через раз. В общем запись не гарантирована.
        Ух, ты!
        Похоже, Tortortor выше был прав:
        удалите статью пока не поздно.


        1. mail-online
          19.12.2016 20:01

          rico_spb — вы хотите сказать что сможете сделать rollback внешней таблицы?

          Вот вам вырезка из мануала по файрберду

          Необязательное предложение EXTERNAL [FILE] указывает, что таблица хранится
          вне базы данных во внешнем текстовом файле. Столбцы таблицы, хранящейся во
          внешнем файле, могут быть любого типа за исключением BLOB и массивов с любым
          типом данных. Над таблицей, хранящейся во внешнем файле, допустимы только
          операции добавления новых строк (INSERT) и выборки (SELECT) данных. Операции
          же изменения существующих данных (UPDATE) или удаления строк такой таблицы
          (DELETE) не могут быть выполнены.


          Если вы импортируете данные из таблицы запросом, все у вас будет хорошо. А вот когда идет интенсивная запись из триггера например… данные теряются (как я убедился). Я долго не мог понять что же за чудеса творятся, запись может произойти, а может и не произойти. Можете самостоятельно провести данные эксперименты. (я правда на firebirde 2.5 еще их проводил, но в третем думаю с этим ничего не изменилось)

          Внешние таблицы хороши для переноса больших массивов информации из одной БД в другую. Тут у вас все получится. Использовать же их в онлайне — терять данные.

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


          1. rico_spb
            19.12.2016 21:58

            То, что внешние таблицы транзакционно-независимо еще в релиз-нотах написано было. Но, что ж поделать, дикий лузер я, раз за последение лет 5 не наступал на ужасные грабли «записи через раз». Это ж, наверное, и в документации описано…


  1. barker
    19.12.2016 07:31
    +2

    Так же я приветствую конструктивную критику.
    По-моему, это всё же тупо троллинг.
    Итак, в предыдущей статье про «облачную платформу для автоматизации предприятий», «неограниченно масштабируемую, с огромным числом потенциальных пользователей» и про работу со временем выяснилось, что вы не только совершенно неприемлемо это делаете и даже не понимаете почему так делать нельзя (о чём вам сказали ВСЕ комментаторы), но и даже не понимаете что такое UTC (!!!).
    И вы всё же продолжаете «цикл статей»? Это не шутка?


    1. mail-online
      19.12.2016 09:37

      barker — не понимаю в чем у вас проблема. Я пишу не про свою платформу, а про техническую часть, как можно реализовать те или иные вещи. То о чем я пишу — реализовано мной на практике. Я делюсь конкретным решением тех проблем, с которыми мне пришлось столкнуться и которые нигде не озвучены. Эти статьи для людей, которым будут эти темы интересны, у кого стоят похожие задачи. Если вам данные темы не интересны — читайте то, что вам интересно. Про UTC — это немного не та статья, вам не кажется? Вы можете в той статье спокойно оставлять свое недовольство. Та статья про то, как реализовать конкретную вещь, а не про личное мнение большинства комментаторов, как по их мнению должно быть устроено ведение часовых поясов. Сколько людей столько и мнений. Я все мнения прочитал и из некоторых отметил для себя полезные вещи.

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


      1. rpeMJIuH
        19.12.2016 10:04

        Мне уже не терпится увидеть третий шедевр…
        И главное, что пугает — это уже кем-то реализовано на практике.

        Основная проблема по статье с часовыми поясами:
        UTC — не должен зависеть от часового пояса, локальное время — должно. Смешивая эти два понятия Вы выносите мозги вообще всем.

        Проблемы в данном случае:
        Манипуляция с тремя таблицами и дополнительными вьюшками… Извините, слегка овер-инжиниринг на пустом месте, для выгрузки данных из базы.
        Что-то мне подсказывает, что использовать исключительно цифры в названиях таблиц и полей — не очень-то и здоровая идея, которая безумно усложняет поддержку.
        «свой встроенный язык программирования в систему» — учитывая всё прочитанное, извините, очень страшно, но безумно интересно увидеть его описание.
        LOG_PACET — граммарнаци негодуют (А гугл транслейт распознаёт это как индонезийский и подсказывает перевод — «слизни»)


        1. mail-online
          19.12.2016 13:13

          Извиняюсь, писал из головы, модератора у меня нет. Поправил «PACET» всех смущавший.

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


        1. tsklab
          19.12.2016 14:26

          использовать исключительно цифры в названиях таблиц и полей
          Это 1С.


          1. rpeMJIuH
            19.12.2016 15:05

            Соболезную.


          1. mail-online
            19.12.2016 19:36

            Кстати не так давно довелось увидеть базу 1С, у них реально схожее решение с моим, только идентификаторы более навороченные в таблицах и полях (со всякими буквенными приставками типа _Fld8005_RTRef) и служебные поля естественно другие. У меня они числовые в целом. Где там у них конфиги всего этого хранятся я сходу тогда не понял, надо почитать как-нибудь.


        1. smplpro
          19.12.2016 21:04

          Извините, слегка овер-инжиниринг на пустом месте, для выгрузки данных из базы.

          Тоже сразу об этом подумалось, когда дочитал до трех таблиц и т.д.


          1. mail-online
            19.12.2016 22:33

            А какое ваше решение по очистке базы? Предложите проще.


            1. michael_vostrikov
              20.12.2016 06:56
              +1

              Не хранить логику в БД, а писать ее на внешнем языке программирования. Банально делаем второе подключение к отдельной базе, и в процедурах изменения данных вызываем что-то типа $db_log->insert('log_table', [$transaction_id, json_encode($data)])


              1. mail-online
                20.12.2016 09:48

                И как вы изменения данных по цепочке триггеров будите логировать?

                Например пользователь из интерфейса нажимает на кнопку, которая запускает процедуру в БД, в этой процедуре выполняется добавление-изменение данных в 10 таблицах, в 5 из этих таблиц еще срабатывают триггеры, которые меняют данные еще в каких-то таблицах.

                И что за логи вы запишите из интерфейса?

                Или еще интереснее, у пользователя настроен план автоматических действий, в 10.00 дергается такая-то процедура, в 12.30 другая, а в 15.00 третья. Дергается все это универсальным скриптом, который просто по времени дергает те или иные процедуры (какие пользователь укажет) в которых может меняться неизвестное количество данных в нескольких таблицах. Как в этом случае собираетесь из внешнего скрипта логировать изменения?


                1. michael_vostrikov
                  20.12.2016 10:58
                  +1

                  Триггеры и процедуры в БД — это «логика в БД». Я написал о том, что делать так не надо. Логику лучше делать внутри приложения.

                  Например пользователь из интерфейса нажимает на кнопку, которая запускает метод объекта на PHP, в этом методе выполняется вызов методов бизнес-логики на добавление-изменение данных в 10 сущностях, в 5 из этих сущностей еще вызываются свои методы, которые меняют данные еще в каких-то сущностях. Все изменения сохраняются в рамках одной транзакции.

                  Или еще интереснее, у пользователя настроен план автоматических действий, в 10.00 запускается такая-то задача на PHP, в 12.30 другая, а в 15.00 третья. Задачи запускаются универсальным PHP-скриптом, который прописан, например, в кроне, и запускает те задачи, которые указал пользователь. В задачах может меняться любое количество данных, если мы написали в коде логирование при изменениях, то оно и будет выполняться.


                  1. mail-online
                    20.12.2016 11:24

                    Ну на мой взгляд это как раз овер-инженеринг :) но как говорится каждому свое. Я в большей степени все таки по БД специализируюсь чем по вебу. Поэтому больше решений в структуре БД.

                    Как кстати у вас пользователь будет создавать эти задачи на php из своего интерфейса? Мне кажется как минимум с точки зрения безопасности нельзя давать таких вещей неизвестным юзерам. Натворить делов могут. Думаю с точки зрения облачной платформы доступной всем — такой подход неприемлем.


                    1. lair
                      20.12.2016 11:32

                      Я в большей степени все таки по БД специализируюсь чем по вебу. Поэтому больше решений в структуре БД.

                      Это не значит, что они правильные — это значит, что вы натягиваете решения в зону своей экспертизы.


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

                      А что, создание задач в БД чем-то отличается?


                      1. mail-online
                        20.12.2016 11:42

                        Это не значит, что они правильные — это значит, что вы натягиваете решения в зону своей экспертизы.


                        Статья извиняюсь про БД и в разделе БД.

                        А что, создание задач в БД чем-то отличается?


                        По безопасности конечно отличается. В БД по сути только исключать вопросы sql-иньекций и бесконечных циклов. Изолировать возможность деятельности только конкретной базой.
                        А давать программировать на php неизвестному юзеру на вашем сервере — ну наверное возможно, но в весьма урезанной версии, исключить опасные команды, в общем это намного сложнее на мой взгляд проконтролировать.


                        1. lair
                          20.12.2016 11:52

                          Статья извиняюсь про БД и в разделе БД.

                          … это не значит, что все решения в БД оправданы.


                          В БД по сути только исключать вопросы sql-иньекций и бесконечных циклов. Изолировать возможность деятельности только конкретной базой.

                          Отправка почты? Вызов веб-сервисов? Криптография? Это совершенно типовые задачи в ERP-системе.


                          А давать программировать на php неизвестному юзеру на вашем сервере

                          А не надо давать PHP, надо давать DSL в песочнице.


                        1. michael_vostrikov
                          20.12.2016 12:05

                          Хм, причем здесь «программировать на PHP»? Как я понял, у вас пользователь тоже не на SQL программирует, а использует какой-то 1С-подобный язык, который обрабатывается через интерпретатор на PHP. Я имел в виду примерно то же самое, только интерпретатор не транслирует все в БД, а вызывает процедуры, написанные (вами) на PHP.


                          1. mail-online
                            20.12.2016 12:36

                            Нет. Совершенно не так. Есть 2 части:

                            1. Пользователь у меня может полностью программировать структуру БД, создавать таблицы, делать процедуры, триггеры. Абсолютно любой сложности и вложенности. Здесь по сути SQL — только не совсем, логика таже, но реализовано это через веб при помощи постройки определенных элементов. SQL запросов (в вашем понимании, как текст) тут нет. Выглядит примерно так, но тут очень простой пример. Более сложное просто в принтскрин не влезет. Поддерживаются любые структуры и компануются они в древовидную структуру определенной последовательность выполнения. Потом идет процесс компилирования — в БД создается нужная процедура из этого.

                            2. Пользователь может произвольно редактировать веб интерфейс, выводить в элементы интерфейса данные из своих процедур, создавать элементы управления, связывать эти элементы с конкретными процедурами в БД. И т.д. Тут двумя словами не описать. Но тут все делается исключительно редактором при помощи кнопочек, никакого произвольного php текста.
                            У меня есть справка, Часть 3 и 4 посмотрите если интересно. Но я пока по программированию далеко не все описал. Описана только частично программирование в БД, к описанию веба я пока не приступал. Только оглавление. Думаю в течении 2-3 недель будет описание полное. Пока работаю еще над этим. Очень много работы помимо этого.


                            1. lair
                              20.12.2016 12:43

                              Потом идет процесс компилирования — в БД создается нужная процедура из этого.

                              Ну вот на этом этапе надо создавать не процедуру в БД, а код на аппликейшн-сервере. И все будет хорошо.


                              Собственно, это и есть DSL, просто визуальный. Лет десять назад они были в моде, как сейчас — не знаю.


                              PS Я пять, что ли, лет имел дело с прикладной системой такого же генезиса. В поддержке она была отвратительна до невозможности.


                              1. mail-online
                                20.12.2016 12:54

                                А по конкретнее можете описать, что было отвратительного? Мне просто действительно интересно.

                                И что за система? Я кроме 1С с подобной концепцией никого не знаю. А тем более с реализацией через веб.


                                1. lair
                                  20.12.2016 13:10

                                  А по конкретнее можете описать, что было отвратительного?

                                  Отсутствие всего стандартного инструментария для разработки. Версионное хранилище? Рефакторинг? Тесты? Не, ни шанса.


                                  1. mail-online
                                    20.12.2016 15:19

                                    lair — тут нет требования к проф среде разработки. Главное предназначение этого механизма — корректировки нюансов. Он дает пользователям потрясающую гибкость.
                                    Все пытаются сделать системы, чтобы ее структура подходила всем компаниям, а такого не бывает. У каждой компании свой уникальный бизнес-процесс. Можно сделать что-то среднее — подходящее примерно всем, но как известно — дьявол кроется в нюансах. Нюансы бизнес-процессов копании никогда не угадаете. Но используя такие средства редактирования можно их довольно просто реализовать.

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

                                    Тесты — есть система запуска процедуры и просмотра предварительного результата. Для запросов работает очень удобно. Для отладки добавления или удаления данных такое подходит не очень, согласен. Но этого как правило не требуется, я пока не сталкивался чтобы это понадобилось.

                                    Вообще добавления и редактирования давно с нуля не делал чтобы отлаживать. У меня есть очень удобный механизм созадния типовых процедур мегаупрощающий разработку. После создания таблицы можно просто нажать кнопочку «Создать типовые процедуры» и система создаст 5 процедур к этой таблице: Вывод всех данных, Вывод строки, Редактирование строки, Добавление строки, Удаление строки. Там уже будут прописаны все переменные вашей таблицы, весь функционал. Во многих случаях этого даже достаточно. Если требуется что-то немного другое, какие-то фильтры, просто процедура нужная доредактируется. Это намного быстрее и проще.
                                    Можно в процедуру импортировать все переменные с набором из таблицы, можно заполнить вывод селекта по сопоставлению имен переменных.
                                    В общем на самом деле разрабатывать тут гораздо быстрее чем в IBExperte. В нем чтобы сделать процедуру к таблице: давай прописывать переменные и типы данных их на вход-выход, потом перечислять их в select, потом пречислять их в into. На это пипец времени уходит впустую. Я уже забыл что это такое. У меня бывает что целый небольшой модуль в течении часа появляется.


                                    1. lair
                                      20.12.2016 15:24

                                      тут нет требования к проф среде разработки.

                                      Вот и люди, которые ту систему писали, так думали.


                                      Но используя такие средства редактирования можно их довольно просто реализовать.

                                      … а можно использовать другие средства редактирования, поддерживающие "профессиональную среду разработки", и все равно реализовать нужные бизнес-процессы.


                                      Вресионное хранилище кстати в разработке.

                                      Вот-вот. Вместо того, чтобы использовать любое мейнстримное, вы разрабатываете свое. Я даже стесняюсь спрашивать про диф/мерж.


                                      Тесты — есть система запуска процедуры и просмотра предварительного результата. Для запросов работает очень удобно.

                                      Это вообще не имеет отношения к тестам.


                                      Вообще добавления и редактирования давно с нуля не делал чтобы отлаживать.

                                      CRUD — это самая тривиальная часть в любом бизнесе, и приличная современная система уже давно генерит CRUD-интерфейс просто по описанию схемы данных.


                                      Цирк начинается, когда заходит речь собственно о бизнес-процессе: это вычисляется так-то, здесь такие-то условия, здесь должно пойти вот так и вот так.


                            1. michael_vostrikov
                              20.12.2016 13:52
                              +1

                              Я не предлагаю никакого произвольного php текста. Все то же самое, как у вас, только вместо процесса компилирования в процедуру БД будет процесс интерпретации интерпретатором. Например, при обработке [:current_timestamp] будет вызов date('Y-m-d H:i:s'). Как вариант, можно сделать компиляцию в PHP код, как это делают шаблонизаторы наподобие Twig. Суть не в том, как это реализовать, а в том, что в этом случае логирование изменений в другой БД становится простой задачей, не требующей добавления триггеров в каждую таблицу.


                              1. mail-online
                                20.12.2016 14:35

                                Ну на мой взгляд разработать такую систему для того чтобы логировать более просто — весьма непросто. Проще уж 3 таблички в БД создать :)
                                Но вы правы, теоретически ваша версия имеет право на жизнь. Просто получится альтернативная технологическая платформа.
                                Но что из этого «овер-инженеринг» большой вопрос :)


                                1. lair
                                  20.12.2016 14:39

                                  Ну на мой взгляд разработать такую систему для того чтобы логировать более просто — весьма непросто.

                                  А эта система — она не для того, чтобы логировать более просто. Она для того, чтобы все делать более просто.


      1. barker
        19.12.2016 20:51

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

        upd з.ы. А на свою «платформу» вы ссылку таки оставили, только что заметил (что, кстати, запрещено правилами, имнип). Даже не сомневался, что там увижу charset=windows-1251, вёрстку таблицами и прочие body bgcolor. Цикл статей про веб-разработку будет? Ой всё, на ночь одни расстройства.


        1. mail-online
          19.12.2016 22:27

          barker — такова политика, минимум css и java. Только по большой необходимости.


          1. webus
            20.12.2016 09:40

            OMG! Автор. Вы даже меня заставили вылезти из read-only :)

            такова политика, минимум css и java


            Какая еще у вас там Java? Может все таки Javascript? Или вы там Java-аплеты имели в виду, которые уже deprecated?

            Ну и все таки да. Прочитав ваши статьи, чувствуется что вам не хватает фундаментальных знаний что ли. Про UTC вам верно сказали. Если вы пишете приложение, которое работает в нескольких тайм-зонах, то использовать UTC необходимо. То что вы советуете это скорее анти-паттерн. Вот начитаются новички ваших статей и напишут такое же. А потом поймут все, и будут вспоминать вас недобрым словом :)


            1. mail-online
              20.12.2016 10:34

              Ну вообще как-то мы очень далеко вылезли из темы статьи. Предлагаю все таки вернуться. Мы рассматриваем темы базы данных, а не веб интерфейсов :)

              Javascript имелся ввиду.

              Я объясню свою позицию по данному вопросу если очень интересно.
              Например у меня в бразуере стоит NoScript, и когда я захожу на какой-то сайт, естественно скрипты не запускаются. И вот знаете, очень печально зачастую все выглядит, хорошо если хоть что-то отображается хоть и криво косо, зачастую вообще просто белая страница и понять что там вообще невозможно.

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

              Про часовые пояса — я не вижу смысла комментировать. По моему уже все сказано. Да и из другой статьи это. Предлагаю все таки там это обсуждать.
              С какими-то высказываниями я согласен, все доводы я услышал и выводы сделаны, и я действительно в последующей разработке буду кое что новое учитывать, но повторюсь, нельзя в лоб давать каждому пользователю менять свой часовой пояс, только компания целиком или дробить до офиса или склада — иначе будет белеберда в работе т.к. есть рабочие группы, есть много других нюансов. А в рамках темы статьи, как сделать именно на базы в рамках одного сервера запись в разной временной зоны — на мой взгляд нет решения проще.
              Почему люди негативно это восприняли — ну странно на самом деле. Хотя за некоторые конструктивные комментари — спасибо. Лично я посмотрел на проблему немного с другой стороны.


  1. host13
    19.12.2016 13:13

    «LOG_PACET», «SCRAM» и «SCRUM» путаются на сайте, «NUM_POLE», «API» среднего рода на сайте…
    Попахивает дельфями образца середины девяностых


    1. mail-online
      19.12.2016 13:13

      Спасибо за инфу, поправил.


  1. eviland
    19.12.2016 19:36

    У Oracle для этого есть RAC, распределённые транзакции и DBMS_AQ в зависимости от того, какой результат вам нужен.

    Зато Firebird бесплатный.


  1. mmaxm
    19.12.2016 19:36

    >>Есть более быстрая и лучшая процедура DROP TABLE.
    по поводу firebird не скажу, у MS SQL, в описанном случае, для того чтобы быстро очистить таблицу лучше использовать операцию TRUNCATE TABLE, она это делает быстрее, правда есть некоторые ограничения на использования, например таблица не должна содержать внешних ключей для других таблиц.


    1. mail-online
      19.12.2016 20:06

      Насколько я знаю, TRUNCATE TABLE в файрберде нет. В ibexpert есть команда «Empty Table» — но для меня загадка как она работает. Если кто в курсе по empty, буду благодарен за описание.


  1. smplpro
    19.12.2016 21:03

    Имя поля

    ZAPIS SMALLINT,

    доставляет.
    Не уверен как в Firebird, а в MySQL я бы попробовал писать логи в черную дыру и настроить репликацию таблицы с логами. В Firebird есть репликация?


    1. mail-online
      19.12.2016 22:25

      Я пример писал, вы можете обозвать как угодно.
      Штатной репликации в файрберде нет. Есть программы с реализацией репликации. У этих ребят вроде есть насколько я знаю http://www.ibase.ru/hqbird/