Як згадується у відповіді Грега , mysqldump db_name | mysql new_db_name
це вільний, безпечний та простий спосіб передачі даних між базами даних. Однак це теж дуже повільно .
Якщо ви хочете створити резервну копію даних, не можете дозволити собі втратити дані (у цій чи іншій базі даних) або використовуєте інші таблиці, крім них innodb
, вам слід скористатися mysqldump
.
Якщо ви шукаєте щось для розвитку, маєте резервні копії всіх своїх баз даних в іншому місці, і вам зручно чистити та перевстановлювати mysql
(можливо, вручну), коли все піде не так, то я, можливо, просто знайде рішення для вас.
Я не зміг знайти гарну альтернативу, тому створив сценарій, щоб зробити це сам. Я витратив багато часу на те, щоб працювати вперше, і це, чесно кажучи, мене трохи жахає, щоб зараз змінити його. Бази даних Innodb не повинні були копіюватися та вставлятись так. Невеликі зміни призводять до того, що це не виходить чудовими способами. У мене не було проблем з моменту остаточного завершення коду, але це не означає, що ви цього не зробите.
Системи, протестовані на (але можуть все-таки вийти з ладу):
- Ubuntu 16.04, mysql за замовчуванням, innodb, окремі файли в таблиці
- Ubuntu 18.04, mysql за замовчуванням, innodb, окремі файли в таблиці
Що це робить
- Отримує
sudo
привілей та підтверджує, що у вас є достатньо місця для зберігання для клонування бази даних
- Отримує root права mysql
- Створюється нова база даних, названа на честь поточної гілки git
- Структура клонів до нової бази даних
- Переходить у режим відновлення для innodb
- Видаляє дані за замовчуванням у новій базі даних
- Зупиняє mysql
- Клонує дані до нової бази даних
- Починає mysql
- Посилання імпортованих даних у новій базі даних
- Вимикається з режиму відновлення для innodb
- Restarts mysql
- Надає користувачеві mysql доступ до бази даних
- Очищає тимчасові файли
Як вона порівнюється з mysqldump
На базі даних 3gb, використовуючи mysqldump
та займаючи mysql
40-50 хвилин на моїй машині. Використовуючи цей метод, той самий процес зайняв би лише ~ 8 хвилин.
Як ми цим користуємося
У нас збережені зміни SQL поряд із кодом, і процес оновлення автоматизований як при виробництві, так і при розробці, при цьому кожен набір змін робить резервну копію бази даних для відновлення, якщо є помилки. Одна з проблем, з якою ми стикалися, полягала в тому, що ми працювали над довгостроковим проектом зі змінами бази даних, і довелося перемикати гілки в середині, щоб виправити помилку чи три.
Раніше ми використовували єдину базу даних для всіх гілок, і нам доведеться перебудовувати базу даних кожного разу, коли ми переходили на гілку, не сумісну з новими змінами бази даних. І коли ми повернулися назад, нам доведеться запустити оновлення ще раз.
Ми намагалися mysqldump
дублювати базу даних для різних гілок, але час очікування був занадто довгим (40-50 хвилин), і ми поки нічого не могли зробити.
Це рішення скоротило час клонування бази даних до 1/5 часу (подумайте, перерва на каву та ванну замість довгого обіду).
Загальні завдання та їх час
Перемикання між гілками із несумісними змінами бази даних займає 50+ хвилин у одній базі даних, але зовсім немає часу після початкового часу налаштування з mysqldump
цим кодом чи цим. Цей код просто трапляється в 5 разів швидше, ніж mysqldump
.
Ось декілька загальних завдань і приблизно, скільки часу вони б зайняли для кожного методу:
Створіть гілку функції зі змінами бази даних та негайно зробіть об’єднання:
- Єдина база даних: ~ 5 хвилин
- Клон з
mysqldump
: 50-60 хвилин
- Клон з цим кодом: ~ 18 хвилин
Створіть гілку функції зі змінами бази даних, перейдіть на master
помилку, внесіть зміни в гілку функції та зробіть об'єднання:
- Єдина база даних: ~ 60 хвилин
- Клон з
mysqldump
: 50-60 хвилин
- Клон з цим кодом: ~ 18 хвилин
Створіть гілку функції зі змінами бази даних, перейдіть до master
помилки 5 разів, вносячи правки в гілку функції між ними, і зробіть об’єднання:
- Єдина база даних: ~ 4 години 40 хвилин
- Клон з
mysqldump
: 50-60 хвилин
- Клон з цим кодом: ~ 18 хвилин
Код
Не використовуйте це, якщо ви не прочитали і не зрозуміли все вище.
#!/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
Якщо все пройде гладко, ви повинні побачити щось на кшталт: