Clonazione di un database MySQL sulla stessa istanza MySql


154

Vorrei scrivere uno script che copia il mio database corrente sitedb1a sitedb2nella stessa istanza del database mysql. So che posso scaricare il sitedb1 in uno script sql:

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

e quindi importarlo in sitedb2. C'è un modo più semplice, senza scaricare il primo database in un file sql?


Possibile duplicato del database Clone MySQL
bummi,

Risposte:


302

Come dice il manuale in Copia dei database è possibile reindirizzare il dump direttamente nel client mysql:

mysqldump db_name | mysql new_db_name

Se stai usando MyISAM si potrebbe copiare i file, ma io non lo consiglio. È un po 'complicato.

Integrato da varie altre buone risposte

Sia mysqldumpe mysqlcomandi accettano opzioni per l'impostazione dettagli di connessione (e molto altro), come:

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

Inoltre, se il nuovo database non esiste ancora, è necessario crearlo in anticipo (ad es. Con echo "create database new_db_name" | mysql -u <dbuser> -p).


2
Kinda ... salta un sacco di IO del disco, dato che non devi leggere / scrivere i dati due volte
Greg

8
Se il tuo database ha dimensioni di gigabyte, probabilmente non ti guadagnerà molto. Penso che quando l'OP sta arrivando non vogliono esternalizzare la copia: può essere fatto puramente all'interno di mysql?
cletus,

3
Direi che più grande è il DB più ti guadagna ... Non c'è modo di farlo all'interno di MySQL afaik (tranne che a mano, un tavolo / vista alla volta)
Greg

41
Per prima cosa ho dovuto creare new_db usando il comando mysql standard: "CREATE DATABASE new_db;" e poi ha usato questi comandi: mysqldump -u root -p old_db | mysql -u root -p new_db
valentt

4
Questo non funziona per me, se devo mettere la password per lo scarico e l'importazione in questo modo: mysqldump -uroot -p database1 | mysql -uroot -p database2. Mi viene richiesto per entrambi i pws ma posso inserirne solo uno. Gli sguardi di prompt come questo: Enter password: Enter password: . Dopo aver dato il primo pw, il processo attende per sempre.
Torsten,

66

Utilizzo delle utility MySQL

Le Utilità MySQL contengono il simpatico strumento mysqldbcopyche per impostazione predefinita copia un DB includendo tutti gli oggetti correlati ("tabelle, viste, trigger, eventi, procedure, funzioni e concessioni a livello di database") e dati da un server DB allo stesso o all'altro Server DB. Ci sono molte opzioni disponibili per personalizzare ciò che viene effettivamente copiato.

Quindi, per rispondere alla domanda del PO:

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

1
Questo ha funzionato bene per me, la mysqldumpsoluzione basata stava fallendo.
saji89,

1
Nel mio caso ho dovuto specificare la porta in questo modo: --source = root: your_password @ localhost: 3307 (altrimenti mi darebbe un errore di accesso negato)
pbz

4
È necessario sudo apt-get install mysql-utilities, ma questo è molto pulito. Posso lasciare la password e chiedermi di inserirla?
ADTC

2
@ADTC Non so se esiste un modo integrato per mysqldbcopyfarti chiedere la password; almeno non ho trovato nulla del genere nella documentazione. Tuttavia, potresti creare questa funzionalità da solo. A Bash potrebbe essere un po 'così:mysqldbcopy --source=root:"$(read -sp 'Source password: ' && echo $REPLY)"@localhost --destination=root:"$(read -sp 'Destination password: ' && echo $REPLY)"@localhost sitedb1:sitedb2
Chriki,

1
FYI: Sembra che il comando di Chriki funzioni perfettamente. Ho dovuto solo aggiungere --forceal mysqldbcopycomando perché avevo già creato il database di destinazione. Grazie!
Niavlys,

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
Cosa aggiunge alla risposta accettata? È simile, ma aggiungi alcune differenze, aggiungi alcuni commenti per una migliore comprensione
Yaroslav,

Questa dovrebbe essere la risposta accettata, in quanto creerà il database, utile anche per auth. la risposta attualmente accettata ti dirà l'accesso negato, quindi la tabella non esiste.
Rami Dabain,

14

È necessario eseguire il comando dal terminale / prompt dei comandi.

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

per esempio: mysqldump -u root test_db1 | mysql -u root test_db2

Questo copia test_db1 in test_db2 e concede l'accesso a 'root' @ 'localhost'


Mi piace questa risposta, è nitida. Tuttavia, per me mysql ha richiesto -p prima della password.
lwitzel,

1
Come possiamo anche copiare funzioni, eventi ecc. Creati nel database originale? Questo sembra solo copiare le tabelle.
Dogan Askan,

12

Il modo migliore e semplice è inserire questi comandi nel terminale e impostare le autorizzazioni per l'utente root. Per me va bene..!

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

1
La domanda affermava esplicitamente che il metodo di esportazione / importazione è già noto.
lav

3
Questo è il modo migliore per farlo. Funziona anche con database di grandi dimensioni, mentre la versione convogliata mysqldump -u <user> -p <pwd> db_name | mysql -u <user> -p <pwd> new_db_namepuò essere problematica con database di grandi dimensioni.
Alex

10

È possibile utilizzare (in pseudocodice):

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

Il motivo per cui non sto usando la sintassi CREATE TABLE ... SELECT ... è di preservare gli indici. Naturalmente questo copia solo le tabelle. Le viste e le procedure non vengono copiate, sebbene possano essere eseguite allo stesso modo.

Vedi CREATE TABLE .


3
Ciò potrebbe non riuscire sull'integrità di riferimento poiché non è stato ancora possibile copiare tabelle dipendenti. Forse potrebbe funzionare in un'unica grande transazione.
Ondrej Galbavý,

4

Innanzitutto creare il database duplicato:

CREATE DATABASE duplicateddb;

Assicurarsi che le autorizzazioni, ecc. Siano tutte in atto e:

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

2

Puoi fare qualcosa di simile al seguente:

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

1

Questa affermazione è stata aggiunta in MySQL 5.1.7 ma è risultata pericolosa ed è stata rimossa in MySQL 5.1.23. È stato progettato per consentire l'aggiornamento dei database pre-5.1 per utilizzare la codifica implementata in 5.1 per mappare i nomi dei database ai nomi delle directory del database. Tuttavia, l'uso di questa istruzione potrebbe comportare la perdita del contenuto del database, motivo per cui è stata rimossa. Non utilizzare RENAME DATABASE nelle versioni precedenti in cui è presente.

Per eseguire l'attività di aggiornamento dei nomi di database con la nuova codifica, utilizzare invece ALTER DATABASE db_name UPGRADE DATA DIRECTORY NAME: http://dev.mysql.com/doc/refman/5.1/it/alter-database.html


1

Un modo semplice per farlo se hai installato phpmyadmin :

Vai al tuo database, seleziona la scheda "operazione" e puoi vedere il blocco "copia database in". Usalo e puoi copiare il database.


1

Come menzionato nella risposta di Greg , mysqldump db_name | mysql new_db_nameè il modo gratuito, sicuro e semplice per trasferire dati tra database. Tuttavia, è anche molto lento .

Se stai cercando di eseguire il backup dei dati, non puoi permetterti di perdere i dati (in questo o altri database) o stai utilizzando tabelle diverse da innodb, allora dovresti usare mysqldump.

Se stai cercando qualcosa per lo sviluppo, fai il backup di tutti i tuoi database altrove e sei a tuo agio nell'eliminazione e nella reinstallazione mysql(possibilmente manualmente) quando tutto va storto, allora potrei avere la soluzione per te.

Non sono riuscito a trovare una buona alternativa, quindi ho creato una sceneggiatura per farlo da solo. Ho trascorso molto tempo a farlo funzionare la prima volta e onestamente mi terrorizza un po 'a modificarlo ora. I database Innodb non dovevano essere copiati e incollati in questo modo. Piccoli cambiamenti fanno sì che questo fallisca in modi magnifici. Non ho avuto problemi da quando ho finalizzato il codice, ma ciò non significa che non lo farai.

Sistemi testati su (ma potrebbero comunque non funzionare):

  • Ubuntu 16.04, mysql predefinito, innodb, file separati per tabella
  • Ubuntu 18.04, mysql predefinito, innodb, file separati per tabella

Cosa fa

  1. Ottiene il sudoprivilegio e verifica che lo spazio di archiviazione sia sufficiente per clonare il database
  2. Ottiene i privilegi di root mysql
  3. Crea un nuovo database che prende il nome dal ramo git corrente
  4. Clona la struttura in un nuovo database
  5. Passa alla modalità di ripristino per innodb
  6. Elimina i dati predefiniti nel nuovo database
  7. Interrompe mysql
  8. Clona i dati in un nuovo database
  9. Inizia mysql
  10. Collega i dati importati in un nuovo database
  11. Passa dalla modalità di ripristino per innodb
  12. Riavvia mysql
  13. Fornisce l'accesso mysql dell'utente al database
  14. Pulisce i file temporanei

Come si confronta con mysqldump

Su un database da 3 GB, l'utilizzo mysqldumpe mysqlrichiederebbe 40-50 minuti sulla mia macchina. Utilizzando questo metodo, lo stesso processo richiederebbe solo ~ 8 minuti.

Come lo usiamo

Abbiamo le nostre modifiche SQL salvate insieme al nostro codice e il processo di aggiornamento è automatizzato sia in produzione che in sviluppo, con ogni serie di modifiche che esegue un backup del database per ripristinare in caso di errori. Un problema che abbiamo riscontrato è stato quando stavamo lavorando a un progetto a lungo termine con modifiche al database e abbiamo dovuto cambiare ramo nel mezzo per risolvere un bug o tre.

In passato, abbiamo utilizzato un unico database per tutti i rami e avremmo dovuto ricostruire il database ogni volta che passavamo a un ramo non compatibile con le nuove modifiche al database. E quando torniamo indietro, dovremmo eseguire nuovamente gli aggiornamenti.

Abbiamo provato mysqldumpa duplicare il database per diversi rami, ma il tempo di attesa era troppo lungo (40-50 minuti) e nel frattempo non abbiamo potuto fare nient'altro.

Questa soluzione ha ridotto il tempo di clonazione del database a 1/5 del tempo (pensate che il caffè e il bagno si rompano invece di un lungo pranzo).

Compiti comuni e loro tempo

Il passaggio tra filiali con modifiche al database incompatibili richiede più di 50 minuti su un singolo database, ma nessun tempo dopo il tempo di installazione iniziale con mysqldumpo questo codice. Questo codice sembra essere ~ 5 volte più veloce di mysqldump.

Ecco alcuni compiti comuni e approssimativamente quanto tempo impiegherebbero con ciascun metodo:

Crea ramo di funzionalità con modifiche al database e unisci immediatamente:

  • Database singolo: ~ 5 minuti
  • Clona con mysqldump: 50-60 minuti
  • Clona con questo codice: ~ 18 minuti

Crea ramo di funzionalità con modifiche al database, passa a masterper una correzione di bug, apporta una modifica al ramo di funzionalità e unisci:

  • Database singolo: ~ 60 minuti
  • Clona con mysqldump: 50-60 minuti
  • Clona con questo codice: ~ 18 minuti

Crea ramo di funzionalità con modifiche al database, passa a masterper una correzione di bug 5 volte mentre apporti modifiche al ramo di funzionalità tra di loro e unisci:

  • Database singolo: ~ 4 ore, 40 minuti
  • Clona con mysqldump: 50-60 minuti
  • Clona con questo codice: ~ 18 minuti

Il codice

Non utilizzarlo se non hai letto e compreso tutto quanto sopra.

#!/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 tutto procede senza intoppi, dovresti vedere qualcosa di simile:

Schermata dell'output dello script per esempio database


0

Oltre alla risposta di Greg , questo è il modo più semplice e veloce se new_db_namenon esiste ancora:

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 sono presenti trigger nel database originale, è possibile evitare l'errore "Il trigger esiste già" eseguendo il piping di una sostituzione prima dell'importazione:

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

-4

Non penso che ci sia un metodo per farlo. Quando PHPMyAdmin fa questo, scarica il DB, quindi lo reinserisce con il nuovo nome.

Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.