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


154

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

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

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


Answers:


302

如手册中“ 复制数据库”中所述,您可以将转储直接通过管道传递到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 <dbuser> -p)。


2
Kinda ...它跳过了很多磁盘IO,尽管您不必两次读取/写入数据
Greg

8
如果您的数据库大小为千兆字节,那么这可能不会给您带来多少好处。我认为OP即将到来是他们不想将副本外部化:可以完全在mysql内完成吗?
cletus

3
我想说的是,数据库越大,它给您带来的好处就越多...在MySQL afaik中无法做到这一点(除非手工完成,一次只提供一张表/视图)
Greg,2009年

41
我首先必须使用标准的mysql命令创建new_db:“ CREATE DATABASE new_db;” 然后使用以下命令:mysqldump -u root -p old_db | mysql -u root -p new_db
valentt 2014年

4
如果我必须输入密码以进行转储和导入,这对我来说是无效的:mysqldump -uroot -p database1 | mysql -uroot -p database2。提示我输入两个pws,但只能输入一个。提示如下:Enter password: Enter password: 。赋予第一个密码后,该过程将永远等待。
Torsten

66

使用MySQL实用程序

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

因此,要回答OP的问题:

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

1
这对我来说很好,mysqldump基于解决方案的失败。
saji89年

1
在我的情况下,我必须这样指定端口:--source = root:your_password @ localhost:3307(否则它会给我一个拒绝访问错误的信息)
pbz

4
需要sudo apt-get install mysql-utilities,但这很整洁。我可以省略密码并提示输入密码吗?
ADTC '16

2
@ADTC我不知道是否有内置的方式让mysqldbcopy您询问密码;至少我在文档中找不到类似的内容。不过,您可以自己构建此功能。在Bash中可能看起来像这样:mysqldbcopy --source=root:"$(read -sp 'Source password: ' && echo $REPLY)"@localhost --destination=root:"$(read -sp 'Destination password: ' && echo $REPLY)"@localhost sitedb1:sitedb2
Chriki

1
仅供参考:Chriki的命令似乎正常工作。我只需要添加--forcemysqldbcopy命令中,因为我已经创建了目标数据库。谢谢!
Niavlys

19
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

2
它为接受的答案增加了什么?是相似的,但是您添加了一些区别,添加了一些注释以便更好地理解
Yaroslav 2012年

这应该是可接受的答案,因为它将创建数据库,也对auth有利。当前接受的答案将告诉您访问被拒绝,那么表不存在。
拉米达巴因

14

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

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。
lwitzel '17

1
我们还如何复制在原始数据库中创建的函数,事件等?这看起来只复制表。
Dogan Askan

12

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

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

1
该问题明确指出,导出/导入方法是已知的。
lav 17年

3
这是最好的方法。也适用于大型数据库,而管道版本mysqldump -u <user> -p <pwd> db_name | mysql -u <user> -p <pwd> new_db_name对于大型数据库可能会出现问题。
亚历克斯(Alex)

10

您可以使用(以伪代码):

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


3
这可能导致引用完整性失败,因为尚未复制依赖表。也许它可以完成一项大交易。
OndrejGalbavý”

4

首先创建重复的数据库:

CREATE DATABASE duplicateddb;

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

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

2

您可以执行以下操作:

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

1

该语句是在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


1

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

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


1

Greg的回答所述mysqldump db_name | mysql new_db_name是在数据库之间传输数据的一种免费,安全且容易的方法。但是,它也确实很慢

如果您要备份数据,无法承受丢失数据(在此数据库或其他数据库中)或使用以外的表innodb,则应该使用mysqldump

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

我找不到一个好的选择,所以我自己构建了一个脚本。我花了很多时间使它第一次工作,老实说,现在对它进行更改使我有些害怕。Innodb数据库并非要像这样复制和粘贴。微小的变化会导致此方法失败。自完成代码以来,我没有遇到任何问题,但这并不意味着您不会。

经过测试的系统(但可能仍会失败):

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

它能做什么

  1. 获取sudo特权并验证您是否有足够的存储空间来克隆数据库
  2. 获取root mysql特权
  3. 创建一个以当前git分支命名的新数据库
  4. 克隆结构到新数据库
  5. 切换到innodb的恢复模式
  6. 删除新数据库中的默认数据
  7. 停止mysql
  8. 将数据克隆到新数据库
  9. 启动mysql
  10. 在新数据库中链接导入的数据
  11. 退出InnoDB的恢复模式
  12. 重启mysql
  13. 赋予mysql用户访问数据库的权限
  14. 清理临时文件

与之比较 mysqldump

在3gb数据库上,使用mysqldumpmysql在我的计算机上花费40-50分钟。使用这种方法,相同的过程仅需约8分钟。

我们如何使用它

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

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

我们试图mysqldump为不同的分支机构复制数据库,但是等待时间太长(40-50分钟),并且在此期间我们无法做任何其他事情。

此解决方案将数据库克隆时间缩短到原来的1/5(考虑咖啡和洗手间的休息时间,而不是漫长的午餐时间)。

常见任务及其时间

在数据库不兼容的情况下,在分支之间切换在单个数据库上花费50分钟以上,但是使用mysqldump或此代码在初始设置时间之后根本没有时间。这段代码恰好比快约5倍mysqldump

以下是一些常见的任务,以及每种方法大约需要花费多长时间:

创建具有数据库更改的功能分支并立即合并:

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

创建具有数据库更改的功能分支,切换至错误master修正,在功能分支上进行编辑,然后合并:

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

创建具有数据库更改master的功能分支,在其中的功能分支上进行编辑的同时切换至错误修正5次,然后合并:

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

代码

除非您已阅读并理解以上所有内容,否则请不要使用它。

#!/bin/bash
set -e

# This script taken from: https://stackoverflow.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_FORMATTED}d" $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_FORMATTED}d" $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

如果一切顺利,您应该会看到类似以下内容的信息:

示例数据库的脚本输出的屏幕快照


0

除了Greg的答案之外,如果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

0

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

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

-4

我认为没有办法做到这一点。当PHPMyAdmin执行此操作时,它将转储数据库,然后以新名称重新插入数据库。

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.