Миграция баз данных с использованием механизма отслеживания phpMyAdmin - PullRequest
7 голосов
/ 06 марта 2012

В базе данных разработки у меня включено отслеживание phpMyAdmin для всех таблиц.Он регистрирует все изменения, которые я делаю в структурах таблиц (в данном случае меня не интересует отслеживание данных.) Пока все хорошо.

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

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

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

Итак, вкратце, этовот что я ищу:

  • Автоматизированная система отслеживания / рабочий процесс, который регистрирует все обновления структуры и может создавать инкрементные отчеты SQL для всей базы данных из версии или момента времени.
  • Я бы предпочел не использовать какие-либо дополнительные сторонние приложения (я бы хотел использовать только phpMyAdmin или MySQL), если это возможно

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

Ответы [ 5 ]

3 голосов
/ 04 декабря 2012

Алгоритм разбора поля BLOB таблицы «pma_tracking» находится в методе getTrackedData класса PMA_Tracker , в исходном файле libraries/Tracker.class.php.
Исходя из этого кода, я написал простой PHP-скрипт для извлечения всех операторов определения данных (кроме операторов «DROP TABLE») из таблицы «pma_tracking».
Например, предположим, что вы хотите получить список всех изменений всех таблиц «тестовой» базы данных с версии «1»:

<?php

$link = mysqli_init();

// Adjust hostname, username, password and db name before use!
$db = mysqli_real_connect($link, "localhost", "myuser", "mypass", "phpmyadmin") 
      or die(mysqli_connect_error());

// Adjust also target db name and tracking version
$db_name = "test";
$version = "1";

$sql = "SELECT schema_sql FROM pma_tracking 
         WHERE db_name='{$db_name}' AND version>='{$version}' 
         ORDER BY version,date_created";
$result = mysqli_query($link, $sql) or die(mysqli_error($link));
while ($myrow = mysqli_fetch_assoc($result)) {
    $log_schema_entries = explode('# log ',  $myrow['schema_sql']);
    foreach ($log_schema_entries as $log_entry) {
        if (trim($log_entry) != '') {
            $statement = trim(strstr($log_entry, "\n"));
            if (substr($statement, 0, 11) != "DROP TABLE ") {
                echo "{$statement}\n";
            }
        }
    }
}

?>

Перенаправив вывод сценария на файл, вы получите файл команд SQL с (почти) всеми операторами, необходимыми для репликации изменений схемы в целевой (например, рабочей) базе данных; этот файл должен быть выполнен с помощью параметра MySQL "-f" (force):

-f, --force Продолжить, даже если мы получим ошибку SQL.

При этом MySQL будет игнорировать все ошибки «Таблица уже существует», которая будет выдаваться каждый раз, когда встречается оператор CREATE TABLE для существующей таблицы, создавая таким образом только таблицы, которые еще не существуют в целевая база данных.
Такой подход, очевидно, имеет некоторые недостатки:

  1. ALL команды DROP TABLE будут игнорироваться (не только те, которые автоматически вставляются из phpMyAdmin), поэтому, если вы удалили таблицу в исходной базе данных, эта таблица не будет удалена в целевом объекте. базы данных.
  2. ALL ошибки скрипта будут игнорироваться, поэтому он может быть недоступен на 100%.

Последнее слово: всегда делайте полную резервную копию вашей целевой базы данных, прежде чем продолжить!

1 голос
/ 01 декабря 2012

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

0 голосов
/ 04 декабря 2012

У меня был некоторый успех с MySQL Workbench :

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

Затем сгенерируйте свой файл сравнения с опцией «Синхронизировать модель». Вы выбираете производственную базу данных, затем таблицы для синхронизации, и верстак создает файл SQL, который можно запустить для синхронизации обеих моделей.

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

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

0 голосов
/ 04 декабря 2012

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

<?php
//------------------------------------------------------------------------------
// Define the variables we'll be using.
//------------------------------------------------------------------------------
$db1_con = NULL;
$db1_constraints = array();
$db1_dbname = 'db1';
$db1_host = 'localhost';
$db1_password = 'password1';
$db1_tables = array();
$db1_username = 'username1';

$db2_con = NULL;
$db2_constraints = array();
$db2_dbname = 'db2';
$db2_host = '123.123.123.123';
$db2_password = 'password2';
$db2_tables = array();
$db2_username = 'username2';

//------------------------------------------------------------------------------
// Connect to the databases.
//------------------------------------------------------------------------------
try{
    $db1_con = new PDO("mysql:host=$db1_host;dbname=information_schema", $db1_username, $db1_password);
    $db1_con->setAttribute(PDO::ATTR_EMULATE_PREPARES, FALSE); // Try to use the driver's native prepared statements.
    $db1_con->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION); // Let's use exceptions so we can try/catch errors.
}catch(PDOException $e){
    echo "<p>Connection failed for $db1_host: " . $e->getMessage() . '</p>';
    exit;
}

try{
    $db2_con = new PDO("mysql:host=$db2_host;dbname=information_schema", $db2_username, $db2_password);
    $db2_con->setAttribute(PDO::ATTR_EMULATE_PREPARES, FALSE); // Try to use the driver's native prepared statements.
    $db2_con->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION); // Let's use exceptions so we can try/catch errors.
}catch(PDOException $e){
    echo "<p>Connection failed for $db2_host: " . $e->getMessage() . '</p>';
    exit;
}

if (NULL !== $db1_con && NULL !== $db2_con){
    echo "<h2>Column Analysis</h2>";
    $sql = 'SELECT * FROM information_schema.COLUMNS WHERE TABLE_SCHEMA = ? ORDER BY TABLE_NAME, ORDINAL_POSITION';
    $statement1 = $db1_con->prepare($sql);
    $statement1->bindValue(1, $db1_dbname);

    $statement2 = $db2_con->prepare($sql);
    $statement2->bindValue(1, $db2_dbname);

    if (TRUE === $statement1->execute()){
        while ($row = $statement1->fetch(PDO::FETCH_ASSOC)){
            $db1_tables[$row['TABLE_NAME']][$row['COLUMN_NAME']] = array();
            foreach ($row AS $key => $value){
                $db1_tables[$row['TABLE_NAME']][$row['COLUMN_NAME']][$key] = $value;
            }
        }
    }

    if (TRUE === $statement2->execute()){
        while ($row = $statement2->fetch(PDO::FETCH_ASSOC)){
            $db2_tables[$row['TABLE_NAME']][$row['COLUMN_NAME']] = array();
            foreach ($row AS $key => $value){
                $db2_tables[$row['TABLE_NAME']][$row['COLUMN_NAME']][$key] = $value;
            }
        }
    }

    foreach ($db1_tables AS $table => $info){
        if (!isset($db2_tables[$table])){
            echo "<p>Table <strong>$table</strong> does not exist in the SECOND database!</p>";
        }else{
            foreach ($info AS $column => $data){
                if (!isset($db2_tables[$table][$column])){
                    echo "<p>Column <strong>$column</strong> does not exist in table <strong>$table</strong> in the SECOND database!</p>";
                }else{
                    if (count($data)){
                        foreach ($data AS $key => $value){
                            if ($db1_tables[$table][$column][$key] !== $db2_tables[$table][$column][$key]){
                                echo "<p>Column <strong>$column</strong> in table <strong>$table</strong> has differing characteristics for <strong>$key</strong> (". $db1_tables[$table][$column][$key] ." vs. ". $db2_tables[$table][$column][$key] .")</p>";
                            }
                        }
                    }
                }
            }
        }
    }

    foreach ($db2_tables AS $table => $info){
        if (!isset($db1_tables[$table])){
            echo "<p>Table <strong>$table</strong> does not exist in the FIRST database!</p>";
        }else{
            foreach ($info AS $column => $data){
                if (!isset($db1_tables[$table][$column])){
                    echo "<p>Column <strong>$column</strong> does not exist in table <strong>$table</strong> in the FIRST database!</p>";
                }else{
                    if (count($data)){
                        foreach ($data AS $key => $value){
                            if ($db2_tables[$table][$column][$key] !== $db1_tables[$table][$column][$key]){
                                echo "<p>Column <strong>$column</strong> in table <strong>$table</strong> has differing characteristics for <strong>$key</strong> (". $db2_tables[$table][$column][$key] ." vs. ". $db1_tables[$table][$column][$key] .")</p>";
                            }
                        }
                    }
                }
            }
        }
    }
    echo "<h2>Constraint Analysis</h2>";

    $sql = 'SELECT * FROM information_schema.KEY_COLUMN_USAGE WHERE TABLE_SCHEMA = ? ORDER BY TABLE_NAME, ORDINAL_POSITION';
    $statement1 = $db1_con->prepare($sql);
    $statement1->bindValue(1, $db1_dbname);

    $statement2 = $db2_con->prepare($sql);
    $statement2->bindValue(1, $db2_dbname);

    if (TRUE === $statement1->execute()){
        while ($row = $statement1->fetch(PDO::FETCH_ASSOC)){
            foreach ($row AS $key => $value){
                $db1_constraints[$row['TABLE_NAME']][$row['COLUMN_NAME']][$key] = $value;
            }
        }
    }

    if (TRUE === $statement2->execute()){
        while ($row = $statement2->fetch(PDO::FETCH_ASSOC)){
            foreach ($row AS $key => $value){
                $db2_constraints[$row['TABLE_NAME']][$row['COLUMN_NAME']][$key] = $value;
            }
        }
    }

    foreach ($db1_constraints AS $table => $info){
        foreach ($info AS $column => $data){
            if (isset($db2_constraints[$table][$column])){
                if (count($data)){
                    foreach ($data AS $key => $value){
                        if ('CONSTRAINT_NAME' !== $key && $db1_constraints[$table][$column][$key] !== $db2_constraints[$table][$column][$key]){
                            echo "<p>Column <strong>$column</strong> in table <strong>$table</strong> has differing characteristics for <strong>$key</strong> (". $db1_constraints[$table][$column][$key] ." vs. ". $db2_constraints[$table][$column][$key] .")</p>";
                        }
                    }
                }
            }else{
                echo "<p>Column <strong>$column</strong> in table <strong>$table</strong> is missing a constraint in the SECOND database!</p>";
            }
        }
    }

    foreach ($db2_constraints AS $table => $info){
        foreach ($info AS $column => $data){
            if (isset($db1_constraints[$table][$column])){
                if (count($data)){
                    foreach ($data AS $key => $value){
                        if ('CONSTRAINT_NAME' !== $key && $db2_constraints[$table][$column][$key] !== $db1_constraints[$table][$column][$key]){
                            echo "<p>Column <strong>$column</strong> in table <strong>$table</strong> has differing characteristics for <strong>$key</strong> (". $db2_constraints[$table][$column][$key] ." vs. ". $db1_constraints[$table][$column][$key] .")</p>";
                        }
                    }
                }
            }else{
                echo "<p>Column <strong>$column</strong> in table <strong>$table</strong> is missing a constraint in the FIRST database!</p>";
            }
        }
    }
}
?>

Отредактировано для добавления кода, который также показывает различия в ограничениях.

0 голосов
/ 28 ноября 2012

Я не слишком знаком с инструментами SQL, поэтому я не могу рекомендовать что-либо, чтобы помочь вам, но я могу попробовать и помочь с настраиваемым рабочим процессом ...

  1. Создать таблицу с именем struct_log
  2. Создайте PHP-скрипт с именем print_stucture.php, который печатает любую информацию, которую вы хотите, в файл на сервере, сохраняет файл как метку времени (это будет номер вашей версии) и сохраняет имя в таблице Structure_log
  3. Создайте crontab, который запускает print_structure.php, как часто вы хотите
  4. Создайте PHP-скрипт с именем delete_dups.php, который извлекает последние две записи из вашей таблицы struct_log, сравнивает эти два файла и, если они совпадают (не представляют изменений в структурах), удаляет тот, который имеет последнюю метку времени (имя файла ) и удаляет эту запись из таблицы struct_log
  5. Создайте crontab, который запускает delete_dups.php вдвое реже, чем тот, который запускает print_structure.php

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

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...