Как упоминалось в ответе Грега , 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
- Перезапускает mysql
- Предоставляет пользователю mysql доступ к базе данных
- Очищает временные файлы
Как это сравнить с mysqldump
В базе данных 3 ГБ использование 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
function now {
date "+%H:%M:%S";
}
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'
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=$?
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
Если все пройдет гладко, вы должны увидеть что-то вроде:
