如何在同一台服务器上复制一个MySQL数据库

66
我有一个大型的MySQL数据库,叫做live_db,我想在同一台机器上复制它来提供一个测试系统来进行操作(test_db),包括表结构和数据。 我希望定期更新test_db内容与live_db相同;如果可能的话是增量更新。
MySQL中是否有一些内置的机制可以实现这个功能?我认为主从复制不是我想要的东西,因为应该可以更改test_db中的数据。虽然这些更改不必得到保留。

目前为止已经有12.9 GB,并且还在不断增长。 - Christoph Grimmer
如果您不需要担心auto_increment键,那么可以设置复制,但大部分时间保持从服务器处于停止状态。然后定期使用START SLAVE;命令,使其更新到最新状态。但是,如果您有任何auto_increment字段,则会导致键冲突。 - Michael Berkowski
6个回答

96

mysql 命令行客户端可以从标准输入接受一系列 SQL 语句。因此,您可以直接将 mysqldump 的输出通过管道传递到命令行上的 mysql 中。将其设置为定期运行的 cron 任务将会用更新后的实时数据覆盖测试数据:

mysql --user=username --password=passwd -e 'DROP DATABASE test_db;'
mysql --user=username --password=passwd -e 'CREATE DATABASE test_db;'
mysqldump --user=username --password=passwd live_db | mysql --user=username --password=passwd test_db
请注意,由于您的数据很大,所以需要花费很长时间。

1
这是我找到的最好的方法。我期待着听到更好的方法。 - Ray Baxter
2
正如我在上面的评论中所述,数据库当前为12.9 GB,且仍在增长。进行完整转储需要太多时间。 - Christoph Grimmer
4
你真的需要使用DROP|CREATE数据库吗:mysqldump命令将自动包含DROP TABLE,因此除非你在测试数据库中生成了表格(这些表格只有在手动删除后才会不存在),否则你不需要前两行代码。 - artfulrobot
5
我已经使用了mysqldbcopy工具:http://dev.mysql.com/doc/mysql-utilities/1.3/en/mysqldbcopy.html - Boby
9
请注意,如果您想将备份还原到不同的数据库,请勿在左侧使用参数“--database orig_db”。mysqldump在输出中会包含一个“use orig_db”语句,这样您就无法将数据写入到右侧指定的数据库中。我曾经吃过这个亏。:-/ - bradoaks
显示剩余13条评论

11

迈克尔上面的答案很好,但它不会复制事件、存储过程或触发器。

为了复制这些内容,需要在mysqldump中添加一些开关:--events --triggers --routines

为了补充已经制作的副本:

mysqldump --user=username --password=passwd --no-data --no-create-info --no-create-db --events --triggers --routines live_db | mysql --user=username --password=passwd test_db


5

我已经在不同的环境中做了几年这个,用于小到中型数据库(1G到100G)。对于较小的数据集,快速而简单的mysqldump可以胜任;它们越小,备份效果就越好。

但当你超过5-10 GB时,根据MySQL负载情况,这种快速而简单的方法就不再够用了。

为什么mysqldump可能不够用

MySQLdump的问题在于,在转储期间,实时数据库要么无法使用,要么非常难以使用,或者备份将不一致。除非您有足够长的时间窗口,使得实时数据库的不可用性是不重要的,因为无论如何数据库都不需要使用 (例如在深夜)。

默认选项(这里讨论了原因)会使数据库在转储时几乎无法使用,除非只是读取数据,且数据量很少。在繁忙的电子商务网站上,您将看到客户堆积导致崩溃。

因此,您应该使用InnoDB和现代选项(不是默认选项,据我所知)。

--single-transaction --skip-lock-tables

在转储期间,这些允许网站运行,尽管比正常情况下慢,根据使用情况不同,可能会明显变慢。

同时,将可能重要的其他数据一并转储:

--events --triggers --routines

(...噢,这仍然不会转储用户权限。作为测试使用可能并不那么重要)。

我发现了一个“建议”的绕过方法,它基本上禁用了事务完整性,允许数据库在转储时以全速运行。有点像从汽车中拆掉制动器来减轻重量并使其跑得更快,是的它会奏效,但它会有一些你可能无法立即注意到的副作用。您几乎肯定迟早会注意到它们 - 就像刹车一样,当您最需要它们时,它们将变得非常重要,而且情况并不好。

但是,对于测试数据库,它仍然可以工作。

Xtrabackup

如果您有一个“严肃”的数据库,为什么不拥有“严肃”的备份呢

从服务器复制

如果您有足够的空间 - 而现在,20 Gb 并不算太多 - 另一个可能性就是使用辅助数据库。

您可以在同一台服务器上安装MySQL Server的第二个副本,使用不同的端口,并使其成为从服务器 (服务器将受到性能影响,存储速度方面)。然后您将拥有两个相同的数据库 (活动主服务器,活动从服务器)。 第一次,您仍然需要运行完整转储才能让它们同步,带来所有涉及的问题。

当您需要克隆测试数据库时,请停止从服务器复制 - 活动从服务器现在将保持“冻结”状态 - 并将活动从服务器备份到测试数据库,使用MySQLbackup或仅复制数据文件。完成后,您重新启动复制。

对活动主服务器的影响可以忽略不计,而且从服务器实际上可以用于非更新关键查询。


0

如果您喜欢使用MySQL迁移工具包,您可以在数据映射步骤中双击模式名称并更改目标模式名称。


0

对于所有的Mac用户,使用Sequel Pro,你只需要进入数据库(菜单)-> 复制数据库。完成!


-1

这个解决方案很好用,但如果您正在使用PHPunit进行单元测试,则它不起作用。

在命令行中使用密码会生成警告,PHPUnit会捕获并生成异常(是的,这是相当大的问题...)

解决这个问题的方法是使用配置文件。

在我的情况下,我不想在配置文件和PHP代码中都维护密码和用户,因此我从代码中生成配置文件,并检查是否存在(否则,我将直接在命令行中使用用户名和密码作为备用选项)。

以下是一个示例,在PHP中如何复制设置数据库以创建具有不同名称的新数据库(例如,如果您正在管理不同客户的主域名和不同子域/数据库):

/**
* If the $dbName doesn't exist, then create it.
* 
* @param $errorMessage String to return the error message.
* @param $dbName String name of the database to create.
* @param $cleanExisting Boolean if the database exist, clear it to recreate it.
*
* @return boolean ($dbExists)
*/
private function createDatabase(&$errorMessage, $dbName, $clearExisting = false){

    $command = "";
    $configurationString = "[client]" . "\r\n" . "user=" . parent::$support_user . "\r\n" . "password=" . md5(parent::$support_pass);
    $dbExist = false;
    $path = realpath(dirname(__FILE__));

    $connectionString = " --defaults-extra-file=" . $path . "\mysql.cnf ";

    $dbName = strtolower($dbName);

    if ($this->isDestinationDbNameValid($errorMessage, $dbName)) {

        $dbExist = $this->isDestinationDbExist($errorMessage, $dbName);

        if (empty($errorMessage) and ($dbExist === false or $clearExisting === true)) {

            if (file_put_contents($path . '/mysql.cnf', $configurationString) === false) {

                $connectionString = " --user=" . parent::$support_user . " --password=" . md5(parent::$support_pass). " ";
            }

            if ($dbExist and $clearExisting) {

                $command = $path . '/../../../mysql/bin/mysql ' . $connectionString . ' --execute="DROP DATABASE ' . $dbName  .';" &';
            }

            $command .= '"' . $path . '/../../../mysql/bin/mysql" ' . $connectionString . ' --execute="CREATE DATABASE ' . $dbName . ';" &"' .
                        $path . '/../../../mysql/bin/mysqldump" ' . $connectionString . ' --events --triggers --routines setup | "' .
                        $path . '/../../../mysql/bin/mysql" ' . $connectionString . $dbName;

            exec($command);

            $dbExist = $this->isDestinationDbExist($errorMessage, $dbName);

            if (!$dbExist) {

                $errorMessage = parent::getErrorMessage("COPY_SETUP_DB_ERR", "An error occurred during the duplication process of the setup database.");
            }
        }
    }

    return $dbExist;
}

附加说明:

  1. 我不得不在我的SQL语句中使用双引号(")而不是单引号(')。

  2. 我必须使用“&”符号来分隔我的不同命令。

  3. 此示例不包括新数据库名称的验证(isDestinationDbNameValid()方法)。不需要提到您永远不应信任用户输入...

  4. 您还必须编写自定义方法来验证数据库副本是否按预期工作(isDestinationDbExist()方法)。 您至少应该验证数据库是否存在,设置中的表是否存在,并可选地验证存储的程序。

明智地使用力量,我的朋友们,

来自蒙特利尔的Jonathan Parent-Lévesque


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接