MySQL:在同一个 MySql 实例上克隆 MySQL 数据库

Posted

技术标签:

【中文标题】MySQL:在同一个 MySql 实例上克隆 MySQL 数据库【英文标题】:MySQL: Cloning a MySQL database on the same MySql instance 【发布时间】:2010-10-15 02:06:22 【问题描述】:

我想编写一个脚本,将我当前的数据库sitedb1 复制到同一mysql 数据库实例上的sitedb2。我知道我可以将 sitedb1 转储到 sql 脚本:

mysqldump -u root -p sitedb1 >~/db_name.sql

然后将其导入sitedb2。 有没有更简单的方法,不用将第一个数据库转储到 sql 文件中?

【问题讨论】:

Clone MySQL database的可能重复 【参考方案1】:

正如手册在Copying Databases 中所说,您可以将转储直接通过管道传输到 mysql 客户端:

mysqldump db_name | mysql new_db_name

如果您使用的是 MyISAM,您可以复制文件,但我不建议这样做。这有点狡猾。

综合了其他各种好的答案

mysqldumpmysql 命令都接受用于设置连接详细信息(以及更多)的选项,例如:

mysqldump -u <user name> --password=<pwd> <original db> | mysql -u <user name> -p <new db>

另外,如果新数据库还不存在,您必须事先创建它(例如使用echo "create database new_db_name" | mysql -u &lt;dbuser&gt; -p)。

【讨论】:

有点...它跳过了很多磁盘 IO,因为您不必两次读取/写入数据 如果您的数据库大小为千兆字节,这可能不会为您带来太多好处。我认为 OP 的意思是他们不想将副本外部化:可以纯粹在 mysql 中完成吗? 我会说数据库越大,它为你带来的收益就越大......在 MySQL afaik 中没有办法做到这一点(除了手动,一次一张表/视图) 我首先必须使用标准 mysql 命令创建 new_db:“CREATE DATABASE new_db;”然后使用这些命令:mysqldump -u root -p old_db | mysql -u root -p new_db 这对我不起作用,如果我必须输入这样的转储和导入密码:mysqldump -uroot -p database1 | mysql -uroot -p database2。提示我输入两个密码,但只能输入一个。提示如下所示:Enter password: Enter password: 。在给出第一个 pw 后,该过程将永远等待。【参考方案2】:

使用 MySQL 实用程序

MySQL Utilities 包含一个不错的工具mysqldbcopy,它默认复制一个数据库,包括所有相关对象(“表、视图、触发器、事件、过程、函数和数据库级授权”)和来自一个数据库服务器的数据到同一个或另一个数据库服务器。有很多选项可用于自定义实际复制的内容。

所以,回答 OP 的问题:

mysqldbcopy \
    --source=root:your_password@localhost \
    --destination=root:your_password@localhost \
    sitedb1:sitedb2

【讨论】:

这对我来说很好,基于mysqldump 的解决方案失败了。 在我的情况下,我必须像这样指定端口:--source=root:your_password@localhost:3307(否则它会给我一个拒绝访问错误) 需要sudo apt-get install mysql-utilities,不过这个很整洁。我可以省略密码并提示输入吗? @ADTC 不知道有没有内置的方法让mysqldbcopy问你密码;至少我在文档中找不到类似的东西。不过,您可以自己构建此功能。在 Bash 中可能看起来像这样:mysqldbcopy --source=root:"$(read -sp 'Source password: ' &amp;&amp; echo $REPLY)"@localhost --destination=root:"$(read -sp 'Destination password: ' &amp;&amp; echo $REPLY)"@localhost sitedb1:sitedb2 仅供参考:Chriki 的命令似乎完美无缺。我只需将--force 添加到mysqldbcopy 命令,因为我已经创建了目标数据库。谢谢!【参考方案3】:

最好和最简单的方法是在终端中输入这些命令并将权限设置为 root 用户。为我工作..!

:~$> mysqldump -u root -p db1 > dump.sql
:~$> mysqladmin -u root -p create db2
:~$> mysql -u root -p db2 < dump.sql

【讨论】:

问题明确指出导出/导入方法是已知的。 这是最好的方法。也适用于大型数据库,而管道版本mysqldump -u &lt;user&gt; -p &lt;pwd&gt; db_name | mysql -u &lt;user&gt; -p &lt;pwd&gt; new_db_name 可能会在大型数据库中出现问题。 这是最好的答案。只需确保根据您的要求授予新创建的数据库所需的用户权限即可。【参考方案4】:
mysqladmin create DB_name -u DB_user --password=DB_pass && \
        mysqldump -u DB_user --password=DB_pass DB_name | \
        mysql     -u DB_user --password=DB_pass -h DB_host DB_name

【讨论】:

它为接受的答案增加了什么?大同小异,不过你加了一些区别,加了一些cmet更好理解 这应该是公认的答案,因为它将创建数据库,也有利于身份验证。当前接受的答案会告诉您访问被拒绝,然后表不存在。【参考方案5】:

你可以使用(伪代码):

FOREACH tbl IN db_a:
    CREATE TABLE db_b.tbl LIKE db_a.tbl;
    INSERT INTO db_b.tbl SELECT * FROM db_a.tbl;

我不使用 CREATE TABLE ... SELECT ... 语法的原因是为了保留索引。当然,这只会复制表格。视图和过程不会被复制,尽管可以以相同的方式完成。

见CREATE TABLE。

【讨论】:

这可能会在引用完整性方面失败,因为尚未复制依赖表。也许它可以在一笔大交易中发挥作用。【参考方案6】:

您需要从终端/命令提示符运行命令。

mysqldump -u <user name> -p <pwd> <original db> | mysql -u <user name> <pwd> <new db>

例如:mysqldump -u root test_db1 | mysql -u root test_db2

这会将 test_db1 复制到 test_db2 并授予对 'root'@'localhost' 的访问权限

【讨论】:

我喜欢这个答案,它很清晰。但是,对我来说,mysql 在密码之前需要 -p。 我们如何也可以复制原始数据库中创建的函数、事件等?这看起来只是复制表格。【参考方案7】:

首先创建副本数据库:

CREATE DATABASE duplicateddb;

确保权限等都到位,并且:

mysqldump -u admin -p originaldb | mysql -u backup -p password duplicateddb;

【讨论】:

【参考方案8】:

如果您安装了phpmyadmin,这样做的简单方法:

转到您的数据库,选择“操作”选项卡,您可以看到“将数据库复制到”块。使用它,您可以复制数据库。

【讨论】:

【参考方案9】:

如Greg's answer 中所述,mysqldump db_name | mysql new_db_name 是在数据库之间传输数据的免费、安全和简单的方式。但是,它也真的很慢

如果您要备份数据、不能丢失数据(在此数据库或其他数据库中),或者正在使用innodb 以外的表,那么您应该使用mysqldump。强>

如果您正在寻找用于开发的东西,将您的所有数据库备份到其他地方,并且在一切都出现问题时可以轻松地清除并重新安装mysql(可能手动),那么我可能会为您提供解决方案。

我找不到好的替代方案,所以我自己编写了一个脚本。我花了 很多 时间让这个第一次工作,老实说,现在对它进行更改让我有点害怕。 Innodb 数据库不应该像这样复制和粘贴。微小的变化会导致它以惊人的方式失败。自从我完成代码以来,我没有遇到过问题,但这并不意味着你不会。

系统测试(但仍可能失败):

Ubuntu 16.04,默认 mysql,innodb,每个表单独的文件 Ubuntu 18.04,默认 mysql,innodb,每个表单独文件

我们已经切换到 docker 和整个 mysql 数据文件夹的简单副本,所以不再维护这个脚本。留下它以防将来它能够帮助任何人。

它的作用

    获取sudo 权限并验证您有足够的存储空间来克隆数据库 获取 root mysql 权限 创建一个以当前 git 分支命名的新数据库 将结构克隆到新数据库 切换到 innodb 的恢复模式 删除新数据库中的默认数据 停止 mysql 将数据克隆到新数据库 启动mysql 在新数据库中链接导入的数据 退出 innodb 的恢复模式 重启mysql 授予 mysql 用户访问数据库的权限 清理临时文件

mysqldump 相比如何

在 3gb 数据库上,在我的机器上使用 mysqldumpmysql 需要 40-50 分钟。使用这种方法,同样的过程只需要大约 8 分钟。

我们如何使用它

我们将 SQL 更改与代码一起保存,升级过程在生产和开发中都是自动化的,每组更改都会备份数据库,以便在出现错误时进行恢复。我们遇到的一个问题是,当我们处理一个需要更改数据库的长期项目时,必须在其中切换分支以修复一个或三个错误。

过去,我们为所有分支使用单个数据库,并且每当我们切换到与新数据库更改不兼容的分支时都必须重建数据库。当我们切换回来时,我们必须再次运行升级。

我们尝试mysqldump为不同的分支复制数据库,但是等待时间太长(40-50分钟),在此期间我们无能为力。

这个解决方案将数据库克隆时间缩短到了 1/5(想想喝咖啡和洗手间,而不是长时间的午餐)。

常见任务及其时间

在具有不兼容数据库更改的分支之间切换在单个数据库上需要 50 多分钟,但在使用 mysqldump 或此代码的初始设置时间之后根本没有时间。这段代码恰好比 mysqldump 快约 5 倍。

以下是一些常见任务以及每种方法大概需要多长时间:

使用数据库更改创建功能分支并立即合并:

单个数据库:~5 分钟 使用mysqldump 克隆:50-60 分钟 使用此代码克隆:~18 分钟

使用数据库更改创建功能分支,切换到main 进行错误修复,对功能分支进行编辑,然后合并:

单个数据库:~60 分钟 使用mysqldump 克隆:50-60 分钟 使用此代码克隆:~18 分钟

使用数据库更改创建功能分支,切换到 main 进行错误修复 5 次,同时在其间对功能分支进行编辑,然后合并:

单个数据库:~4 小时 40 分钟 使用mysqldump 克隆:50-60 分钟 使用此代码克隆:~18 分钟

代码

除非您已阅读并理解上述所有内容,否则请勿使用此功能。它不再维护,因此随着时间的推移它越来越有可能被破坏。

#!/bin/bash
set -e

# This script taken from: https://***.com/a/57528198/526741

function now 
    date "+%H:%M:%S";


# Leading space sets messages off from step progress.
echosuccess () 
    printf "\e[0;32m %s: %s\e[0m\n" "$(now)" "$1"
    sleep .1

echowarn () 
    printf "\e[0;33m %s: %s\e[0m\n" "$(now)" "$1"
    sleep .1

echoerror () 
    printf "\e[0;31m %s: %s\e[0m\n" "$(now)" "$1"
    sleep .1

echonotice () 
    printf "\e[0;94m %s: %s\e[0m\n" "$(now)" "$1"
    sleep .1

echoinstructions () 
    printf "\e[0;104m %s: %s\e[0m\n" "$(now)" "$1"
    sleep .1

echostep () 
    printf "\e[0;90mStep %s of 13:\e[0m\n" "$1"
    sleep .1


MYSQL_CNF_PATH='/etc/mysql/mysql.conf.d/recovery.cnf'
OLD_DB='YOUR_DATABASE_NAME'
USER='YOUR_MYSQL_USER'

# You can change NEW_DB to whatever you like
# Right now, it will append the current git branch name to the existing database name
BRANCH=`git rev-parse --abbrev-ref HEAD`
NEW_DB="$OLD_DB__$BRANCH"

THIS_DIR=./site/upgrades
DB_CREATED=false

tmp_file () 
    printf "$THIS_DIR/$NEW_DB.%s" "$1"

sql_on_new_db () 
    mysql $NEW_DB --unbuffered --skip-column-names -u root -p$PASS 2>> $(tmp_file 'errors.log')


general_cleanup () 
    echoinstructions 'Leave this running while things are cleaned up...'

    if [ -f $(tmp_file 'errors.log') ]; then
        echowarn 'Additional warnings and errors:'
        cat $(tmp_file 'errors.log')
    fi

    for f in $THIS_DIR/$NEW_DB.*; do
        echonotice 'Deleting temporary files created for transfer...'
        rm -f $THIS_DIR/$NEW_DB.*
        break
    done

    echonotice 'Done!'
    echoinstructions "You can close this now :)"


error_cleanup () 
    exitcode=$?

    # Just in case script was exited while in a prompt
    echo

    if [ "$exitcode" == "0" ]; then
        echoerror "Script exited prematurely, but exit code was '0'."
    fi

    echoerror "The following command on line $BASH_LINENO[0] exited with code $exitcode:"
    echo "             $BASH_COMMAND"

    if [ "$DB_CREATED" = true ]; then
        echo
        echonotice "Dropping database \`$NEW_DB\` if created..."
        echo "DROP DATABASE \`$NEW_DB\`;" | sql_on_new_db || echoerror "Could not drop database \`$NEW_DB\` (see warnings)"
    fi

    general_cleanup

    exit $exitcode


trap error_cleanup EXIT

mysql_path () 
    printf "/var/lib/mysql/"

old_db_path () 
    printf "%s%s/" "$(mysql_path)" "$OLD_DB"

new_db_path () 
    printf "%s%s/" "$(mysql_path)" "$NEW_DB"

get_tables () 
    (sudo find /var/lib/mysql/$OLD_DB -name "*.frm" -printf "%f\n") | cut -d'.' -f1 | sort


STEP=0


authenticate () 
    printf "\e[0;104m"
    sudo ls &> /dev/null
    printf "\e[0m"
    echonotice 'Authenticated.'

echostep $((++STEP))
authenticate

TABLE_COUNT=`get_tables | wc -l`
SPACE_AVAIL=`df -k --output=avail $(mysql_path) | tail -n1`
SPACE_NEEDED=(`sudo du -s $(old_db_path)`)
SPACE_ERR=`echo "$SPACE_AVAIL-$SPACE_NEEDED" | bc`
SPACE_WARN=`echo "$SPACE_AVAIL-$SPACE_NEEDED*3" | bc`
if [ $SPACE_ERR -lt 0 ]; then
    echoerror 'There is not enough space to branch the database.'
    echoerror 'Please free up some space and run this command again.'
    SPACE_AVAIL_FORMATTED=`printf "%'d" $SPACE_AVAIL`
    SPACE_NEEDED_FORMATTED=`printf "%'$#SPACE_AVAIL_FORMATTEDd" $SPACE_NEEDED`
    echonotice "$SPACE_NEEDED_FORMATTED bytes needed to create database branch"
    echonotice "$SPACE_AVAIL_FORMATTED bytes currently free"
    exit 1
elif [ $SPACE_WARN -lt 0 ]; then
    echowarn 'This action will use more than 1/3 of your available space.'
    SPACE_AVAIL_FORMATTED=`printf "%'d" $SPACE_AVAIL`
    SPACE_NEEDED_FORMATTED=`printf "%'$#SPACE_AVAIL_FORMATTEDd" $SPACE_NEEDED`
    echonotice "$SPACE_NEEDED_FORMATTED bytes needed to create database branch"
    echonotice "$SPACE_AVAIL_FORMATTED bytes currently free"
    printf "\e[0;104m"
    read -p " $(now): Do you still want to branch the database? [y/n] " -n 1 -r CONFIRM
    printf "\e[0m"
    echo
    if [[ ! $CONFIRM =~ ^[Yy]$ ]]; then
        echonotice 'Database was NOT branched'
        exit 1
    fi
fi

PASS='badpass'
connect_to_db () 
    printf "\e[0;104m %s: MySQL root password: \e[0m" "$(now)"
    read -s PASS
    PASS=$PASS:-badpass
    echo
    echonotice "Connecting to MySQL..."

create_db () 
    echonotice 'Creating empty database...'
    echo "CREATE DATABASE \`$NEW_DB\` CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci" | mysql -u root -p$PASS 2>> $(tmp_file 'errors.log')
    DB_CREATED=true

build_tables () 
    echonotice 'Retrieving and building database structure...'
    mysqldump $OLD_DB --skip-comments -d -u root -p$PASS 2>> $(tmp_file 'errors.log') | pv --width 80  --name " $(now)" > $(tmp_file 'dump.sql')
    pv --width 80  --name " $(now)" $(tmp_file 'dump.sql') | sql_on_new_db

set_debug_1 () 
    echonotice 'Switching into recovery mode for innodb...'
    printf '[mysqld]\ninnodb_file_per_table = 1\ninnodb_force_recovery = 1\n' | sudo tee $MYSQL_CNF_PATH > /dev/null

set_debug_0 () 
    echonotice 'Switching out of recovery mode for innodb...'
    sudo rm -f $MYSQL_CNF_PATH

discard_tablespace () 
    echonotice 'Unlinking default data...'
    (
        echo "USE \`$NEW_DB\`;"
        echo "SET foreign_key_checks = 0;"
        get_tables | while read -r line;
            do echo "ALTER TABLE \`$line\` DISCARD TABLESPACE; SELECT 'Table \`$line\` imported.';";
        done
        echo "SET foreign_key_checks = 1;"
    ) > $(tmp_file 'discard_tablespace.sql')
    cat $(tmp_file 'discard_tablespace.sql') | sql_on_new_db | pv --width 80 --line-mode --size $TABLE_COUNT --name " $(now)" > /dev/null

import_tablespace () 
    echonotice 'Linking imported data...'
    (
        echo "USE \`$NEW_DB\`;"
        echo "SET foreign_key_checks = 0;"
        get_tables | while read -r line;
            do echo "ALTER TABLE \`$line\` IMPORT TABLESPACE; SELECT 'Table \`$line\` imported.';";
        done
        echo "SET foreign_key_checks = 1;"
    ) > $(tmp_file 'import_tablespace.sql')
    cat $(tmp_file 'import_tablespace.sql') | sql_on_new_db | pv --width 80 --line-mode --size $TABLE_COUNT --name " $(now)" > /dev/null

stop_mysql () 
    echonotice 'Stopping MySQL...'
    sudo /etc/init.d/mysql stop >> $(tmp_file 'log')

start_mysql () 
    echonotice 'Starting MySQL...'
    sudo /etc/init.d/mysql start >> $(tmp_file 'log')

restart_mysql () 
    echonotice 'Restarting MySQL...'
    sudo /etc/init.d/mysql restart >> $(tmp_file 'log')

copy_data () 
    echonotice 'Copying data...'
    sudo rm -f $(new_db_path)*.ibd
    sudo rsync -ah --info=progress2 $(old_db_path) --include '*.ibd' --exclude '*' $(new_db_path)

give_access () 
    echonotice "Giving MySQL user \`$USER\` access to database \`$NEW_DB\`"
    echo "GRANT ALL PRIVILEGES ON \`$NEW_DB\`.* to $USER@localhost" | sql_on_new_db


echostep $((++STEP))
connect_to_db

EXISTING_TABLE=`echo "SELECT SCHEMA_NAME FROM INFORMATION_SCHEMA.SCHEMATA WHERE SCHEMA_NAME = '$NEW_DB'" | mysql --skip-column-names -u root -p$PASS 2>> $(tmp_file 'errors.log')`
if [ "$EXISTING_TABLE" == "$NEW_DB" ]
    then
        echoerror "Database \`$NEW_DB\` already exists"
        exit 1
fi

echoinstructions "The hamsters are working. Check back in 5-10 minutes."
sleep 5

echostep $((++STEP))
create_db
echostep $((++STEP))
build_tables
echostep $((++STEP))
set_debug_1
echostep $((++STEP))
discard_tablespace
echostep $((++STEP))
stop_mysql
echostep $((++STEP))
copy_data
echostep $((++STEP))
start_mysql
echostep $((++STEP))
import_tablespace
echostep $((++STEP))
set_debug_0
echostep $((++STEP))
restart_mysql
echostep $((++STEP))
give_access

echo
echosuccess "Database \`$NEW_DB\` is ready to use."
echo

trap general_cleanup EXIT

如果一切顺利,您应该会看到如下内容:

【讨论】:

【参考方案10】:

您可以执行以下操作:

mysqldump -u[username] -p[password] database_name_for_clone 
 | mysql -u[username] -p[password] new_database_name

【讨论】:

【参考方案11】:

此语句在 MySQL 5.1.7 中添加,但被发现有危险并在 MySQL 5.1.23 中删除。它旨在支持升级 5.1 之前的数据库以使用 5.1 中实现的编码将数据库名称映射到数据库目录名称。但是,使用此语句可能会导致数据库内容丢失,这就是它被删除的原因。不要在它存在的早期版本中使用 RENAME DATABASE。

要使用新编码执行升级数据库名称的任务,请改用 ALTER DATABASE db_name UPGRADE DATA DIRECTORY NAME:http://dev.mysql.com/doc/refman/5.1/en/alter-database.html

【讨论】:

【参考方案12】:

使用 Mydumper

sudo apt install mydumper

生成转储

mydumper --user=YOUR_USER --password=YOUR_PASSWORD -B YOUR_CURRENT_DB \ 
         --triggers --routines --events --outputdir YOUR_OUTPUT_DIR

负载转储

myloader --user=YOUR_USER --password=YOUR_PASSWORD --database=YOUR_NEW_DB \ 
         --directory=YOUR_OUTPUT_DIR

【讨论】:

【参考方案13】:

除了Greg's answer,如果new_db_name 尚不存在,这是最简单、最快的方法:

echo "create database new_db_name" | mysql -u <user> -p <pwd> 
mysqldump -u <user> -p <pwd> db_name | mysql -u <user> -p <pwd> new_db_name

【讨论】:

【参考方案14】:

如果您的原始数据库中有触发器,则可以通过在导入前通过管道替换来避免“触发器已存在”错误:

mysqldump -u olddbuser -p -d olddbname | sed "s/`olddbname`./`newdbname`./" | mysql -u newdbuser -p -D newdbname

【讨论】:

【参考方案15】:

使用 MySQL Workbench,您可以使用 Database > Migration Wizard 将数据库复制到相同或其他服务器实例。我相信它适用于服务器端,因此它应该是复制大型数据库的好解决方案。

【讨论】:

【参考方案16】:

你可以这样做:

CREATE DATABASE copy_of_db;
create table copy_of_db.table LIKE source_db.table;

If you want to copy data too:
INSERT INTO copy_of_db.table SELECT * FROM source_db.table;

对所有表、函数、过程等重复

(mysqldump 是正确的方法,但这是一种在许多情况下有用的快速而肮脏的解决方案)

【讨论】:

以上是关于MySQL:在同一个 MySql 实例上克隆 MySQL 数据库的主要内容,如果未能解决你的问题,请参考以下文章

如何在多实例基础上再添加一个mysql的实例

Mysql-my-innodb-heavy-4G.cnf配置文件注解

Mysql多实例安装

MySQL单机多实例部署详解

Mysql多实例 安装以及配置

在端口 3307 Windows 上运行 MySQL