Seperti yang disebutkan dalam jawaban Greg , mysqldump db_name | mysql new_db_name
adalah cara gratis, aman, dan mudah untuk mentransfer data antar database. Namun, ini juga sangat lambat .
Jika Anda mencari untuk membuat cadangan data, tidak mampu kehilangan data (dalam database ini atau lainnya), atau menggunakan tabel selain innodb
, maka Anda harus menggunakannya mysqldump
.
Jika Anda mencari sesuatu untuk pengembangan, minta semua basis data Anda dicadangkan di tempat lain, dan nyaman dibersihkan dan diinstal ulang mysql
(mungkin secara manual) ketika semuanya berjalan salah, maka saya mungkin punya solusi untuk Anda.
Saya tidak dapat menemukan alternatif yang baik, jadi saya membuat skrip untuk melakukannya sendiri. Saya menghabiskan banyak waktu untuk melakukan ini untuk pertama kalinya dan itu benar-benar membuat saya sedikit takut untuk mengubahnya sekarang. Database Innodb tidak dimaksudkan untuk disalin dan ditempelkan seperti ini. Perubahan kecil menyebabkan ini gagal dengan cara yang luar biasa. Saya tidak punya masalah sejak saya menyelesaikan kode, tetapi itu tidak berarti Anda tidak akan melakukannya.
Sistem diuji pada (tetapi mungkin masih gagal):
- Ubuntu 16.04, default mysql, innodb, file terpisah per tabel
- Ubuntu 18.04, default mysql, innodb, file terpisah per tabel
Apa yang dilakukannya
- Dapatkan
sudo
hak istimewa dan verifikasi Anda memiliki ruang penyimpanan yang cukup untuk mengkloning basis data
- Dapatkan root mysql privilege
- Membuat database baru yang dinamai cabang git saat ini
- Struktur klon ke database baru
- Beralih ke mode pemulihan untuk innodb
- Menghapus data default di database baru
- Berhenti mysql
- Mengkloning data ke basis data baru
- Mulai mysql
- Tautan data yang diimpor dalam database baru
- Beralih dari mode pemulihan untuk innodb
- Mulai ulang mysql
- Memberikan akses pengguna mysql ke database
- Membersihkan file sementara
Bagaimana membandingkannya dengan mysqldump
Pada basis data 3gb, menggunakan mysqldump
dan mysql
akan memakan waktu 40-50 menit pada mesin saya. Dengan menggunakan metode ini, proses yang sama hanya akan memakan waktu ~ 8 menit.
Bagaimana kami menggunakannya
Kami memiliki perubahan SQL yang disimpan di samping kode kami dan proses pemutakhiran terotomasi untuk produksi dan pengembangan, dengan setiap rangkaian perubahan membuat cadangan dari basis data untuk dipulihkan jika ada kesalahan. Satu masalah yang kami temui adalah ketika kami sedang mengerjakan proyek jangka panjang dengan perubahan basis data, dan harus mengganti cabang di tengahnya untuk memperbaiki satu atau tiga bug.
Di masa lalu, kami menggunakan database tunggal untuk semua cabang, dan harus membangun kembali basis data setiap kali kami beralih ke cabang yang tidak kompatibel dengan perubahan database baru. Dan ketika kami beralih kembali, kami harus menjalankan upgrade lagi.
Kami mencoba mysqldump
menduplikasi database untuk cabang yang berbeda, tetapi waktu tunggu terlalu lama (40-50 menit), dan sementara itu kami tidak dapat melakukan hal lain.
Solusi ini mempersingkat waktu klon basis data menjadi 1/5 waktu (bayangkan kopi dan istirahat kamar mandi alih-alih makan siang yang panjang).
Tugas umum dan waktu mereka
Berpindah antar cabang dengan perubahan basis data yang tidak kompatibel membutuhkan waktu lebih dari 50 menit pada satu basis data, tetapi tidak ada waktu sama sekali setelah waktu pengaturan awal dengan mysqldump
atau kode ini. Kode ini kebetulan ~ 5 kali lebih cepat dari mysqldump
.
Berikut adalah beberapa tugas umum dan kira-kira berapa lama waktu yang dibutuhkan untuk setiap metode:
Buat cabang fitur dengan perubahan database dan segera bergabung:
- Basis data tunggal: ~ 5 menit
- Klon dengan
mysqldump
: 50-60 menit
- Kloning dengan kode ini: ~ 18 menit
Buat cabang fitur dengan perubahan basis data, alihkan ke master
untuk perbaikan bug, edit di cabang fitur, dan gabungkan:
- Basis data tunggal: ~ 60 menit
- Klon dengan
mysqldump
: 50-60 menit
- Kloning dengan kode ini: ~ 18 menit
Buat cabang fitur dengan perubahan basis data, alihkan ke master
untuk perbaikan bug 5 kali saat mengedit di cabang fitur di antara, dan gabungkan:
- Basis data tunggal: ~ 4 jam, 40 menit
- Klon dengan
mysqldump
: 50-60 menit
- Kloning dengan kode ini: ~ 18 menit
Kode
Jangan gunakan ini kecuali Anda sudah membaca dan memahami semua yang di atas.
#!/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
Jika semuanya berjalan lancar, Anda akan melihat sesuatu seperti: