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

В других и вовсе приводит к засорению базы мусором с других площадок и к ошибкам после «простейшего мержа».

Знакомых с такими ситуациями, критиков и знающих точно, что я изобрел велосипед — приглашаю под кат.

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

Общая концепция проста как апельсин и принимает 2 установки:

1. База данных должна знать о том, какие патчи к ней применялись.
2. При создании связей в записях ни в коем случае не используются значения идентификаторов. (кроме полученных в вычислениях).

При соблюдении этих условий продукт должен работать на любой машине с объективно стабильным результатом. Да, может показаться, что второе условие не выполнимо, но если все базы создаются одним и тем же скриптом — расхождение идентификаторов будет уже аномалией.

Итерации в процессе создания схемы.


«Кто создал столбец?»
«Что здесь должно храниться? Откуда эти числа? Пишите хотя бы комментарии!»
«Мы это уже 100 лет как не используем. Откуда оно здесь?»

Знакомо? Многие принимают как аксиому тот факт, что база данных существует «как есть». Это письмо Дяди Федора, которое не имеет своего автора. Но все так лишь отчасти. У каждого изменения, столбца и записи есть свой автор. Все изменения делаются на линии времени. Похоже на git / svn, etc.? Мы все активно пользуемся системами версионного контроля и прекрасно сдружились со всеми бонусами, которые дарит нам такой подход. Давайте попробуем применить его здесь.

Итак, переходим к практике и взглянем на несметное количество папок в проекте.

image

Для начала создадим таблицу с информацией о патчах. Здесь мы запомним какие патчи у нас отработали (name + type) и с каким результатом (result)

dc.sql
CREATE TABLE IF NOT EXISTS dc (
  id INTEGER(11) AUTO_INCREMENT NOT NULL,
  code VARCHAR(100) NOT NULL,
  type VARCHAR(50) CHARACTER SET utf8 COLLATE utf8_bin NOT NULL,
  result VARCHAR(1) CHARACTER SET utf8 COLLATE utf8_bin NOT NULL,
  m_date TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
  PRIMARY KEY (id)
);


Чтобы все работало так, как мы хотим, добавим самый главный файл с оригинальным названием.

start.sh
#!/usr/bin/env bash

username="habr"
password="habr"
database="mydatabase"

cd COMMON
mysql --user ${username} --password=${password} -D${database} < dc.sql
if [ $? -eq "1" ]; then
    exit $?
fi

echo ''
echo '>>> TABLES'
echo ''
cd TABLE
FILES=*
for f in ${FILES}
do
  scriptName=`expr "$f" : '\([a-z_]*\)'`
  var=$(mysql --user ${username} --password=${password} -D${database} <<< "select count(*) from information_schema.tables as t where t.TABLE_NAME='${scriptName}'" -s)
  if [ ${var} -ne '1' ]; then
    echo "Processing $f file..."
    mysql --user ${username} --password=${password} -D${database} < ${f}
    mysql --user ${username} --password=${password} -D${database}<<<"INSERT INTO dc (code, type, result) values ('${f}', 'CREATE TABLE', "$?")"
    if [ $? -ne 0 ]; then
        exit $?
    fi
  else
    echo '--- Skip '${f}' ---'
  fi
done

echo ''
echo '>>> FOREIGN KEYS'
echo ''
cd ../F_KEY
FILES=*
for f in ${FILES}
do
  scriptName=`expr "$f" : '\([a-z_A-Z]*\)'`
  var=$(mysql --user ${username} --password=${password} -D${database} <<< "select count(*) from information_schema.table_constraints as t where t.constraint_name='${scriptName}'" -s)
  if [ ${var} -ne '1' ]; then
    echo "Processing $f file..."
    mysql --user ${username} --password=${password} -D${database} < ${f}
    mysql --user ${username} --password=${password} -D${database}<<<"INSERT INTO dc (code, type, result) values ('${f}', 'CREATE FK', "$?")"
    if [ $? -ne 0 ]; then
        exit $?
    fi
  else
    echo '--- Skip '${f}' ---'
  fi
done
echo ''


echo ''
echo '>>> LOAD DATA SCRIPTS'
echo ''
cd ../DATA
FILES=*
for f in ${FILES}
do
  scriptName=`expr "$f" : '\([a-z0-9]*\)'`
  var=$(mysql --user ${username} --password=${password} -D${database} <<< "select count(*) from ${database}.dc as t where t.code='${f}' and result='0'" -s)
  if [ ${var} -ne '1' ]; then
    echo "Processing $f file..."
    mysql --user ${username} --password=${password} -D${database} < ${f}
    mysql --user ${username} --password=${password} -D${database}<<<"INSERT INTO dc (code, type, result) values ('${f}', 'LOAD DATA', "$?")"
    if [ $? -ne 0 ]; then
        exit $?
    fi
  else
    echo '--- Skip '${f}' ---'
  fi
done
echo ''

echo ''
echo '>>> LOAD TRIGGERS'
echo ''
cd ../TRIGGER
FILES=*
for f in ${FILES}
do
  scriptName=`expr "$f" : '\([a-z_0-9]*\)'`
  var=$(mysql --user ${username} --password=${password} -D${database} <<< "select count(*) from information_schema.triggers as t where t.trigger_name='${f}'" -s)
  if [ ${var} -ne '1' ]; then
    echo "Processing $f file..."
    mysql --user ${username} --password=${password} -D${database} < ${f}
    mysql --user ${username} --password=${password} -D${database}<<<"INSERT INTO dc (code, type, result) values ('${f}', 'LOAD TRIGGER', "$?")"
    if [ $? -ne 0 ]; then
        exit $?
    fi
  else
    echo '--- Skip '${f}' ---'
  fi
done
echo ''


exit $?


Принцип был взят как самый очевидный — имя патча оно же имя файла. Для триггеров, таблиц и внешних ключей проверяется наличие объектов с такими именами через information_schema. Если результат отрицательный — выполняется прогон соответствующего скрипта.

Никаких коллизий идентификаторов


Гораздо интереснее наполнение каталогов данными. Оно проводится через исполнения go-скриптов в каталоге /COMMON/DATA.

Факт отработки скрипта фиксируется в нашей таблице dc (data containers). Если все было ок — при следующем прогоне файл пропускаем.

Исполнение идет в алфавитном порядке, так что наиболее универсальным является использование timestamp в имени.

В самом простом исполнении это обычные INSERT-запросы со светлой надеждой на стабильность и предсказуемость автоинкремента.

-- добавление базовых ресурсов

INSERT INTO ds.reso_type (name, description) VALUES ('Пища', 'Основной ресурс, обеспечивающий жизнь');
INSERT INTO ds.reso_type (name, description) VALUES ('Дерево', 'Основной ресурс, обеспечивающий строительство');
INSERT INTO ds.reso_type (name, description) VALUES ('Камень', 'Основной ресурс, обеспечивающий строительство');


# noinspection SqlResolve
INSERT INTO ds.human_type (name, description) VALUES ("Чернь", "Кто все эти люди?");

А как же быть с более сложными данными? Используем вложенные запросы.

INSERT INTO reso_speed (resoId, popId, speed) VALUES (
  (SELECT ht.id
   FROM human_type ht
   WHERE ht.name = 'Чернь'),
  (SELECT rt.id
   FROM reso_type rt
   WHERE rt.name = 'Пища'),
  30);

Или же напишем вспомогательные функции.

#создание tasktype
DELIMITER //
CREATE FUNCTION installTaskType(name VARCHAR(50), title VARCHAR(100), groupId INT(11), description VARCHAR(1000))
  RETURNS int(11)
  BEGIN
    SELECT count(id) into @c from tasktype as t where t.name = name COLLATE 'utf8_unicode_ci';
    if (@c = 0) THEN
      insert  into `tasktype`(`name`,`title`,`group_id`,`description`)
      VALUES (name, title, groupId, description);
      RETURN LAST_INSERT_ID();
    END IF;
    SELECT id INTO @taskTypeId FROM tasktype as t where t.name = name COLLATE 'utf8_unicode_ci';
    RETURN @taskTypeId;
  END //
DELIMITER ;

# создание новых taskgroup
DELIMITER //
CREATE FUNCTION installTaskGroup(name VARCHAR(255))
  RETURNS INT(11)
  BEGIN
    SELECT count(id) into @c FROM taskgroup as t where t.name = name COLLATE 'utf8_unicode_ci';
    if (@c = 0) THEN
      INSERT INTO taskgroup (`name`) VALUES (name);
      RETURN LAST_INSERT_ID();
    END IF;
    SELECT id INTO @groupId FROM taskgroup as t WHERE t.name = name COLLATE 'utf8_unicode_ci';
    RETURN @groupId;
  END //
DELIMITER ;

В go-скрипт пишем:


SELECT installTaskGroup('TEST_GROUP') into @groupId;
SELECT installTaskType('TEST_TASK', 'Это тестовая задача отправки статьи на HABR', @groupId, '');

Итак, мы получили подход, который позволяет выработать единую систему управления базой данных на примере git + mysql.

В качестве профита получили:

— авторство + в некоторых случаях привязка задач таск-трекера;
— последовательность в накате данных;
— новый проект на jenkins;
— спокойные нервы.

Искренне благодарю всех дочитавших за внимание. Приветствую и жду критику). Согласен, что продукт местами получился сырой, но для домашних нужд подходит отлично.

Тапками сильно не закидывайте — это первая попытка опубликоваться на Хабре (девушкам можно).
Поделиться с друзьями
-->

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


  1. greabock
    02.01.2017 15:18
    +18

    Это называется «миграции схемы бд». Есть куча различных реализаций на куче разных языков.


  1. sshikov
    02.01.2017 15:24
    +3

    Не знаю, что вы в сети искали, но принципы построения подобных продуктов точно никто от вас не прячет.

    Можно например взглянуть на liquibase, или на flyway (это лишь два первых пришедших в голову, и далеко не единственные), где подобные (и еще кое-какие другие полезные) концепции давно реализованы. Идее хранить список примененных патчей в самой базе — наверное сто лет в обед.

    И кстати, вынужден вас огорчить — на ваши же три вопроса:

    «Кто создал столбец?»
    «Что здесь должно храниться? Откуда эти числа? Пишите хотя бы комментарии!»
    «Мы это уже 100 лет как не используем. Откуда оно здесь?»

    данный велосипед ответов все равно не дает. Во всяком случае — простых и полных ответов.


    1. gunya
      02.01.2017 19:35
      +1

      > «Кто создал столбец?»
      > «Что здесь должно храниться? Откуда эти числа? Пишите хотя бы комментарии!»
      > «Мы это уже 100 лет как не используем. Откуда оно здесь?»

      Семь бед — один git blame.
      Если по commit message непонятно, зачем и откуда появилась миграция БД — это должно было быть выявлено на этапе code review.


      1. sshikov
        02.01.2017 19:45
        +3

        Чтобы понять, «кто создал столбец» — для начала надо понять, какой скрипт это содержал. Причем «это» может содержать как create table, так и alter table, в разных вариантах — и тут важно понимать, какой из них был применен последним.

        Blame на этот вопрос не отвечает, точнее отвечает не на этот. Blame это второй этап — когда мы знаем скрипт, мы можем узнать автора. А первый этап тут как раз не раскрыт.

        Вопрос же «что тут хранится» вообще к blame никаким боком по-моему не относится.


        1. sshikov
          02.01.2017 19:55

          На самом деле полноценный ответ на поставленные вопросы — это история изменений объекта базы (обычно колонки или таблицы). Которая содержит все примененные ранее скрипты миграции, повлиявшие на объект.

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


        1. gunya
          02.01.2017 20:03

          Обычно найти в миграциях нужный столбец / таблицу не так сложно — есть grep / поиск по проекту.

          В идеале, конечно, нужно в комментарии к таблице/колонке ставить комментарий с ID миграции — но не уверен, что все СУБД это поддерживают и не уверен, что есть системы управления миграциями, которые это умеют.


          1. sshikov
            02.01.2017 21:44

            Вы забываете, что не все миграции могли быть применены к конкретной базе в данный момент времени. Это как минимум одна причина, почему просто grep недостаточно. Вторая — это порядок применения потенциально многих alter table, о котором grep тоже нам мало что скажет.


            1. gunya
              02.01.2017 22:18

              Для этого очень помогает числовой идентификатор миграции и convention для имен файлов:

              — 001_create_users_table.sql
              — 002_create_posts_table.sql
              — 003_add_author_to_posts.sql

              Есть засада — при работе в параллельных ветках могут появиться две миграции с одним id — но это элементарно проверяется во время CI


              1. sshikov
                03.01.2017 11:11

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

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

                Что до конвенций… ну вот вам типичная миграция в моем проекте:

                — найти constraint (некоторые из них были созданы давно, и соглашениям об именовании не соответствуют), как правило это PK или FK, и имя сгенерировано автоматически.
                — drop constraint по найденному ранее имени
                — drop computed column
                — alter table alter column
                — re-create constraint
                — re-create computed column

                Это все одна достаточно типовая сделанная вручную миграция. Всего-лишь изменение типа колонки, на которую завязаны либо constraint, либо другие вычисляемые колонки. Почему так? А потому что MS SQL, например, иначе не умеет, даже в enterprise edition. Если у вас база большая, то все может быть еще намного более грустно.

                Наименование вида add_author_to_posts ничего нам в данном случае не даст, потому что оно отражает бизнес смысл миграции, а какие конкретно объекты базы она затронула — тут не говорится.

                Т.е. по хорошему, механизм миграций должен бы уметь как минимум группировать операции DDL, которые умеет текущая база, и понимать, что вот эти вот пять или десять изменений — это часть одного большого изменения с точки зрения бизнеса. И еще понимать, какие конкретно объекты примененный конкретный скрипт затронул, и что он с ними сделал. А это совсем не просто, и один очевидный случай уже озвучивали — отличить переименование колонки от drop + create это задачка вовсе не для grep, тут даже анализ SQL не всегда может помочь.


                1. gunya
                  03.01.2017 15:14

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

                  Любой тул для миграций хранит в БД идентификатор последней примененной миграции. Если же тул умеет применять миграции в разном порядке (alembic) — он же и умеет выводить текущее состояние базы.

                  > Т.е. по хорошему, механизм миграций должен бы уметь как минимум группировать операции DDL, которые умеет текущая база, и понимать, что вот эти вот пять или десять изменений — это часть одного большого изменения с точки зрения бизнеса. И еще понимать, какие конкретно объекты примененный конкретный скрипт затронул, и что он с ними сделал. А это совсем не просто, и один очевидный случай уже озвучивали — отличить переименование колонки от drop + create это задачка вовсе не для grep, тут даже анализ SQL не всегда может помочь.

                  Поэтому для этого есть программист, который умеет парсить SQL и комментарии.


                  1. sshikov
                    03.01.2017 21:25

                    >Поэтому для этого есть программист, который умеет парсить SQL и комментарии.

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


                1. gunya
                  03.01.2017 15:18

                  С другой стороны, если вот очень хочется комбайн, который умеет в самые спорные хотелки — есть Liquibase. Он умеет по каждому изменению документацию генерировать, например.


                  1. sshikov
                    03.01.2017 21:21

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

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

                    И чтобы инструмент сам бы понимал, какие из модификаций внесенных в ER-модель, в какие скрипты выливаются, и помнил бы историю для конкретной базы, и общую историю модели.

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


                    1. ggrnd0
                      03.01.2017 23:49
                      +1

                      Примерно похож EntityFramework.
                      Но до идеального инструменту ему далеко.

                      А в Enterprise подобного софта должно быть навалом на любой вкус.

                      Сомневаюсь что есть инструмент умеющий работать с разделением/объединением таблиц.


  1. hardex
    02.01.2017 16:15
    +6

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


    1. alexey-m-ukolov
      02.01.2017 16:26
      +5

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


      1. k12th
        02.01.2017 16:41
        +5

        Любит человек, чтобы девушки в него тапками кидались, чего ж такого, это же по обоюдному согласию и никому вреда не приносит:D


      1. vics001
        02.01.2017 16:42
        +3

        Бдсм похоже


        1. ohotNik_alex
          05.01.2017 17:26

          снял побои — получил борщ))))


  1. conf
    02.01.2017 16:38
    +1

    Как уже отметили, это реализовано во всех крупных фреймворках, например, вот так в Ruby on Rails: http://rusrails.ru/rails-database-migrations. Ищите по ключевым словам «database migrations».


  1. alprk
    02.01.2017 16:52

    Миграции это конечно замечательно, а пробовал кто-нибудь организовать проект таким образом чтобы миграции были артефактом сборки? Т.е. например есть у меня проект, в котором есть директории tables, views и пр. Мы вносим изменения в код создания одной из таблиц, затем запускаем сборку

    make migration 
    

    и получаем в папке migrations код обновления схемы до новой версии.


    1. andreymal
      02.01.2017 17:03
      +2

      В общем случае полностью автоматическое создание такого кода невозможно (без логики не понять, удалили один столбец и создали другой или просто переименовали столбец), но как минимум у Django есть django-admin makemigrations


    1. aikixd
      02.01.2017 18:08

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


    1. greabock
      02.01.2017 18:23

      Ну, для примера, на php в `doctrine-orm` и `propel` (первая это datamapper, а вторая это active record), можно делать diff на основе схемы модели сущностей, которая может быть описана в отдельном xml/yml файле, или в аннотацияx к самим сущностям.


    1. gunya
      02.01.2017 19:32

      У python есть alembic, который делает интроспекцию текущей БД, смотрит на модели ORM, делает сравнение и генерирует миграцию.

      Естественно, он может отловить не все, например, переименования таблиц и индексов надо дописывать вручную. Но 80% простых миграций генерируются автоматически, но всегда нужно проверять, какие миграции получаются на выходе.


    1. mayorovp
      02.01.2017 19:33

      Первая проблема тут в том, что создание миграций должно быть stateful, а не stateless. Миграция зависит не от текущих исходников — а от разности текущих исходников и прошлой версии. Это выбивается из идеологии make. Создание миграции больше похоже на git commit, чем на make.


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


      1. alprk
        10.01.2017 15:12

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


      1. alprk
        10.01.2017 15:14

        применительно того как получить миграцию, можно использовать что-то подобное http://www.apgdiff.com/, просто получать разницу между временными БД


        1. ohotNik_alex
          10.01.2017 15:44

          creates output with DDL statements that can be used to update old database schema to new one


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


        1. gunya
          10.01.2017 16:19
          +2

          Все упирается в rename:
          — ALTER TABLE… RENAME TO
          — ALTER TABLE… RENAME COLUMN… TO…

          diff сгенерирует максимум:
          — DROP COLUMN / ADD COLUMN


          1. alprk
            12.01.2017 17:22

            да, с этим все плохо, конечно(


    1. ggrnd0
      02.01.2017 22:31

      В ECM7.Migrator есть поддержка nant/msbuild. А так же есть консольная утилита и возможность запустить миграции прямо из кода.


      Hibernate/NHibernate умеют мигрировать БД сами.


      1. mayorovp
        02.01.2017 22:42

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


    1. Danik-ik
      05.01.2017 09:35

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


  1. D_T
    02.01.2017 16:56
    +5

    В самом простом исполнении это обычные INSERT-запросы со светлой надеждой на стабильность и предсказуемость автоинкремента.

    Зачем таким таблицам автоинкремент если надо одинаковый ID во всех копиях БД? Если это таблицы с метаданными, которые могут меняться только очередным скриптом обновления, то тут просто надо убрать автоинкремент и явно задавать значение ID в INSERT-запросе.


    1. ohotNik_alex
      05.01.2017 17:08

      для работы в разных ветках при участии нескольких разработчиков. при завязке на идентификатор можно при мерже выяснить, что id занят.


      1. andreymal
        06.01.2017 02:37

        Тогда просто изменить id да смержить, не?


        1. ohotNik_alex
          09.01.2017 11:06

          если ветка отводилась на месяц — два (такое при крупных переработках случается часто) — это чревато большим количеством ручных операций. всегда можно что-то где-то недоглядеть. опасно. особенно если плохо налажено тестирование.


          1. andreymal
            09.01.2017 11:11

            Надо налаживать тестирование) А большое количество ручных операций на всего лишь смену айдишников по-моему намекает, что что-то ещё тоже не налажено


    1. ggrnd0
      05.01.2017 19:53

      И лучше давать строковые id при наличии такой возможности.


  1. gunya
    02.01.2017 19:52
    +1

    Написать скрипт на bash — это конечно, хорошо, но только вы не проверяете, что миграция вообще завершилась, не говоря об обработке ошибок:

        mysql --user ${username} --password=${password} -D${database} < ${f} # Explosion
        mysql --user ${username} --password=${password} -D${database}<<<"INSERT INTO dc (code, type, result) values ('${f}', 'LOAD TRIGGER', "$?")" # OK
        if [ $? -ne 0 ]; then
            exit $?
        fi
    


    Более того, что произойдет, если миграция применилась частично? Что произойдет, если скрипт по какой бы то ни было причине упадет в середине применения миграции (ребут сервера, потеря сети, что угодно)? Что произойдет, когда необходимо произвести даунгрейд базы?

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

    При этом:
    — Все миграции обернуты в транзакции, следовательно миграции атомарны. К сожалению, в mysql нет транзакций для изменения схем данных, но это можно обойти. Миграции данных будут атомарны везде.
    — У этих тулов есть community — вы перестаете быть незаменимым (и можете спокойно ходить в отпуск), плюс вы избавитесь от overhead на поддержание своей утилиты для миграций.
    — Вы получите не только тот функционал, который есть сейчас, но и как минимум получите возможность rollback. В случае с mattes/migrate можно даже переиспользовать те SQL-скрипты, которые есть у вас.


    1. ohotNik_alex
      05.01.2017 17:21
      -1

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


  1. akzhan
    02.01.2017 20:48

    Вместо тысячи слов


    http://sqitch.org/


  1. Danik-ik
    05.01.2017 09:42

    При создании связей в записях ни в коем случае не используются значения идентификаторов. (кроме полученных в вычислениях)

    Что имеется в виду и почему?


  1. Cad1L
    05.01.2017 17:03

    Советую использовать liquibase.


  1. baldrs_asgaardson
    05.01.2017 17:03

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


  1. miksir
    05.01.2017 17:03

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


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


    1. ohotNik_alex
      05.01.2017 17:05

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


    1. ggrnd0
      05.01.2017 20:18

      Еще можно откладывать удаление колонок на несколько миграций позже.

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


      1. miksir
        05.01.2017 21:08

        Именно на несколько миграций позже это и откладывается. Главное, что бы разработчики не забывали помечать такие элементы схемы как deprecated. Или же были подготовлены сответствующие скрипты синхронизации со схемой приложения (например, в случае использования ORM). Такие изменения прекрасно делаются и без остановки сервиса, равно как и бакапы.


  1. Pinsky
    05.01.2017 22:34
    -1

    Есть золотое слово «апрув». Все изменения в продовой БД проводить только с получения апрува и фиксировать каждое из них и ответственного.