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

问题描述 投票:113回答:13

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

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

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

mysql database copy clone mysqldump
13个回答
234
投票

正如手册在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 <dbuser> -p)。


1
投票

如果您安装了phpmyadmin,这是一种简单的方法:

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


0
投票

除了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

0
投票

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

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

0
投票

正如Greg's answer中所提到的,mysqldump db_name | mysql new_db_name是在数据库之间传输数据的免费,安全且简便的方法。但是,它也很慢。

如果你想备份数据,不能丢失数据(在这个或其他数据库中),或者使用innodb以外的表,那么你应该使用mysqldump

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

我找不到一个好的选择,所以我自己编写了一个脚本。我花了很多时间让它第一次工作,它真的让我感到害怕,现在对它进行一些修改。 Innodb数据库并不意味着像这样复制和粘贴。微小的变化导致这种方式失败。自从我最终确定代码以来,我没有遇到任何问题,但这并不意味着你不会。

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

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

What it does

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

How it compares with mysqldump

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

How we use it

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

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

我们尝试使用mysqldump来复制不同分支的数据库,但等待时间太长(40-50分钟),同时我们也无法做任何其他事情。

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

常见任务和时间

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

以下是一些常见任务以及每种方法的大致时间:

Create feature branch with database changes and merge immediately:

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

Create feature branch with database changes, switch to master for a bugfix, make an edit on the feature branch, and merge:

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

Create feature branch with database changes, switch to master for a bugfix 5 times while making edits on the feature branch inbetween, and merge:

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

The code

除非您已阅读并理解上述所有内容,否则请勿使用此功能。

#!/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

如果一切顺利,你应该看到类似的东西:

Screenshot of script output for example database


-2
投票

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


59
投票

使用MySQL实用程序

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

那么,回答OP的问题:

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

18
投票
$ 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

12
投票

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

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”的访问权限


9
投票

最简单的方法是在终端中输入这些命令,并为root用户设置权限。对我有用..!

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

8
投票

你可以使用(伪代码):

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


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

© www.soinside.com 2019 - 2024. All rights reserved.