ตามที่ระบุไว้ในคำตอบของเกร็ก , mysqldump db_name | mysql new_db_nameเป็นฟรีปลอดภัยและใช้งานง่ายด้วยวิธีการถ่ายโอนข้อมูลระหว่างฐานข้อมูล แต่ก็ยังช้าจริงๆ
หากคุณกำลังมองหาในการสำรองข้อมูลไม่สามารถจะสูญเสียข้อมูล (ในนี้หรือฐานข้อมูลอื่น ๆ ) หรือใช้ตารางอื่น ๆ กว่าแล้วคุณควรใช้innodbmysqldump
หากคุณกำลังมองหาบางอย่างเพื่อการพัฒนาให้สำรองฐานข้อมูลทั้งหมดของคุณไว้ที่อื่นและลบทิ้งและติดตั้งใหม่mysql(อาจเป็นด้วยตนเอง) เมื่อทุกอย่างผิดพลาดฉันก็อาจมีทางออกให้คุณ
ฉันหาทางเลือกที่ดีไม่ได้ดังนั้นฉันจึงสร้างสคริปต์ขึ้นมาเพื่อทำด้วยตัวเอง ฉันใช้เวลามากในการทำให้สิ่งนี้ทำงานเป็นครั้งแรกและมันทำให้ฉันตกใจเล็กน้อยที่จะทำการเปลี่ยนแปลงในตอนนี้ ฐานข้อมูล Innodb ไม่ได้หมายถึงการคัดลอกและวางแบบนี้ การเปลี่ยนแปลงเล็กน้อยทำให้สิ่งนี้ล้มเหลวในรูปแบบที่งดงาม ฉันไม่ได้มีปัญหาเนื่องจากฉันทำการสรุปรหัส แต่นั่นไม่ได้หมายความว่าคุณจะไม่ทำ
ระบบทดสอบใน (แต่อาจยังล้มเหลว):
- Ubuntu 16.04, mysql เริ่มต้น, innodb, แยกไฟล์ต่อตาราง
 
- Ubuntu 18.04, mysql เริ่มต้น, innodb, แยกไฟล์ต่อตาราง
 
มันทำอะไร
- รับ
sudoสิทธิ์และตรวจสอบว่าคุณมีพื้นที่เก็บข้อมูลเพียงพอในการโคลนฐานข้อมูล 
- รับสิทธิ์ root mysql
 
- สร้างฐานข้อมูลใหม่ตั้งชื่อตามสาขา git ปัจจุบัน
 
- โครงสร้างโคลนกับฐานข้อมูลใหม่
 
- เปลี่ยนเป็นโหมดการกู้คืนสำหรับ innodb
 
- ลบข้อมูลเริ่มต้นในฐานข้อมูลใหม่
 
- หยุด mysql
 
- โคลนข้อมูลไปยังฐานข้อมูลใหม่
 
- เริ่ม mysql
 
- ลิงก์ข้อมูลที่นำเข้าในฐานข้อมูลใหม่
 
- สลับออกจากโหมดการกู้คืนสำหรับ innodb
 
- รีสตาร์ท 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
หากทุกอย่างราบรื่นคุณควรเห็นสิ่งต่อไปนี้:
