Comme mentionné dans la réponse de Greg , mysqldump db_name | mysql new_db_name
c'est le moyen gratuit, sûr et facile de transférer des données entre des bases de données. Cependant, c'est aussi très lent .
Si vous cherchez à sauvegarder des données, que vous ne pouvez pas vous permettre de perdre des données (dans cette base de données ou dans d'autres), ou si vous utilisez des tables autres que innodb
, vous devriez utiliser mysqldump
.
Si vous cherchez quelque chose à développer, que toutes vos bases de données sont sauvegardées ailleurs et que vous êtes à l'aise pour purger et réinstaller mysql
(éventuellement manuellement) lorsque tout va mal, alors j'ai peut-être la solution pour vous.
Je n'ai pas pu trouver une bonne alternative, alors j'ai construit un script pour le faire moi-même. J'ai passé beaucoup de temps à faire fonctionner cela la première fois et honnêtement, cela me terrifie un peu d'y apporter des modifications maintenant. Les bases de données Innodb n'étaient pas destinées à être copiées et collées de cette manière. De petits changements provoquent un échec magnifique. Je n'ai pas eu de problème depuis que j'ai finalisé le code, mais cela ne veut pas dire que vous ne le ferez pas.
Systèmes testés sur (mais peuvent toujours échouer):
- Ubuntu 16.04, mysql par défaut, innodb, fichiers séparés par table
- Ubuntu 18.04, mysql par défaut, innodb, fichiers séparés par table
Ce qu'il fait
- Obtient le
sudo
privilège et vérifie que vous disposez de suffisamment d'espace de stockage pour cloner la base de données
- Obtient les privilèges root mysql
- Crée une nouvelle base de données nommée d'après la branche git actuelle
- Cloner la structure vers une nouvelle base de données
- Passe en mode de récupération pour innodb
- Supprime les données par défaut dans la nouvelle base de données
- Arrête mysql
- Clone les données dans une nouvelle base de données
- Démarre mysql
- Liens des données importées dans une nouvelle base de données
- Sort du mode de récupération pour innodb
- Redémarre mysql
- Donne aux utilisateurs mysql l'accès à la base de données
- Nettoie les fichiers temporaires
Comment ça se compare avec mysqldump
Sur une base de données de 3 Go, l'utilisation de mysqldump
et mysql
prendrait 40 à 50 minutes sur ma machine. En utilisant cette méthode, le même processus ne prendrait que 8 minutes environ.
Comment nous l'utilisons
Nous avons nos modifications SQL enregistrées avec notre code et le processus de mise à niveau est automatisé à la fois en production et en développement, chaque ensemble de modifications effectuant une sauvegarde de la base de données à restaurer en cas d'erreurs. Un problème que nous avons rencontré était lorsque nous travaillions sur un projet à long terme avec des changements de base de données, et que nous devions changer de branche au milieu pour corriger un bogue ou trois.
Dans le passé, nous utilisions une seule base de données pour toutes les branches et devions reconstruire la base de données chaque fois que nous passions à une branche qui n'était pas compatible avec les nouvelles modifications de la base de données. Et lorsque nous reviendrions, nous devions exécuter à nouveau les mises à niveau.
Nous avons essayé mysqldump
de dupliquer la base de données pour différentes succursales, mais le temps d'attente était trop long (40 à 50 minutes), et nous ne pouvions rien faire d'autre entre-temps.
Cette solution a réduit le temps de clonage de la base de données à 1/5 du temps (pensez à une pause café et salle de bain au lieu d'un long déjeuner).
Tâches courantes et leur temps
Le basculement entre les branches avec des modifications de base de données incompatibles prend plus de 50 minutes sur une seule base de données, mais pas du tout après l'heure de configuration initiale avec mysqldump
ou ce code. Ce code se trouve être ~ 5 fois plus rapide que mysqldump
.
Voici quelques tâches courantes et le temps approximatif qu'elles prendraient avec chaque méthode:
Créez une branche de fonctionnalités avec les modifications de la base de données et fusionnez immédiatement:
- Base de données unique: ~ 5 minutes
- Cloner avec
mysqldump
: 50-60 minutes
- Cloner avec ce code: ~ 18 minutes
Créez une branche de fonctionnalités avec les modifications de la base de données, passez à master
une correction de bogue, apportez une modification sur la branche de fonctionnalités et fusionnez:
- Base de données unique: ~ 60 minutes
- Cloner avec
mysqldump
: 50-60 minutes
- Cloner avec ce code: ~ 18 minutes
Créez une branche de fonctionnalité avec les modifications de la base de données, passez à master
5 fois pour une correction de bogue tout en apportant des modifications sur la branche de fonctionnalité entre les deux, et fusionnez:
- Base de données unique: ~ 4 heures, 40 minutes
- Cloner avec
mysqldump
: 50-60 minutes
- Cloner avec ce code: ~ 18 minutes
Le code
N'utilisez pas ceci sans avoir lu et compris tout ce qui précède.
#!/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 tout se passe bien, vous devriez voir quelque chose comme: