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

ucl*_*att 118 mysql database clone copy mysqldump

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

mysqldump -u root -p sitedb1 >~/db_name.sql
Run Code Online (Sandbox Code Playgroud)

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

Gre*_*reg 248

正如手册在复制数据库中所说,您可以将转储直接传递到mysql客户端:

mysqldump db_name | mysql new_db_name
Run Code Online (Sandbox Code Playgroud)

如果你正在使用MyISAM,你可以复制文件,但我不推荐它.这有点狡猾.

从各种好的其他答案整合

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

mysqldump -u <user name> --password=<pwd> <original db> | mysql -u <user name> -p <new db>
Run Code Online (Sandbox Code Playgroud)

此外,如果新数据库尚未存在,则必须事先创建它(例如with echo "create database new_db_name" | mysql -u <dbuser> -p).

  • 我首先必须使用标准的mysql命令创建new_db:"CREATE DATABASE new_db;" 然后使用以下命令:mysqldump -u root -p old_db | mysql -u root -p new_db (36认同)
  • 如果您的数据库大小为千兆字节,这可能不会给您带来太多帮助.我认为OP正在进行的是他们不想将副本外部化:它可以纯粹在mysql中完成吗? (8认同)
  • 我会说数据库越大,你获得的就越多......在MySQL afaik中没有办法做到这一点(除了手工,一次一个表/视图) (3认同)
  • 如果我必须像这样输入转储和导入的密码,这对我不起作用:`mysqldump -uroot -p database1 | mysql -uroot -p database2`。提示我输入两个密码,但只能输入一个。提示如下:`输入密码:输入密码:`。赋予第一个密码后,该过程将永远等待。 (3认同)
  • 有点......它跳过了很多磁盘IO,因为你不必两次读/写数据 (2认同)

Chr*_*iki 60

使用MySQL实用程序

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

那么,回答OP的问题:

mysqldbcopy \
    --source=root:your_password@localhost \
    --destination=root:your_password@localhost \
    sitedb1:sitedb2
Run Code Online (Sandbox Code Playgroud)

  • 需要`sudo apt-get install mysql-utilities`,但这非常简洁.我可以省略密码并提示输入密码吗? (3认同)
  • @ADTC我不知道是否有内置的方法让`mysqldbcopy`要求你输入密码; 至少我在文档中找不到类似的东西.不过,您可以自己构建此功能.在Bash中可能看起来像这样:`mysqldbcopy --source = root:"$(读-sp'源密码:'&& echo $ REPLY)"@ localhost --destination = root:"$(read -sp'Destination密码:'&& echo $ REPLY)"@ localhost sitedb1:sitedb2` (2认同)
  • 仅供参考:看起来 Chriki 的命令运行得非常完美。我只需将“--force”添加到“mysqldbcopy”命令中,因为我已经创建了目标数据库。谢谢! (2认同)

小智 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
Run Code Online (Sandbox Code Playgroud)

  • 什么增加了接受的答案?类似,但你添加一些差异,添加一些注释,以便更好地理解 (2认同)
  • 这是我到目前为止找到的唯一解决方案. (2认同)

Sri*_*ram 12

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

mysqldump -u <user name> -p <pwd> <original db> | mysql -u <user name> <pwd> <new db>
Run Code Online (Sandbox Code Playgroud)

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

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

  • 我们如何还可以复制在原始数据库中创建的函数、事件等?这看起来只是复制表格。 (2认同)

fzy*_*cjy 11

如果您安装了一个简单的方法phpmyadmin

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


Emi*_*l H 9

你可以使用(伪代码):

FOREACH tbl IN db_a:
    CREATE TABLE db_b.tbl LIKE db_a.tbl;
    INSERT INTO db_b.tbl SELECT * FROM db_a.tbl;
Run Code Online (Sandbox Code Playgroud)

我没有使用CREATE TABLE ... SELECT ...语法的原因是保留索引.当然这只能复制表格.虽然可以以相同的方式完成视图和过程,但不会复制视图和过程.

请参见CREATE TABLE.

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

Dry*_*_09 9

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

:~$> mysqldump -u root -p db1 > dump.sql
:~$> mysqladmin -u root -p create db2
:~$> mysql -u root -p db2 < dump.sql
Run Code Online (Sandbox Code Playgroud)

  • 这是最好的方法.也适用于大型数据库,而管道版本`mysqldump -u <user> -p <pwd> db_name | 对于大型数据库,mysql -u <user> -p <pwd> new_db_name`可能会有问题. (3认同)
  • 该问题明确指出导出/导入方法是已知的。 (2认同)

小智 6

首先创建复制数据库:

CREATE DATABASE duplicateddb;
Run Code Online (Sandbox Code Playgroud)

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

mysqldump -u admin -p originaldb | mysql -u backup -p password duplicateddb;
Run Code Online (Sandbox Code Playgroud)


0b1*_*011 6

正如Greg 的回答中提到的,mysqldump db_name | mysql new_db_name这是在数据库之间传输数据的免费、安全且简单的方法。然而,它也确实很慢

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

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

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

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

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

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

它能做什么

  1. 获取sudo权限并验证您有足够的存储空间来克隆数据库
  2. 获取mysql root权限
  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(想象一下喝咖啡和上厕所的时间,而不是一顿漫长的午餐)。

常见任务及其时间

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

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

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

  • 单个数据库:~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://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
Run Code Online (Sandbox Code Playgroud)

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

示例数据库的脚本输出的屏幕截图