Mengkloning database MySQL pada instance MySql yang sama


154

Saya ingin menulis naskah yang salinan database saya saat ini sitedb1untuk sitedb2di contoh database mysql yang sama. Saya tahu saya bisa membuang sitedb1 ke skrip sql:

mysqldump -u root -p sitedb1 >~/db_name.sql

dan kemudian mengimpornya ke sitedb2. Apakah ada cara yang lebih mudah, tanpa membuang database pertama ke file sql?


Kemungkinan duplikat dari database MySQL Clone
bummi

Jawaban:


302

Seperti yang dikatakan manual di Menyalin Database, Anda dapat menyalurkan dump langsung ke klien mysql:

mysqldump db_name | mysql new_db_name

Jika Anda menggunakan MyISAM, Anda dapat menyalin file, tetapi saya tidak akan merekomendasikannya. Agak cerdik.

Terintegrasi dari berbagai jawaban baik lainnya

Baik perintah mysqldumpmaupun mysqlmenerima opsi untuk mengatur detail koneksi (dan banyak lagi), seperti:

mysqldump -u <user name> --password=<pwd> <original db> | mysql -u <user name> -p <new db>

Juga, jika database baru belum ada, Anda harus membuatnya sebelumnya (misalnya dengan echo "create database new_db_name" | mysql -u <dbuser> -p).


2
Agak ... itu melompat banyak IO disk meskipun Anda tidak harus membaca / menulis data dua kali
Greg

8
Jika database Anda berukuran gigabytes, ini mungkin tidak akan banyak membantu Anda. Saya pikir apa yang OP dapatkan adalah mereka tidak ingin mengeksternalkan salinan: dapatkah itu dilakukan murni dalam mysql?
cletus

3
Saya akan mengatakan semakin besar DB semakin Anda mendapatkan ... Tidak ada cara untuk melakukan ini dalam MySQL afaik (kecuali dengan tangan, satu tabel / tampilan pada suatu waktu)
Greg

41
Saya pertama kali harus membuat new_db dengan menggunakan perintah mysql standar: "CREATE DATABASE new_db;" dan kemudian menggunakan perintah ini: mysqldump -u root -p old_db | mysql -u root -p new_db
valentt

4
Ini tidak bekerja untuk saya, jika saya harus dimasukkan ke dalam password untuk dumping dan mengimpor seperti ini: mysqldump -uroot -p database1 | mysql -uroot -p database2. Saya diminta untuk kedua pws tetapi hanya bisa dimasukkan ke dalam satu. Terlihat cepat seperti ini: Enter password: Enter password: . Setelah memberikan pw pertama, proses menunggu selamanya.
Torsten

66

Menggunakan Utilities MySQL

Utilitas MySQL berisi alat mysqldbcopyyang bagus yang secara default menyalin DB termasuk semua objek terkait ("tabel, tampilan, pemicu, peristiwa, prosedur, fungsi, dan hibah tingkat basis data") dan data dari satu server DB ke server yang sama atau yang lain Server DB. Ada banyak opsi yang tersedia untuk menyesuaikan apa yang sebenarnya disalin.

Jadi, untuk menjawab pertanyaan OP:

mysqldbcopy \
    --source=root:your_password@localhost \
    --destination=root:your_password@localhost \
    sitedb1:sitedb2

1
Ini bekerja dengan baik untuk saya, mysqldumpsolusi berbasis gagal.
saji89

1
Dalam kasus saya, saya harus menentukan port seperti ini: --source = root: your_password @ localhost: 3307 (kalau tidak itu akan memberi saya kesalahan akses ditolak)
pbz

4
Perlu sudo apt-get install mysql-utilities, tapi ini sangat rapi. Bisakah saya meninggalkan kata sandi dan diminta untuk memasukkannya?
ADTC

2
@ADTC Saya tidak tahu apakah ada cara untuk membiarkan mysqldbcopyAnda meminta kata sandi; setidaknya saya tidak dapat menemukan hal seperti itu di dokumentasi. Anda dapat membangun fungsionalitas ini sendiri. Dalam Bash yang bisa terlihat seperti ini:mysqldbcopy --source=root:"$(read -sp 'Source password: ' && echo $REPLY)"@localhost --destination=root:"$(read -sp 'Destination password: ' && echo $REPLY)"@localhost sitedb1:sitedb2
Chriki

1
FYI: Sepertinya perintah Chriki bekerja dengan sempurna. Aku hanya harus menambahkan --forceke mysqldbcopyperintah karena saya sudah menciptakan database tujuan. Terima kasih!
Niavlys

19
mysqladmin create DB_name -u DB_user --password=DB_pass && \
        mysqldump -u DB_user --password=DB_pass DB_name | \
        mysql     -u DB_user --password=DB_pass -h DB_host DB_name

2
Apa yang ditambahkan ke jawaban yang diterima? Mirip, tetapi Anda menambahkan beberapa perbedaan, menambahkan beberapa komentar untuk pemahaman yang lebih baik
Yaroslav

Ini harus menjadi jawaban yang diterima, karena akan membuat database, juga bagus untuk auth. jawaban yang diterima saat ini akan memberi tahu Anda akses ditolak, maka tabel tidak ada.
Rami Dabain

14

Anda perlu menjalankan perintah dari terminal / command prompt.

mysqldump -u <user name> -p <pwd> <original db> | mysql -u <user name> <pwd> <new db>

misalnya: mysqldump -u root test_db1 | mysql -u root test_db2

Ini menyalin test_db1 ke test_db2 dan memberikan akses ke 'root' @ 'localhost'


Saya suka jawaban ini, renyah. Namun, bagi saya diperlukan mysql -p sebelum kata sandi.
lwitzel

1
Bagaimana kita juga bisa menyalin fungsi, acara, dll. Yang dibuat dalam database asli? Ini terlihat hanya salinan tabel.
Dogan Askan

12

Cara terbaik dan mudah adalah dengan memasukkan perintah-perintah ini di terminal Anda dan mengatur izin untuk pengguna root. Bekerja untukku ..!

:~$> mysqldump -u root -p db1 > dump.sql
:~$> mysqladmin -u root -p create db2
:~$> mysql -u root -p db2 < dump.sql

1
Pertanyaan secara eksplisit menyatakan bahwa metode ekspor / impor sudah diketahui.
lav

3
Ini adalah cara terbaik untuk melakukannya. Juga berfungsi dengan basis data besar, sedangkan versi pipa mysqldump -u <user> -p <pwd> db_name | mysql -u <user> -p <pwd> new_db_namebisa bermasalah dengan basis data besar.
Alex

10

Anda bisa menggunakan (dalam kodesemu):

FOREACH tbl IN db_a:
    CREATE TABLE db_b.tbl LIKE db_a.tbl;
    INSERT INTO db_b.tbl SELECT * FROM db_a.tbl;

Alasan saya tidak menggunakan sintaks CREATE TABLE ... SELECT ... adalah untuk menjaga indeks. Tentu saja ini hanya menyalin tabel. Tampilan dan prosedur tidak disalin, meskipun dapat dilakukan dengan cara yang sama.

Lihat CREATE TABLE .


3
Ini bisa gagal pada integritas referensi karena tabel dependen belum dapat disalin. Mungkin itu bisa bekerja dalam satu transaksi besar.
Ondrej Galbavý

4

Pertama buat duplikat basis data:

CREATE DATABASE duplicateddb;

Pastikan semua izin sudah ada dan:

mysqldump -u admin -p originaldb | mysql -u backup -p password duplicateddb;

2

Anda dapat melakukan sesuatu seperti berikut ini:

mysqldump -u[username] -p[password] database_name_for_clone 
 | mysql -u[username] -p[password] new_database_name

1

Pernyataan ini ditambahkan di MySQL 5.1.7 tetapi ternyata berbahaya dan dihapus di MySQL 5.1.23. Itu dimaksudkan untuk mengaktifkan pemutakhiran basis data pra-5.1 untuk menggunakan pengkodean yang diterapkan pada 5.1 untuk memetakan nama basis data ke nama direktori basis data. Namun, penggunaan pernyataan ini dapat mengakibatkan hilangnya konten basis data, itulah mengapa ia dihapus. Jangan gunakan RENAME DATABASE di versi sebelumnya yang hadir.

Untuk melakukan tugas memutakhirkan nama database dengan pengkodean baru, gunakan ALTER DATABASE db_name sebagai ganti NAMA DIRECTORY DATA UPGRADE: Sebagai gantinya: http://dev.mysql.com/doc/refman/5.1/en/alter-database.html


1

Cara mudah untuk melakukannya jika Anda menginstal phpmyadmin :

Buka database Anda, pilih tab "operasi", dan Anda dapat melihat blok "salin database ke". Gunakan itu dan Anda dapat menyalin basis data.


1

Seperti yang disebutkan dalam jawaban Greg , mysqldump db_name | mysql new_db_nameadalah 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

  1. Dapatkan sudohak istimewa dan verifikasi Anda memiliki ruang penyimpanan yang cukup untuk mengkloning basis data
  2. Dapatkan root mysql privilege
  3. Membuat database baru yang dinamai cabang git saat ini
  4. Struktur klon ke database baru
  5. Beralih ke mode pemulihan untuk innodb
  6. Menghapus data default di database baru
  7. Berhenti mysql
  8. Mengkloning data ke basis data baru
  9. Mulai mysql
  10. Tautan data yang diimpor dalam database baru
  11. Beralih dari mode pemulihan untuk innodb
  12. Mulai ulang mysql
  13. Memberikan akses pengguna mysql ke database
  14. Membersihkan file sementara

Bagaimana membandingkannya dengan mysqldump

Pada basis data 3gb, menggunakan mysqldumpdan mysqlakan 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 mysqldumpmenduplikasi 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 mysqldumpatau 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 masteruntuk 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 masteruntuk 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:

Cuplikan layar output skrip misalnya database


0

Selain jawaban Greg , ini adalah cara termudah dan tercepat jika new_db_namebelum ada:

echo "create database new_db_name" | mysql -u <user> -p <pwd> 
mysqldump -u <user> -p <pwd> db_name | mysql -u <user> -p <pwd> new_db_name

0

Jika Anda memiliki pemicu dalam database asli Anda, Anda dapat menghindari kesalahan "Pemicu sudah ada" dengan memiparkan pengganti sebelum mengimpor:

mysqldump -u olddbuser -p -d olddbname | sed "s/`olddbname`./`newdbname`./" | mysql -u newdbuser -p -D newdbname

-4

Saya tidak berpikir ada metode untuk melakukan ini. Ketika PHPMyAdmin melakukan ini, ia membuang DB lalu memasukkannya kembali dengan nama baru.

Dengan menggunakan situs kami, Anda mengakui telah membaca dan memahami Kebijakan Cookie dan Kebijakan Privasi kami.
Licensed under cc by-sa 3.0 with attribution required.