Версионирование структуры БД в MySQL: MySQL Migration with PHP

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

    Мои требования к инструменту очень просты:
    • Как бы я не издевался над структурой данных в приложении, инструмент должен уметь изменить структуру в другой инсталляции приложения так, чтобы она была идентична моей.
    • System requirements: PHP и MySQL — не более того.
    • Бесплатность.
    • Открытость.




    Похожие инструменты


    Давайте рассмотрим существующие интрументы. Если я что-то недогуглил — отпишитесь в комментах — буду благодарен, добавлю в статью.
    • Ruby on Rails — Доп. зависимость от RoR. UPD: подробности в комментах.
    • Doctrine — насколько я понял — нужно сначала изменить schema.yml, затем сгенерировать миграцию
    • MySQL Migration Toolkit — требует установки Java (не разбирался)
    • MySQL Workbench — требует графической оболочки и нескольких кликов мышью, умеет генерить alter-скрипты, которые можно потом накатывать автоматически.
    • habrahabr.ru/blogs/php/63585 — статья про Phing.
    • code.google.com/p/mygrate — Python
    • code.google.com/p/mysql-php-migrations — умеет генерить пустые классы миграций, запросы в них вписываются руками
    • svn.limb-project.com/misc/migration — не удавалось запустить, параметры подключения к БД захардкожены во всех 3х или 4х скриптах. Как обстоят дела сейчас не знаю. Знаю, что велись работы в этом направлении. Korchasa! Прокомментируешь?
    • Платные инструменты


    Рассмотрев все эти варианты я решил создать свой инструмент, который бы удовлетворил всем этим требованиям. На мои решения при сборке велосипеда написании инструмента более всего повлияли два проекта: code.google.com/p/mysql-php-migrations и svn.limb-project.com/misc/migration

    Что мы умеем:

    • Работать только в CLI-режиме.
    • Создать инициализационную схему.
    • Проинициализировать БД.
    • Создать PHP-класс миграции, в который уже не надо писать запросы руками — там все есть!!!
    • Накатить миграции до определенной даты.
    • Откатить миграции до определенной даты (осторожно, вы можете потерять данные навсегда )
    • Показать список доступных миграций, пометив текущую.
    • Хранить данные о версионности БД в таблице с заданным пользователем именем.


    Чего мы не умеем:

    1. Создавать ALTER-скриты — все хранится внутри классов.
    2. Накатывать дампы и ALTER-скрипты.
    3. Работать с PDO — нам требуется MySQLi.
    4. Бегать за пивом.


    Что мы имеем?

    Всего один конфиг-файл

    config.ini
    host=localhost
    user=root
    password=
    db=mmpi_test
    savedir=db ; каталог для хранения классов миграций
    verbose=On
    versiontable=db_version ; имя таблицы для хранения мета-данных о версионности


    Небольшую библиотеку кода.

    Всего один исполняемый файл: ./migration.php

    Несколько команд:

    • help: Показать HELP
    • schema: Создать инициализационную схему.
    • init: Загрузить инициализационную схему (инсталлировать БД)
    • create: Создать новую миграцию
    • list: Показать список доступных миграций. Текущая помечена тремя ***
    • migrate: Произвести миграцию БД до указанного времени или до последней версии, если время не указано


    Все просто. Можно поэксперементировать на тестовой базе и приступать к работе.

    Системные требования:


    • PHP >= 5.3 with MySQLi
    • MySQL >=5.0 ( на четверке просто не пробовал )
    • Пользователь MySQL должен иметь права на создание базы.


    Что нужно знать


    Команда migrate работает с параметрами, которые распознаются функцией strtotime. Если параметры не заданы берется текущее время. Имя класса миграции, а так же переменная внутри него хранит timestamp своего создания. Пользователь MySQL должен иметь права на создание новой базы — инструмент использует это при генерации новой миграции и при накатывании/откатывании миграций, после работы скрипт удаляет временную БД.

    Механизм работы


    При создании миграции: Создается временная БД, в нее вливается schema.php ( там запросы инициализационной схемы), далее по очереди вливаются миграции до самой последней. Снимается массив-снэпшот каждой из баз, определяются различия, создается новый класс миграции. Если вам при апгрейде/даунгрейде нужны какие-либо манипуляции с данными — отредактируйте класс.
    При применении миграции: читается список миграций, определятеся миграция до которой нужен апгрейд/даунгрейд последовательно выполняются все миграции от текущей до целевой.

    Класс миграции: содержит два массива up и down, запросы из которых последовательно выполняются при применении данной миграции в соответствующем направлении.

    Где взять?



    hg clone bitbucket.org/idler/mmp

    P. S. Ну тут надо бы написать, что это альфа-версия! Прошу за код пинать, но не до смерти. Багрепорты и фичреквесты приветствуются. У меня оно работает, но это не значит, что заработает у всех.

    P. P. S. Подумываю о версии для SQLite.
    Поделиться публикацией

    Похожие публикации

    AdBlock похитил этот баннер, но баннеры не зубы — отрастут

    Подробнее
    Реклама

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

      0
      «Doctrine — подход тот же что и в RoR» — ??? 0_о
        0
        Во всяком случае я так понял: Нужно изменить либо модель, либо schema.yml, потом только сгенерировать миграцию.
          +3
          В RoR можно генерировать миграции вообще без моделей.
          script/generate migration migration_name
            +1
            Все поняли не правильно, или недопоняли. Миграции в RoR — это файлы где специальным DSL-ем описывают изменение структуры БД. Модели тут не причем.
              –1
              Ну с RoR я действительно почти не знаком. Однако миграции на RoR я не стал использовать по 2м причинам:
              1. Дополнительная зависимость — я работаю с PHP.
              2. Не смог запустить. В Debian unstable, что стоит на моем лаптопе, никак не хотело устанавливаться. Странные зависимости в gem-ах. Виноваты в этом то ли мэйнтэйнеры пакетов Debian то ли мои кривые руки. Буквально две три недели назад я еще раз захотел пощупать rails — все завелось, однако сам Ruby пришлось собирать в ~/local и gem-ы туда же.

              Пы. Сы. Спасибо за информацию о миграциях в рельсах.
                +2
                Нэнэнэ. Если у вас php, то не надо заморачиватся. А в дебиане наверно надо так: apt-get install ruby, а потом из консольки gem install rails. Гемы из пакетов ставить _не надо_
                  0
                  Ну, конечно спасибо за инфо. Но я так и делал. Я повторю — у меня unstable (о причинах долго объяснять).

                  + если я не ошибаюсь gem-ов в пакетах и нет :)

                  В любом случае — проблема с миграцией решена по другому. Tutorial на RoR я поковырял. Пока мне от RoR больше ничего не надо :).
          0
          Обновил информацию по поводу Doctrine и RoR.
            +1
            После поверхностного ознакомления не очень понял почему PHP >= 5.3

            Слишком поверхностно ознакомился?
              –3
              там используется __DIR__, константа, которая была введена в 5.3
              Для удаления тестовой базы используется register_shutdown_function, принимающая в параметр замкнутую функцию (closures)
                +3
                Ну если это все зависимости, то легко даунгрейдить до 5.2. Сделайте хотя бы опцией.

                А механизм миграций — это что-то общее для всех этих инструментов? Фактически хранятся диффы текущей версии с какой-то первой и накладываются при необходимости? При этом возможно указание логики для преобразования уже существующих данных?

                ЗЫ: Ruby
                  –3
                  Даунгредить до 5.2? Как вы себе представляете даунгредить замыкание? Мне важно, чтобы оно отрабатывало, даже если скрипт упадет — иначе мы получим кучу мусора в виде неудаленных бд с именами test234928349.

                    +2
                    Вы знаете, register_shutdown_function существует с 4-го PHP.
                      –2
                      Прекрасно знаю. Так же прекрасно знаю, что замыкания сущетвуют с 5.3.

                      Впрочем, если вы предложите стабильно-работающий патч — я готов включить его в код.

                        +5
                        __DIR__ === dirname(__FILE__)

                        register_shutdown_function(function() use($config,$tmpdb)
                        {
                        Helper::verbose(«database {$config['db']} droped»);
                        $tmpdb->query(«drop database `{$config['db']}`»);
                        })
                        ;

                        register_shutdown_function(create_function(
                        '$config, $tmpdb',
                        '
                        Helper::verbose(«database {$config[\'db\']} droped»);
                        $tmpdb->query(«drop database `{$config[\'db\']}`»);
                        '
                        ), $config, $tmpdb);
                          –1
                          эх… забыл я про create_function
                          Попробую.
                            0
                            function a() {

                            }

                            register_shutdown_function('a');
                              +1
                              Тогда придестся делать не красиво — использовать глобальные переменные
                                0
                                а деструкторы не помогут отцу русской демократии?
                                  0
                                  деструктор не дает гарантии выполнения, если скрипт вылетел каким-либо образом. register_shutdown_function такую гарантию дает
                                    –1
                                    When using CLI the shutdown function doesn't get called if the process gets a SIGINT or SIGTERM. only the natural exit of PHP calls the shutdown function
                    0
                    >>Фактически хранятся диффы текущей версии с какой-то первой и накладываются при необходимости?
                    Верно!
                    >>При этом возможно указание логики для преобразования уже существующих данных?
                    Для этого нужно внести изменения в файл миграции. migration[0-9]+\.php
                0
                Обновил статью, добавив информацию о механизме работы миграций.
                  +1
                  А вот это не прообовали? www.liquibase.org/ На жава, но не вижу с этим проблем. Устновка и настройка JVM будет явно быстрее чем самому писать.
                    +2
                    Инструментик хорош! Спасибо! Однако есть два но:
                    1. Java в зависимостях (знаю, что установка на любой системе == 5 мин.)
                    2. Нужно писать xml-код — не удовлетворяет первому требованию.
                      0
                      Сказать по чести, лично для себя я еще не нашел инструмента который бы подходил мне на 100%. Liquibase пока что приближается к идиалу, для реального мира Почему для реального? Может и ошибаюсь, но в большом приложение, пытаться автоматически отслеживать изменения БД — очень не благодарное дело. Триггеры, индексы, типы данных, чарсеты, движки БД — все это надо отслеживать.
                      Имхо самый реалистичный вариант — максимально облегчать себе жизнь в плане создания alter скриптов в полу ручном режиме.

                      К тому же у автоматизации есть еще один минус — одно дело менять структуру на базе в пару мегов. Другое — на 10 и ГБ и больше базе под нагрузкой — тут уже безопасней руками и очень, очень аккуратно :)

                      Но желаю вам удачи — больше инструментов разных и нужных.
                        0
                        Хм. Есть у вас правда. Спасибо за обоснованную точку зрения.
                    +1
                    Для Питона + Django стоит упомянуть south. По модели данных проекта генерирует миграционные скрипты для разных БД.
                      0
                      Спасибо.
                      +1
                      Не могли бы Вы сказать причину такого витиеватого подхода в коде функции автозагрузки классов «mmpAutoload»? PHP ведь сам пробегается по путям, перечисленным в include_path при поиске необходимого файла, а Вы это делаете вручную.

                      И еще, для тех, кто портирует код под PHP 5.2: в функции __autoload до версии 5.3 нельзя было бросать исключения ( php.net/manual/en/language.oop5.autoload.php ). Здесь же автор использует spl_autoload_register, по ней ничего в документации не нашел, но было бы неплохо проверить, работает ли.

                      По теме топика: также использую самописный простой php-скрипт накатов ALTER-скриптов в зависимости от версии БД, которая также хранится в отдельной таблице. Ну еще он умеет развернуть начальный дамп из скрипта. ALTER-скрипты получаются ручками или копированием из сред управления БД типа EMS MySQL Manager.

                      В принципе, такого полуавтоматического «деплоймента» пока хватает и подобный инструмент действительно уменьшает рутину.
                        0
                        Нда, судя по всему, если в автолоадере необходимо кидать исключение, то без ручного поиска по путям из include_path не обойтись, потому что file_exists принимает абсолютный путь к файлу.
                          0
                          ru.php.net/manual/en/function.spl-autoload-register.php — конечно работает
                          За исключение спасибо.
                            +1
                            Не, spl_autoload_register -то работает, я имел в виду можно ли в функции, зарегистрированной через spl_autoload_register бросать исключение в PHP младше 5.3. По этой теме в доке сказано только про __autoload, но ведь мы регистрируем свою кастомную функцию и на нее эти правила могут не распространяться.

                            Проверил. Не ловятся в PHP < 5.3 исключения, брошенные в функции автозагрузчике, будь то __autoload или кастомная — всегда Fatal Error.
                              0
                              Спасибо за тесты
                          0
                          Мигратор для zf code.google.com/p/zfcore/wiki/Core_Migration
                          Думаю можно юзать отдельно.
                            0
                            Спасибо. Посмотрю. Возможно по комментариям к этому материалу созрею написать обзор миграторов.
                            0
                            Не забудьте про dbdeploy.
                              0
                              Несколько баз одновременно поддерживало бы еще…
                              Да на разных серверах.

                              Оно конечно можно и пачку конфигов и пачку скриптов, но для ленивых…
                              :-)
                                +1
                                я — разработчик mygrate, спасибо за упоминание!

                                от себя скажу, что выбор PHP в качестве языка для таких задач, на мой взгляд, неудачен. хотя ваш проект написан гладко, но априори — для всех будущих разработок — вы много мучаетесь с тем, что в языках высокого уровня есть «из коробки» (т.е. — в стандартной библиотеке). командная строка на PHP — мучение (особенно попробуйте в windows). мое мнение — Python априори лучше для таких задач. тем не менее, спасибо за вашу разработку! уверен, она много кому пригодится ;-)
                                  +1
                                  ну если не кривить душой то с python в винде дела обстоят не намного лучше чем с php, универсальное решение наверное — это использование java приложение, но каждый для себя определяет наиболее для него удобный инструмент в данный момент времени и на данном проекте.
                                    0
                                    CLI Питона на винде работает замечательно.
                                      0
                                      хм, CLI php аналогично.
                                      В чем у вас были проблемы?
                                    0
                                    Согласен насчет выбора языка, но при всех этих минусах для PHP проектов этот выбор все равно останется лучшим
                                    +1
                                    Вы все делаете правильно. Отметаете готовые решения и скручиваете свой единственно правильный велосипед. Совсем как в басне про русского программиста.
                                    Однако, дела обстоят еще более прозаично.
                                    Создание проектов, подразумевающих более менее развитую инфраструктуру, на коленке, с постоянно меняющейся базой (да еще изменяемой произвольным количеством разработчиков) не должно быть по определению. Даже если не можете/не хотите/не успеваете спроектировать структуру базы сейчас, то со временем вы всеравно придете к более цивилизованным методам, а ее изменения не будут принимать глобальных масштабов, выходящих из-под контроля.
                                    Создание сайтов или сайтиков может вестись и на коленке, но кому понадобится велосипед, да еще и со списком того, чего он не умеет? Возникают неудобства между 2-3 разработчиками? Можно вести простой файл с изменениями структуры, отметками о выполнении каждым разработчиком и датой фиксации на проде.
                                      0
                                      Ну каким бы ни был проект, требования имеют свойство изменяться в течении срока разработки. Изменение требований может повлечь за собой как изменения в коде, так и изменения структуры данных. Сколько бы ни было разработчиков 2 или 555 базу под (svn|git|hg|cvs) не положить, а соответственно нужен инструмент, который бы делал передачу изменений с наименьшими человеческими трудозатратами. Существующие инструменты хороши, но у большинства один недостаток (для меня лично — недостаток) — при их использовании нельзя менять базу напрямую. Мне неудобно вносить по несколько раз изменения в файлы (yml,xml и т.д.) потом применять миграцию в тот момент, когда я эксперементирую со структурой, индексами и т.д.. Неудобно потому, что в этот момент меня интересует только база, и никуда, кроме консольного mysql я смотреть в такие моменты не хочу. Когда я поменял структуру 555 раз и решил, что такая меня устраивает — я могу успокоиться и наконец создать миграцию. Ни один инструмент не удовлетворял одновременно двум требованиям — быть написанным на PHP и генерить миграции по отличиям от старой структуры, потому и появился мой велосипед.

                                      Буду очень рад, если он облегчит кому-то жизнь.
                                        0
                                        > никуда, кроме консольного mysql я смотреть в такие моменты не хочу.
                                        Ну это все объясняет. На велосипед обычно садятся, когда не нравится ходит пешком. В любом случае, подтяните спицы, смажьте цепь, отрегулируйте седло. Пользователям должно быть удобно ездить. И разумеется, такие вещи как установка новых катафотов, брызговиков и наклеек тоже будет не лишней.
                                        Удачи вам.
                                      –1
                                      забавно наблюдать, как программисты изобретают себе проблему, а потом изобретают к ней решение.
                                        0
                                        В этом из заключается концепция интеллекта:
                                        Интеллект — есть способность поставить задачу и решить ее! :)
                                          0
                                          не спорю.
                                          но иногда грустно наблюдать, как компьютеры или не используются в гос. системах или только усложняют все.
                                          везде бюрократия беспросветная…
                                            0
                                            не понял как это относится к теме статьи
                                              0
                                              это относится к концепции интеллекта.

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

                                      Самое читаемое