使用phpMyAdmin的跟踪机制迁移数据库

时间:2012-03-06 18:23:17

标签: php mysql phpmyadmin database-migration

在开发数据库中,我在所有表上启用了phpMyAdmin Tracking。它记录了我对表结构所做的所有更改(在这种情况下,我对数据跟踪不感兴趣。)到目前为止一直都很好。

我想要做的是为所有跟踪表取出一个报告,使用特定版本(或者日期甚至可以工作)进行更改,以便我可以在生产数据库上运行生成的SQL ,升级到新版本时,确保数据库完全相同,不必担心手动处理这些错误。

但是,我找不到生成此类报告的功能。所有的跟踪报告都是针对单个表格的,如果我必须点击所有表格(20+),它就会带走这个功能的好处。所有表格都没有变化,但我不想跟踪改变的内容,这就是我想要phpMyAdmin为我做的事情。

我试图对存储更改的pma_tracking表进行自己的查询,并取得了部分成功。问题是一个版本的所有更改都存储为一个BLOB,每个新版本都有一个DROP TABLE / CREATE TABLE语句,我不能删除生产数据库上的表,因为那里有数据(我是每次都不重新创建数据库,只添加增量更改)。我只是想升级结构,我唯一想要CREATE TABLE语句的时候就是在数据库中实际创建一个新表。所以我认为我可以用SQL过滤掉那些,但随后它被存储为博客,然后我将不得不解析并弄乱看起来过于复杂的blob文本。

所以,总结一下,这就是我要找的东西:

  • 自动跟踪系统/工作流,记录所有结构更新,并可以从版本或时间点为整个数据库创建增量SQL报告。
  • 我更愿意不使用任何其他第三方应用(我只想使用phpMyAdmin或MySQL),如果可能的话

另外,如果有人有更好的想法,我会喜欢对工作流程的评论。任何帮助表示赞赏。

5 个答案:

答案 0 :(得分:3)

解析“pma_tracking”表的BLOB字段的算法位于libraries/Tracker.class.php源文件中getTrackedDataPMA_Tracker class方法。
从该代码开始,我编写了一个简单的PHP脚本来从“pma_tracking”表中提取所有数据定义语句(“DROP TABLE”语句除外)。
例如,假设您想要获取“test”数据库的所有表的所有更改列表,因为版本“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命令文件,其中包含(几乎)复制目标(例如生产)数据库上的模式更改所需的所有语句;必须通过指定“-f”(强制)MySQL选项来执行此文件:

  

-f, - force即使我们收到SQL错误也继续。

通过这样做,MySQL将忽略每次遇到现有表的CREATE TABLE语句时将抛出的所有“表已存在”错误,从而仅创建仍然不存在的表在目标数据库中 这种方法显然有一些缺点:

  1. ALL DROP TABLE命令将被忽略(不仅是那些从phpMyAdmin自动插入的命令),因此,如果您删除了源数据库中的表,则不会删除该表在目标数据库中。
  2. 所有脚本错误将被忽略,因此可能无法100%承受。
  3. 最后一条忠告:在继续之前始终对目标数据库进行完整备份!

答案 1 :(得分:1)

我不知道你如何使用phpMyAdmin解决这个问题,但是还有其他工具可以帮助你实现你想要的效果。 Liquibase就是其中之一。我过去曾经使用过一段时间,而且非常好。它需要一点点才能掌握它,但我认为它可能对你有帮助。

答案 2 :(得分:0)

我对SQL工具不太熟悉,所以我不建议任何帮助你的东西,但我可以尝试帮助自定义工作流程...

  1. 创建一个名为structure_log
  2. 的表
  3. 创建一个名为print_stucture.php的PHP脚本,它将您想要的任何信息打印到服务器上的文件中,将文件保存为时间戳(这将是您的版本号),并将名称保存在structure_log表中
  4. 创建一个运行print_structure.php的crontab,但通常需要
  5. 创建一个名为delete_dups.php的PHP脚本,它从struct_log表中获取最后两个记录,比较这两个文件,如果它们相同(表示没有对结构进行更改),则删除具有最新时间戳的文件(filename并从structure_log表中删除该记录
  6. 创建一个运行delete_dups.php的crontab,其运行速度是运行print_structure.php的一半。
  7. 这将在您的服务器上创建一个版本控制文件夹。您可以随时手动运行print_structure.php脚本,并将其与服务器文件夹中的最新版本日志进行比较,以查看您刚运行它的数据库是否与上次运行版本检查时相同。

答案 3 :(得分:0)

我没有任何在两个数据库之间创建增量差异的东西,但这是我用来比较两个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>";
            }
        }
    }
}
?>

编辑以添加显示约束差异的代码。

答案 4 :(得分:0)

我在MySQL Workbench

取得了一些成功

将您的开发人员数据库导入(逆向工程)到工作台中。您可以通过将架构导出到SQL文件并将其加载到工作台来完成此操作,或者工作台将直接从服务器获取架构。

接下来,使用“Synchronize model”选项生成diff文件。您选择生产数据库,然后选择要同步的表,并且工作台生成一个可以运行以同步两个模型的SQL文件。

提醒一句:第一次,当数据库更新为工作台“风格”时,可能会有相当多的显然未完成的更改。对于后续更新,该工具相当可靠,但我永远不会让自动化工具在我的生产数据库上有自由范围; - )

始终检查SQL文件是否有错误,在某些情况下,删除列然后添加另一个相同名称但不同类型将生成一个失败的alter列。