Clonación de una base de datos MySQL en la misma instancia de MySql


154

Me gustaría escribir un script que copie mi base sitedb1de datos actual sitedb2en la misma instancia de base de datos mysql. Sé que puedo volcar el sitedb1 a un script sql:

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

y luego importarlo a sitedb2. ¿Hay una manera más fácil, sin volcar la primera base de datos en un archivo sql?


Posible duplicado de la base de datos Clone MySQL
bummi

Respuestas:


302

Como dice el manual en Copiar bases de datos , puede canalizar el volcado directamente en el cliente mysql:

mysqldump db_name | mysql new_db_name

Si está utilizando MyISAM, podría copiar los archivos, pero no lo recomendaría. Es un poco dudoso.

Integrado de varias buenas otras respuestas

Ambos mysqldumpy los mysqlcomandos aceptan opciones para configurar detalles de conexión (y mucho más), como:

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

Además, si la nueva base de datos aún no existe, debe crearla de antemano (por ejemplo, con echo "create database new_db_name" | mysql -u <dbuser> -p).


2
Un poco ... se salta una gran cantidad de S de disco, aunque como usted no tiene que leer / escribir los datos en dos ocasiones
Greg

8
Si su base de datos tiene un tamaño de gigabytes, esto probablemente no le gane mucho. Creo que a lo que está llegando el OP es que no quieren externalizar la copia: ¿se puede hacer puramente dentro de mysql?
cletus

3
Yo diría que cuanto más grande sea la base de datos, más ganará ... No hay forma de hacer esto dentro de MySQL afaik (excepto a mano, una tabla / vista a la vez)
Greg

41
Primero tuve que crear new_db usando el comando estándar mysql: "CREATE DATABASE new_db;" y luego usó estos comandos: mysqldump -u root -p old_db | mysql -u root -p new_db
valentt

44
Esto no funciona para mí, si tengo que poner la contraseña de dumping y la importación de la siguiente manera: mysqldump -uroot -p database1 | mysql -uroot -p database2. Me solicitan ambas pws pero solo puedo poner una. Las miradas rápidas como esto: Enter password: Enter password: . Después de dar la primera pw, el proceso espera para siempre.
Torsten

66

Usando utilidades MySQL

Las utilidades MySQL contienen la buena herramienta mysqldbcopy que, de forma predeterminada, copia una base de datos que incluye todos los objetos relacionados ("tablas, vistas, disparadores, eventos, procedimientos, funciones y concesiones a nivel de base de datos") y datos de un servidor de base de datos a la misma o a otra Servidor de base de datos. Hay muchas opciones disponibles para personalizar lo que realmente se copia.

Entonces, para responder la pregunta del OP:

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

1
Esto funcionó bien para mí, la mysqldumpsolución basada estaba fallando.
saji89

1
En mi caso, tuve que especificar el puerto de esta manera: --source = root: your_password @ localhost: 3307 (de lo contrario me daría un error de acceso denegado)
pbz

44
Necesito sudo apt-get install mysql-utilities, pero esto es muy bueno. ¿Puedo omitir la contraseña y se me solicitará que la ingrese?
ADTC

2
@ADTC No sé si hay una forma integrada de permitir mysqldbcopyque te pida la contraseña; al menos no pude encontrar algo así en la documentación. Sin embargo, podría construir esta funcionalidad usted mismo. En Bash eso podría verse más o menos así: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 el comando de Chriki funciona perfectamente. Solo tenía que agregar --forceal mysqldbcopycomando porque ya había creado la base de datos de destino. ¡Gracias!
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
¿Qué agrega a la respuesta aceptada? Es similar, pero agrega algunas diferencias, agrega algunos comentarios para una mejor comprensión
Yaroslav

Esta debería ser la respuesta aceptada, ya que creará la base de datos, también es buena para la autenticación. la respuesta aceptada actual le dirá el acceso denegado, entonces la tabla no existe.
Rami Dabain

14

Necesita ejecutar el comando desde el terminal / símbolo del sistema.

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

p.ej: mysqldump -u root test_db1 | mysql -u root test_db2

Esto copia test_db1 a test_db2 y otorga el acceso a 'root' @ 'localhost'


Me gusta esta respuesta, es crujiente. Sin embargo, para mí mysql requiere -p antes de la contraseña.
lwitzel

1
¿Cómo podemos también copiar funciones, eventos, etc. creados en la base de datos original? Esto solo parece tablas de copias.
Dogan Askan

12

La mejor manera es ingresar estos comandos en su terminal y establecer permisos para el usuario root. Funciona para mi..!

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

1
La pregunta indicaba explícitamente que el método de exportación / importación ya se conoce.
lav

3
Esta es la mejor manera de hacerlo. También funciona con grandes bases de datos, mientras que la versión canalizada mysqldump -u <user> -p <pwd> db_name | mysql -u <user> -p <pwd> new_db_namepuede ser problemática con grandes bases de datos.
Alex

10

Puede usar (en 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;

La razón por la que no estoy usando la sintaxis CREATE TABLE ... SELECT ... es para preservar los índices. Por supuesto, esto solo copia tablas. Las vistas y los procedimientos no se copian, aunque se puede hacer de la misma manera.

Ver CREAR TABLA .


3
Esto podría fallar en la integridad de referencia ya que las tablas dependientes aún no se pueden copiar. Tal vez podría funcionar en una gran transacción.
Ondrej Galbavý

4

Primero cree la base de datos duplicada:

CREATE DATABASE duplicateddb;

Asegúrese de que todos los permisos, etc. estén en su lugar y:

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

2

Puedes hacer algo como lo siguiente:

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

1

Esta declaración se agregó en MySQL 5.1.7, pero se descubrió que era peligrosa y se eliminó en MySQL 5.1.23. Estaba destinado a permitir la actualización de bases de datos anteriores a 5.1 para usar la codificación implementada en 5.1 para asignar nombres de bases de datos a nombres de directorios de bases de datos. Sin embargo, el uso de esta declaración podría provocar la pérdida del contenido de la base de datos, por lo que se eliminó. No utilice RENAME DATABASE en versiones anteriores en las que está presente.

Para realizar la tarea de actualizar los nombres de las bases de datos con la nueva codificación, use ALTER DATABASE db_name UPGRADE DATA DIRECTORIO DE NOMBRES en su lugar: http://dev.mysql.com/doc/refman/5.1/en/alter-database.html


1

Una manera simple de hacerlo si instaló phpmyadmin :

Vaya a su base de datos, seleccione la pestaña "operación" y podrá ver el bloque "copiar base de datos a". Úselo y puede copiar la base de datos.


1

Como se menciona en la respuesta de Greg , mysqldump db_name | mysql new_db_namees la forma gratuita, segura y fácil de transferir datos entre bases de datos. Sin embargo, también es muy lento .

Si está buscando hacer una copia de seguridad de los datos, no puede permitirse el lujo de perder datos (en esta u otras bases de datos), o está usando tablas diferentes innodb, entonces debe usarlas mysqldump.

Si está buscando algo para el desarrollo, haga una copia de seguridad de todas sus bases de datos en otro lugar y se sienta cómodo purgando y reinstalando mysql (posiblemente manualmente) cuando todo sale mal, entonces podría tener la solución para usted.

No pude encontrar una buena alternativa, así que construí un script para hacerlo yo mismo. Gasté mucho tiempo haciendo que esto funcione la primera vez y, sinceramente, me aterra un poco hacer cambios ahora. Las bases de datos Innodb no estaban destinadas a copiarse y pegarse de esta manera. Pequeños cambios hacen que esto falle de maneras magníficas. No he tenido ningún problema desde que finalicé el código, pero eso no significa que no lo harás.

Sistemas probados (pero aún pueden fallar):

  • Ubuntu 16.04, mysql predeterminado, innodb, archivos separados por tabla
  • Ubuntu 18.04, mysql predeterminado, innodb, archivos separados por tabla

Que hace

  1. Obtiene sudoprivilegios y verifica que tenga suficiente espacio de almacenamiento para clonar la base de datos
  2. Obtiene privilegios de root mysql
  3. Crea una nueva base de datos con el nombre de la rama git actual
  4. Estructura de clones a nueva base de datos
  5. Cambia al modo de recuperación para innodb
  6. Elimina los datos predeterminados en la nueva base de datos.
  7. Detiene mysql
  8. Clona datos en una nueva base de datos
  9. Inicia mysql
  10. Vincula datos importados en una nueva base de datos
  11. Cambia del modo de recuperación para innodb
  12. Reinicia mysql
  13. Da acceso de usuario mysql a la base de datos
  14. Limpia archivos temporales

Cómo se compara con mysqldump

En una base de datos de 3 gb, usar mysqldumpy mysqltomaría 40-50 minutos en mi máquina. Usando este método, el mismo proceso solo tomaría ~ 8 minutos.

Como lo usamos

Tenemos nuestros cambios de SQL guardados junto con nuestro código y el proceso de actualización está automatizado tanto en producción como en desarrollo, con cada conjunto de cambios haciendo una copia de seguridad de la base de datos para restaurar si hay errores. Un problema con el que nos encontramos fue cuando estábamos trabajando en un proyecto a largo plazo con cambios en la base de datos, y tuvimos que cambiar ramas en el medio para corregir un error o tres.

En el pasado, utilizamos una única base de datos para todas las sucursales, y tendríamos que reconstruir la base de datos cada vez que cambiamos a una sucursal que no era compatible con los nuevos cambios en la base de datos. Y cuando volviéramos, tendríamos que ejecutar las actualizaciones nuevamente.

Intentamos mysqldumpduplicar la base de datos para diferentes sucursales, pero el tiempo de espera fue demasiado largo (40-50 minutos) y, mientras tanto, no pudimos hacer nada más.

Esta solución acortó el tiempo de clonación de la base de datos a 1/5 del tiempo (piense en el café y el baño en lugar de un almuerzo largo).

Tareas comunes y su tiempo

Cambiar entre sucursales con cambios incompatibles en la base de datos lleva más de 50 minutos en una sola base de datos, pero no hay tiempo después del tiempo de configuración inicial con mysqldumpeste código. Este código resulta ser ~ 5 veces más rápido que mysqldump.

Aquí hay algunas tareas comunes y aproximadamente cuánto tiempo tomarían con cada método:

Cree una rama de características con cambios en la base de datos y fusione de inmediato:

  • Base de datos única: ~ 5 minutos
  • Clonar con mysqldump: 50-60 minutos
  • Clonar con este código: ~ 18 minutos

Cree una rama de características con cambios en la base de datos, cambie a masteruna corrección de errores, realice una edición en la rama de características y fusione:

  • Base de datos única: ~ 60 minutos
  • Clonar con mysqldump: 50-60 minutos
  • Clonar con este código: ~ 18 minutos

Cree una rama de características con cambios en la base de datos, cambie a masteruna corrección de errores 5 veces mientras realiza ediciones en la rama de características intermedias y combine:

  • Base de datos única: ~ 4 horas, 40 minutos
  • Clonar con mysqldump: 50-60 minutos
  • Clonar con este código: ~ 18 minutos

El código

No use esto a menos que haya leído y entendido todo lo anterior.

#!/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

Si todo va bien, debería ver algo como:

Captura de pantalla de la salida del script, por ejemplo, la base de datos


0

Además de la respuesta de Greg , esta es la forma más fácil y rápida si new_db_nameaún no existe:

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

Si tiene activadores en su base de datos original, puede evitar el error "El activador ya existe" colocando un reemplazo antes de la importación:

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

-4

No creo que haya un método para hacer esto. Cuando PHPMyAdmin hace esto, volca la base de datos y luego la vuelve a insertar con el nuevo nombre.

Al usar nuestro sitio, usted reconoce que ha leído y comprende nuestra Política de Cookies y Política de Privacidad.
Licensed under cc by-sa 3.0 with attribution required.