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
- Ottiene il
sudo
privilegio e verifica che lo spazio di archiviazione sia sufficiente per clonare il database
- Ottiene i privilegi di root mysql
- Crea un nuovo database che prende il nome dal ramo git corrente
- Clona la struttura in un nuovo database
- Passa alla modalità di ripristino per innodb
- Elimina i dati predefiniti nel nuovo database
- Interrompe mysql
- Clona i dati in un nuovo database
- Inizia mysql
- Collega i dati importati in un nuovo database
- Passa dalla modalità di ripristino per innodb
- Riavvia mysql
- Fornisce l'accesso mysql dell'utente al database
- Pulisce i file temporanei
Come si confronta con mysqldump
Su un database da 3 GB, l'utilizzo mysqldump
e mysql
richiederebbe 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 mysqldump
a 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 mysqldump
o 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 master
per 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 master
per 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: