Pull to refresh

Comments 55

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

Но я таких идеальных инструментов не видел. Для тех что видел — как правило типичны проблемы с версионированием. Возможно они есть и стоят больших денег, поэтому никто из работодателей их не покупает.
UFO just landed and posted this here
То, что вы выбрали путь разработчика — похвально, но просить к своей статье лучшего отношения из-за того, что вы девушка — это уже сексизм.
Судя по статье и логину, автор — мужчина и он зачем-то разрешает девушкам кидаться в него тапками. Странно, одним словом.

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

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

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

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

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

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

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

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


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

согласен, действительно с идеологией make расходится, возможно будет более здраво если предполагать не создание цепочки миграций и одной миграции для приведения конкретной базы в состояние, описанное кодом?
применительно того как получить миграцию, можно использовать что-то подобное http://www.apgdiff.com/, просто получать разницу между временными БД
creates output with DDL statements that can be used to update old database schema to new one


даже на уровне только схемы часто возникают коллизии. как минимум большинство подобных инструментов (за данный конкретный не скажу) работают исключительно на добавление.
а есть еще данные, которые нужно как-то мержить между площадками.
Все упирается в rename:
— ALTER TABLE… RENAME TO
— ALTER TABLE… RENAME COLUMN… TO…

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

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

А если у вас materialized view на несколько гигабайт?

Проверить, нужно ли пересоздавать — и при необзодимости пересоздать. Другого варианта попросту нет — конструкции ALTER VIEW в не существует.

UFO just landed and posted this here

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

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

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

Зачем таким таблицам автоинкремент если надо одинаковый ID во всех копиях БД? Если это таблицы с метаданными, которые могут меняться только очередным скриптом обновления, то тут просто надо убрать автоинкремент и явно задавать значение ID в INSERT-запросе.
для работы в разных ветках при участии нескольких разработчиков. при завязке на идентификатор можно при мерже выяснить, что id занят.
Тогда просто изменить id да смержить, не?
если ветка отводилась на месяц — два (такое при крупных переработках случается часто) — это чревато большим количеством ручных операций. всегда можно что-то где-то недоглядеть. опасно. особенно если плохо налажено тестирование.
Надо налаживать тестирование) А большое количество ручных операций на всего лишь смену айдишников по-моему намекает, что что-то ещё тоже не налажено
UFO just landed and posted this here
Написать скрипт на 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-скрипты, которые есть у вас.
да, вы правы. тут у меня логическая ошибка. отслеживается результат второго выражения. подпилю.
за ссылки огромнейшее спасибо — ознакомлюсь то ли это.
атомарность миграции вещь полезная, но не обязательная. предложенный принцип работы таков, что все отрабатывает как с бусинками. нанизываются команды одна за другой. если встретим проблему — работа прекратится и продолжится со скрипта, который не прошел. хотя в случае обрыва связи вопрос. надо проверить опытом.
даунгрейд в текущей реализации не предусмотрен. хотя для себя я всегда держу rollback скрипты к каждому изменению (где это возможно, разумеется).
При создании связей в записях ни в коем случае не используются значения идентификаторов. (кроме полученных в вычислениях)

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

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

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

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


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

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

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

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

Sign up to leave a comment.

Articles