Wie in Gregs Antwort erwähnt , mysqldump db_name | mysql new_db_name
ist dies die kostenlose, sichere und einfache Möglichkeit, Daten zwischen Datenbanken zu übertragen. Es ist jedoch auch sehr langsam .
Wenn Sie Daten sichern möchten, es sich nicht leisten können, Daten (in dieser oder anderen Datenbanken) zu verlieren, oder andere Tabellen als verwenden innodb
, sollten Sie diese verwenden mysqldump
.
Wenn Sie nach etwas für die Entwicklung suchen, alle Ihre Datenbanken an anderer Stelle sichern lassen und das Löschen und Neuinstallieren mysql
(möglicherweise manuell) problemlos durchführen möchten, wenn alles schief geht, habe ich möglicherweise nur die Lösung für Sie.
Ich konnte keine gute Alternative finden, also habe ich ein Skript erstellt, um es selbst zu machen. Ich habe viel Zeit damit verbracht, dies zum ersten Mal zum Laufen zu bringen, und es macht mir ehrlich gesagt ein wenig Angst, jetzt Änderungen daran vorzunehmen. Innodb-Datenbanken sollten nicht so kopiert und eingefügt werden. Kleine Änderungen führen dazu, dass dies auf großartige Weise fehlschlägt. Ich hatte kein Problem, seit ich den Code fertiggestellt habe, aber das heißt nicht, dass Sie es nicht tun werden.
Systeme, auf denen getestet wurde (die jedoch möglicherweise immer noch ausfallen):
- Ubuntu 16.04, Standard MySQL, Innodb, separate Dateien pro Tabelle
- Ubuntu 18.04, Standard MySQL, Innodb, separate Dateien pro Tabelle
Was es macht
- Ruft
sudo
Berechtigungen ab und überprüft, ob Sie über genügend Speicherplatz verfügen, um die Datenbank zu klonen
- Ruft Root-MySQL-Berechtigungen ab
- Erstellt eine neue Datenbank, die nach dem aktuellen Git-Zweig benannt ist
- Klont die Struktur in eine neue Datenbank
- Schaltet für innodb in den Wiederherstellungsmodus
- Löscht Standarddaten in neuer Datenbank
- Stoppt MySQL
- Klont Daten in eine neue Datenbank
- Startet MySQL
- Verknüpft importierte Daten in eine neue Datenbank
- Schaltet den Wiederherstellungsmodus für innodb aus
- Startet MySQL neu
- Ermöglicht MySQL-Benutzern den Zugriff auf die Datenbank
- Bereinigt temporäre Dateien
Wie es mit vergleicht mysqldump
In einer 3-GB-Datenbank würde die Verwendung von mysqldump
und mysql
auf meinem Computer 40-50 Minuten dauern. Mit dieser Methode würde der gleiche Vorgang nur ~ 8 Minuten dauern.
Wie wir es benutzen
Wir haben unsere SQL-Änderungen zusammen mit unserem Code gespeichert und der Upgrade-Prozess wird sowohl in der Produktion als auch in der Entwicklung automatisiert, wobei jede Reihe von Änderungen eine Sicherungskopie der Datenbank erstellt, um sie bei Fehlern wiederherzustellen. Ein Problem, auf das wir stießen, war, als wir an einem langfristigen Projekt mit Datenbankänderungen arbeiteten und die Zweige in der Mitte wechseln mussten, um einen oder drei Fehler zu beheben.
In der Vergangenheit haben wir eine einzige Datenbank für alle Zweige verwendet und mussten die Datenbank immer dann neu erstellen, wenn wir zu einem Zweig gewechselt sind, der nicht mit den neuen Datenbankänderungen kompatibel war. Und wenn wir zurückschalteten, mussten wir die Upgrades erneut ausführen.
Wir haben versucht mysqldump
, die Datenbank für verschiedene Zweige zu duplizieren, aber die Wartezeit war zu lang (40-50 Minuten), und wir konnten in der Zwischenzeit nichts anderes tun.
Diese Lösung verkürzte die Klonzeit der Datenbank auf 1/5 der Zeit (denken Sie an eine Kaffee- und Badezimmerpause anstelle eines langen Mittagessens).
Gemeinsame Aufgaben und ihre Zeit
Das Wechseln zwischen Zweigen mit inkompatiblen Datenbankänderungen dauert in einer einzelnen Datenbank mehr als 50 Minuten, jedoch nach der anfänglichen Einrichtungszeit mit mysqldump
oder diesem Code überhaupt keine Zeit . Dieser Code ist zufällig ~ 5 mal schneller als mysqldump
.
Hier sind einige allgemeine Aufgaben und ungefähr, wie lange sie mit jeder Methode dauern würden:
Erstellen Sie einen Feature-Zweig mit Datenbankänderungen und führen Sie ihn sofort zusammen:
- Einzelne Datenbank: ~ 5 Minuten
- Klonen mit
mysqldump
: 50-60 Minuten
- Klonen Sie mit diesem Code: ~ 18 Minuten
Erstellen Sie einen Feature-Zweig mit Datenbankänderungen, wechseln Sie zu, master
um einen Bugfix zu erhalten, bearbeiten Sie den Feature-Zweig und führen Sie Folgendes zusammen:
- Einzelne Datenbank: ~ 60 Minuten
- Klonen mit
mysqldump
: 50-60 Minuten
- Klonen Sie mit diesem Code: ~ 18 Minuten
Erstellen Sie einen Feature-Zweig mit Datenbankänderungen, wechseln Sie master
fünfmal zu einem Bugfix, während Sie den Feature-Zweig dazwischen bearbeiten, und führen Sie Folgendes zusammen:
- Einzelne Datenbank: ~ 4 Stunden, 40 Minuten
- Klonen mit
mysqldump
: 50-60 Minuten
- Klonen Sie mit diesem Code: ~ 18 Minuten
Der Code
Verwenden Sie dies nur, wenn Sie alles oben Gelesene gelesen und verstanden haben.
#!/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
Wenn alles reibungslos verläuft, sollten Sie Folgendes sehen: