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

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

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

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

317370 次浏览

正如手册在复制数据库中所说,你可以将转储直接输送到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)。

你可以使用(在伪代码中):

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的原因是…选择……语法是保存索引。当然,这只是复制表。视图和过程不会被复制,尽管可以以相同的方式完成。

看到创建表

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

首先创建重复的数据库:

CREATE DATABASE duplicateddb;

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

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

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

使用MySQL实用工具

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

所以,为了回答OP的问题:

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

该命令需要在终端/命令提示符下执行。

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

除了格雷格的回答,如果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

你可以这样做:

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

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

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

如果你安装了phpmyadmin,有一个简单的方法:

进入你的数据库,选择“operation”;Tab,可以看到“copy database to”;块。使用它,您可以复制数据库。

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

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

正如在格雷格的回答中提到的,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(就像咖啡和厕所休息代替了漫长的午餐)。

常见任务和他们的时间

在一个数据库上,在不兼容的数据库更改的分支之间切换需要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://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

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

 script output截图for example database

使用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

使用MySQL Workbench可以使用数据库>迁移向导将数据库复制到相同或其他服务器实例。我相信它可以在服务器端工作,因此它应该是复制大型数据库的一个很好的解决方案。

你可以:

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是正确的方法,但这是一个快速和肮脏的解决方案,在许多情况下有用)