Conforme mencionado na resposta de Greg , mysqldump db_name | mysql new_db_name
é a maneira gratuita, segura e fácil de transferir dados entre bancos de dados. No entanto, também é muito lento .
Se você deseja fazer backup de dados, não pode perder dados (neste ou em outros bancos de dados) ou está usando tabelas diferentes innodb
, então você deve usá-lo mysqldump
.
Se você estiver procurando por algo para desenvolvimento, faça backup de todos os seus bancos de dados em outros lugares e se sinta confortável em limpar e reinstalar mysql
(possivelmente manualmente) quando tudo der errado, talvez eu tenha a solução para você.
Como não consegui encontrar uma boa alternativa, construí um script para fazer isso sozinho. Eu gastei muito tempo fazendo com que isso funcionasse pela primeira vez e sinceramente me aterroriza um pouco para fazer alterações agora. Os bancos de dados Innodb não foram copiados e colados dessa maneira. Pequenas mudanças fazem com que isso falhe de maneiras magníficas. Não tive problemas desde que finalizei o código, mas isso não significa que você não terá.
Sistemas testados (mas ainda podem falhar):
- Ubuntu 16.04, mysql padrão, innodb, arquivos separados por tabela
- Ubuntu 18.04, mysql padrão, innodb, arquivos separados por tabela
O que faz
- Obtém
sudo
privilégio e verifica se você tem espaço de armazenamento suficiente para clonar o banco de dados
- Obtém privilégios de root no mysql
- Cria um novo banco de dados com o nome da ramificação git atual
- Estrutura de clones para o novo banco de dados
- Muda para o modo de recuperação para innodb
- Exclui dados padrão no novo banco de dados
- Pára o mysql
- Clona dados no novo banco de dados
- Inicia o mysql
- Vincula dados importados no novo banco de dados
- Muda do modo de recuperação para o innodb
- Reinicia o mysql
- Dá acesso de usuário mysql ao banco de dados
- Limpa arquivos temporários
Como ele se compara com mysqldump
Em um banco de dados de 3 GB, usar mysqldump
e mysql
levaria de 40 a 50 minutos na minha máquina. Usando esse método, o mesmo processo levaria apenas 8 minutos.
Como usamos
Temos nossas alterações de SQL salvas junto com nosso código e o processo de atualização é automatizado tanto na produção quanto no desenvolvimento, com cada conjunto de alterações fazendo um backup do banco de dados para restaurar se houver erros. Um problema que encontramos foi quando estávamos trabalhando em um projeto de longo prazo com alterações no banco de dados e tivemos que trocar de ramificações no meio dele para corrigir um bug ou três.
No passado, usamos um único banco de dados para todas as filiais e precisávamos reconstruir o banco de dados sempre que passávamos para uma filial que não era compatível com as novas alterações no banco de dados. E quando voltamos, teríamos que executar as atualizações novamente.
Tentamos mysqldump
duplicar o banco de dados para diferentes ramificações, mas o tempo de espera foi muito longo (40 a 50 minutos) e, enquanto isso, não podíamos fazer mais nada.
Essa solução reduziu o tempo de clone do banco de dados para 1/5 do tempo (pense no café e no banheiro em vez de um almoço longo).
Tarefas comuns e seu tempo
A alternância entre ramificações com alterações incompatíveis no banco de dados leva mais de 50 minutos em um único banco de dados, mas não há tempo após o tempo de configuração inicial com mysqldump
ou com esse código. Esse código é aproximadamente 5 vezes mais rápido que mysqldump
.
Aqui estão algumas tarefas comuns e aproximadamente quanto tempo elas levariam com cada método:
Crie uma ramificação de recursos com alterações no banco de dados e mescle imediatamente:
- Banco de dados único: ~ 5 minutos
- Clone com
mysqldump
: 50-60 minutos
- Clone com este código: ~ 18 minutos
Crie uma ramificação de recurso com alterações no banco de dados, mude master
para uma correção de bug, faça uma edição na ramificação de recurso e mescle:
- Banco de dados único: ~ 60 minutos
- Clone com
mysqldump
: 50-60 minutos
- Clone com este código: ~ 18 minutos
Crie uma ramificação de recurso com alterações no banco de dados, alterne master
para uma correção de bug 5 vezes enquanto faz edições na ramificação de recurso entre elas e mescle:
- Banco de dados único: ~ 4 horas, 40 minutos
- Clone com
mysqldump
: 50-60 minutos
- Clone com este código: ~ 18 minutos
O código
Não use isso, a menos que você tenha lido e compreendido tudo acima.
#!/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
Se tudo correr bem, você verá algo como: