Như đã đề cập trong câu trả lời của Greg , mysqldump db_name | mysql new_db_name
là cách miễn phí, an toàn và dễ dàng để chuyển dữ liệu giữa các cơ sở dữ liệu. Tuy nhiên, nó cũng rất chậm .
Nếu bạn đang tìm cách sao lưu dữ liệu, không thể để mất dữ liệu (trong cơ sở dữ liệu này hoặc cơ sở dữ liệu khác) hoặc đang sử dụng các bảng khác innodb
, thì bạn nên sử dụng mysqldump
.
Nếu bạn đang tìm kiếm thứ gì đó để phát triển, có tất cả các cơ sở dữ liệu của bạn được sao lưu ở nơi khác và thoải mái thanh lọc và cài đặt lại mysql
(có thể là thủ công) khi mọi thứ không ổn, thì tôi có thể có giải pháp cho bạn.
Tôi không thể tìm thấy một sự thay thế tốt, vì vậy tôi đã xây dựng một kịch bản để tự làm điều đó. Tôi đã dành rất nhiều thời gian để làm việc này lần đầu tiên và nó thực sự làm tôi sợ một chút để thay đổi nó ngay bây giờ. Cơ sở dữ liệu Innodb không có nghĩa là sao chép và dán như thế này. Những thay đổi nhỏ khiến điều này thất bại theo những cách tuyệt vời. Tôi đã không gặp vấn đề gì kể từ khi tôi hoàn tất mã, nhưng điều đó không có nghĩa là bạn sẽ không.
Các hệ thống được thử nghiệm trên (nhưng vẫn có thể thất bại):
- Ubuntu 16.04, mysql mặc định, innodb, các tệp riêng biệt trên mỗi bảng
- Ubuntu 18.04, mysql mặc định, innodb, các tệp riêng biệt trên mỗi bảng
Những gì nó làm
- Nhận
sudo
đặc quyền và xác minh bạn có đủ dung lượng lưu trữ để sao chép cơ sở dữ liệu
- Được quyền root mysql
- Tạo một cơ sở dữ liệu mới được đặt tên theo nhánh git hiện tại
- Cấu trúc nhân bản vào cơ sở dữ liệu mới
- Chuyển sang chế độ phục hồi cho innodb
- Xóa dữ liệu mặc định trong cơ sở dữ liệu mới
- Dừng mysql
- Nhân bản dữ liệu vào cơ sở dữ liệu mới
- Bắt đầu mysql
- Liên kết dữ liệu đã nhập trong cơ sở dữ liệu mới
- Chuyển ra khỏi chế độ phục hồi cho innodb
- Khởi động lại mysql
- Cung cấp cho người dùng mysql quyền truy cập vào cơ sở dữ liệu
- Dọn dẹp tập tin tạm thời
Làm thế nào nó so sánh với mysqldump
Trên cơ sở dữ liệu 3gb, sử dụng mysqldump
và mysql
sẽ mất 40-50 phút trên máy của tôi. Sử dụng phương pháp này, quá trình tương tự sẽ chỉ mất ~ 8 phút.
Chúng ta sử dụng nó như thế nào
Chúng tôi có các thay đổi SQL được lưu cùng với mã của chúng tôi và quá trình nâng cấp được tự động hóa trên cả sản xuất và phát triển, với mỗi bộ thay đổi tạo bản sao lưu cơ sở dữ liệu để khôi phục nếu có lỗi. Một vấn đề chúng tôi gặp phải là khi chúng tôi đang thực hiện một dự án dài hạn với các thay đổi cơ sở dữ liệu và phải chuyển các nhánh ở giữa nó để sửa một hoặc ba lỗi.
Trước đây, chúng tôi đã sử dụng một cơ sở dữ liệu duy nhất cho tất cả các chi nhánh và sẽ phải xây dựng lại cơ sở dữ liệu bất cứ khi nào chúng tôi chuyển sang một chi nhánh không tương thích với các thay đổi cơ sở dữ liệu mới. Và khi chúng tôi quay trở lại, chúng tôi sẽ phải chạy lại các bản nâng cấp.
Chúng tôi đã cố gắng mysqldump
sao chép cơ sở dữ liệu cho các chi nhánh khác nhau, nhưng thời gian chờ đợi quá dài (40-50 phút) và chúng tôi không thể làm gì khác trong lúc này.
Giải pháp này rút ngắn thời gian sao chép cơ sở dữ liệu xuống còn 1/5 thời gian (nghĩ rằng nghỉ giải lao và cà phê thay vì một bữa trưa dài).
Nhiệm vụ chung và thời gian của họ
Chuyển đổi giữa các nhánh với thay đổi cơ sở dữ liệu không tương thích mất hơn 50 phút trên một cơ sở dữ liệu, nhưng không có thời gian nào sau thời gian thiết lập ban đầu với mysqldump
hoặc mã này. Mã này chỉ xảy ra nhanh hơn ~ 5 lần so vớimysqldump
.
Dưới đây là một số tác vụ phổ biến và khoảng thời gian chúng sẽ sử dụng trong mỗi phương thức:
Tạo nhánh tính năng với các thay đổi cơ sở dữ liệu và hợp nhất ngay lập tức:
- Cơ sở dữ liệu duy nhất: ~ 5 phút
- Nhân bản với
mysqldump
: 50-60 phút
- Sao chép với mã này: ~ 18 phút
Tạo nhánh tính năng với các thay đổi cơ sở dữ liệu, chuyển sang tìm master
lỗi, thực hiện chỉnh sửa trên nhánh tính năng và hợp nhất:
- Cơ sở dữ liệu duy nhất: ~ 60 phút
- Nhân bản với
mysqldump
: 50-60 phút
- Sao chép với mã này: ~ 18 phút
Tạo nhánh tính năng với các thay đổi cơ sở dữ liệu, chuyển sang sửa master
lỗi 5 lần trong khi thực hiện các chỉnh sửa trên nhánh tính năng ở giữa và hợp nhất:
- Cơ sở dữ liệu duy nhất: ~ 4 giờ, 40 phút
- Nhân bản với
mysqldump
: 50-60 phút
- Sao chép với mã này: ~ 18 phút
Mật mã
Không sử dụng điều này trừ khi bạn đã đọc và hiểu mọi thứ ở trên.
#!/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
Nếu mọi thứ diễn ra suôn sẻ, bạn sẽ thấy một cái gì đó như: