Clonando um banco de dados MySQL na mesma instância MySql


154

Eu gostaria de escrever um script que copie meu banco sitedb1de dados atual para sitedb2a mesma instância de banco de dados mysql. Eu sei que posso despejar o sitedb1 em um script sql:

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

e importe-o para sitedb2. Existe uma maneira mais fácil, sem despejar o primeiro banco de dados em um arquivo sql?


Possível duplicata do banco de dados Clone MySQL
bummi 16/11

Respostas:


302

Como o manual diz em Copiando Bancos de Dados, você pode canalizar o dump diretamente no cliente mysql:

mysqldump db_name | mysql new_db_name

Se você estiver usando o MyISAM, poderá copiar os arquivos, mas eu não o recomendaria. É um pouco desonesto.

Integrado a partir de várias outras boas respostas

Ambos os comandos mysqldumpe mysqlaceitam opções para definir detalhes da conexão (e muito mais), como:

mysqldump -u <user name> --password=<pwd> <original db> | mysql -u <user name> -p <new db>

Além disso, se o novo banco de dados ainda não existir, você deverá criá-lo previamente (por exemplo, com echo "create database new_db_name" | mysql -u <dbuser> -p).


2
Meio ... ele ignora um monte de disco IO embora como você não tem que ler / escrever os dados duas vezes
Greg

8
Se o seu banco de dados tiver gigabytes, isso provavelmente não lhe renderá muito. Eu acho que o OP está chegando, eles não querem externalizar a cópia: isso pode ser feito puramente no mysql?
cletus

3
Eu diria que o maior o DB mais ele ganha você ... Não há nenhuma maneira de fazer isso dentro de MySQL afaik (exceto à mão, uma mesa / fotografia de cada vez)
Greg

41
Primeiro tive que criar new_db usando o comando padrão do mysql: "CREATE DATABASE new_db;" e depois usei estes comandos: mysqldump -u root -p old_db | mysql -u root -p new_db
valentt

4
Isso não funciona para mim, se eu tenho que colocar a senha para o dumping e importação como este: mysqldump -uroot -p database1 | mysql -uroot -p database2. Eu sou solicitado para ambos os pws, mas só pode colocar em um. Os olhares rápidos como este: Enter password: Enter password: . Depois de dar a primeira senha, o processo espera para sempre.
Torsten

66

Usando utilitários MySQL

Os MySQL Utilities contêm a boa ferramenta mysqldbcopyque, por padrão, copia um banco de dados, incluindo todos os objetos relacionados ("tabelas, visualizações, gatilhos, eventos, procedimentos, funções e concessões no nível do banco de dados") e dados de um servidor de banco de dados para o mesmo ou para outro Servidor de banco de dados. Existem muitas opções disponíveis para personalizar o que é realmente copiado.

Então, para responder à pergunta do OP:

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

1
Isso funcionou bem para mim, a mysqldumpsolução baseada estava falhando.
saji89

1
No meu caso eu tive que especificar a porta como esta: --source = root: your_password @ localhost: 3307 (caso contrário ele iria me dar um erro acesso negado)
PBZ

4
Precisa sudo apt-get install mysql-utilities, mas isso é muito legal. Posso deixar de fora a senha e ser solicitado a digitá-la?
ADTC

2
@ADTC Não sei se existe uma maneira embutida de mysqldbcopysolicitar a senha; pelo menos não consegui encontrar nada parecido na documentação. Você mesmo pode criar essa funcionalidade. Em Bash que podem olhar um pouco como este:mysqldbcopy --source=root:"$(read -sp 'Source password: ' && echo $REPLY)"@localhost --destination=root:"$(read -sp 'Destination password: ' && echo $REPLY)"@localhost sitedb1:sitedb2
Chriki

1
FYI: Parece que o comando de Chriki funciona perfeitamente. Eu apenas tive que adicionar --forceao mysqldbcopycomando porque já havia criado o banco de dados de destino. Obrigado!
Niavlys 06/03

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
O que isso acrescenta à resposta aceita? É semelhante, mas você adicionar algumas diferenças, adicionar alguns comentários para uma melhor compreensão
Yaroslav

Essa deve ser a resposta aceita, pois criará o banco de dados, também bom para autenticação. a resposta aceita atual informará o acesso negado e a tabela não existe.
Rami Dabain

14

Você precisa executar o comando no terminal / prompt de comando.

mysqldump -u <user name> -p <pwd> <original db> | mysql -u <user name> <pwd> <new db>

por exemplo: mysqldump -u root test_db1 | mysql -u root test_db2

Isso copia test_db1 para test_db2 e concede o acesso ao 'root' @ 'localhost'


Eu gosto desta resposta, é nítida. No entanto, para mim, o mysql exigia -p antes da senha.
Lwitzel

1
Como também podemos copiar funções, eventos etc. criados no banco de dados original? Parece apenas cópias de tabelas.
Dogan Askan

12

A maneira mais fácil e fácil é inserir esses comandos no seu terminal e definir permissões para o usuário root. Funciona para mim..!

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

1
A pergunta afirmou explicitamente que o método de exportação / importação já é conhecido.
lav

3
Esta é a melhor maneira de fazê-lo. Também funciona com bancos de dados grandes, enquanto a versão canalizada mysqldump -u <user> -p <pwd> db_name | mysql -u <user> -p <pwd> new_db_namepode ser problemática com bancos de dados grandes.
1428 Alex

10

Você pode usar (em pseudocódigo):

FOREACH tbl IN db_a:
    CREATE TABLE db_b.tbl LIKE db_a.tbl;
    INSERT INTO db_b.tbl SELECT * FROM db_a.tbl;

O motivo pelo qual não estou usando a sintaxe CREATE TABLE ... SELECT ... é preservar os índices. Claro que isso apenas copia tabelas. As visualizações e os procedimentos não são copiados, embora possam ser feitos da mesma maneira.

Consulte CRIAR TABELA .


3
Isso pode falhar na integridade da referência, pois as tabelas dependentes ainda não podem ser copiadas. Talvez pudesse funcionar em uma grande transação.
Ondrej Galbavý

4

Primeiro, crie o banco de dados duplicado:

CREATE DATABASE duplicateddb;

Verifique se as permissões, etc., estão todas no lugar e:

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

2

Você pode fazer algo como o seguinte:

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

1

Esta declaração foi adicionada no MySQL 5.1.7, mas foi considerada perigosa e foi removida no MySQL 5.1.23. O objetivo era permitir que a atualização de bancos de dados anteriores à 5.1 usasse a codificação implementada na 5.1 para mapear nomes de banco de dados para nomes de diretório de banco de dados. No entanto, o uso dessa instrução pode resultar na perda do conteúdo do banco de dados, motivo pelo qual foi removida. Não use RENAME DATABASE em versões anteriores nas quais ele está presente.

Para executar a tarefa de atualizar nomes de banco de dados com a nova codificação, use ALTER DATABASE db_name ATUALIZAR NOME DO DIRETÓRIO DE DADOS: http://dev.mysql.com/doc/refman/5.1/en/alter-database.html


1

Uma maneira simples de fazer isso se você instalou o phpmyadmin :

Vá para o seu banco de dados, selecione a guia "operação" e você pode ver o bloco "copiar banco de dados para". Use-o e você pode copiar o banco de dados.


1

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

  1. Obtém sudoprivilégio e verifica se você tem espaço de armazenamento suficiente para clonar o banco de dados
  2. Obtém privilégios de root no mysql
  3. Cria um novo banco de dados com o nome da ramificação git atual
  4. Estrutura de clones para o novo banco de dados
  5. Muda para o modo de recuperação para innodb
  6. Exclui dados padrão no novo banco de dados
  7. Pára o mysql
  8. Clona dados no novo banco de dados
  9. Inicia o mysql
  10. Vincula dados importados no novo banco de dados
  11. Muda do modo de recuperação para o innodb
  12. Reinicia o mysql
  13. Dá acesso de usuário mysql ao banco de dados
  14. Limpa arquivos temporários

Como ele se compara com mysqldump

Em um banco de dados de 3 GB, usar mysqldumpe mysqllevaria 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 mysqldumpduplicar 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 mysqldumpou 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 masterpara 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 masterpara 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:

Captura de tela da saída do script, por exemplo, banco de dados


0

Além da resposta de Greg , esta é a maneira mais fácil e rápida se new_db_nameainda não existir:

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

Se você tiver gatilhos no banco de dados original, poderá evitar o erro "O gatilho já existe" canalizando uma substituição antes da importação:

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

-4

Eu não acho que exista um método para fazer isso. Quando o PHPMyAdmin faz isso, ele despeja o banco de dados e o insere novamente com o novo nome.

Ao utilizar nosso site, você reconhece que leu e compreendeu nossa Política de Cookies e nossa Política de Privacidade.
Licensed under cc by-sa 3.0 with attribution required.