Клонування бази даних MySQL в тому самому екземплярі MySql


154

Я хотів би написати сценарій, який копіює мою поточну базу даних sitedb1в sitedb2той самий екземпляр бази даних mysql. Я знаю, що можу скинути sitedb1 на sql-скрипт:

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

а потім імпортувати в sitedb2. Чи є простіший спосіб, не скидаючи першу базу даних у файл sql?


Можливий дублікат бази даних Clone MySQL
bummi

Відповіді:


303

Як зазначено в посібнику в Копіюванні баз даних, ви можете передати дамп безпосередньо клієнту mysql:

mysqldump db_name | mysql new_db_name

Якщо ви використовуєте MyISAM, ви можете скопіювати файли, але я б не рекомендував його. Це трохи хитро.

Інтегрований з різних хороших інших відповідей

І команди, mysqldumpі mysqlкоманди приймають параметри встановлення деталей про з'єднання (та багато іншого), наприклад:

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

Крім того, якщо нова база даних ще не існує, її потрібно створити заздалегідь (наприклад, за допомогою echo "create database new_db_name" | mysql -u <dbuser> -p).


2
Ніби ... це пропускає багато дискового вводу, хоча, оскільки вам не доведеться читати / записувати дані двічі
Грег,

8
Якщо ваша база даних має розмір гігабайт, це, ймовірно, не принесе вам великої кількості. Я думаю, у кого працює ОП, вони не хочуть екстерналізувати копію: чи можна це зробити виключно в mysql?
клетус

3
Я б сказав, що чим більша БД, тим більше вона отримує від вас ... Немає способу це зробити в MySQL afaik (крім рук, одна таблиця / перегляд за один раз)
Грег,

41
Мені спочатку довелося створити new_db, використовуючи стандартну команду mysql: "CREATE DATABASE new_db;" а потім використовував ці команди: mysqldump -u root -p old_db | mysql -u root -p new_db
valentt

4
Це не працює для мене, якщо я повинен поставити в пароль для скидання і імпорту , як це: mysqldump -uroot -p database1 | mysql -uroot -p database2. Мені з’являється запит обох pws, але можна поставити лише один. Контекстне виглядає наступним чином : Enter password: Enter password: . Після надання першого pw процес чекає вічно.
Торстен

66

Використання утиліт MySQL

Утиліти MySQL містять приємний інструмент, mysqldbcopyякий за замовчуванням копіює БД, включаючи всі пов'язані об’єкти ("таблиці, представлення, тригери, події, процедури, функції та дотації рівня баз даних") та дані з одного сервера БД на той самий або на інший Сервер БД. Існує маса варіантів налаштування того, що насправді скопійовано.

Отже, щоб відповісти на питання ОП:

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

1
Для мене це нормально спрацювало, mysqldumpрішення на базі було невдалим.
saji89

1
У моєму випадку мені довелося вказати порт так: --source = root: your_password @ localhost: 3307 (інакше це дасть мені помилку в доступі)
pbz

4
Потрібно sudo apt-get install mysql-utilities, але це дуже акуратно. Чи можу я залишити пароль і попросити його ввести?
ADTC

2
@ADTC Я не знаю, чи існує вбудований спосіб, щоб дозволити mysqldbcopyвам запитати пароль; принаймні, я не зміг знайти нічого подібного в документації. Ви можете створити цю функціональність самостійно. У Баша це могло б виглядати приблизно так:mysqldbcopy --source=root:"$(read -sp 'Source password: ' && echo $REPLY)"@localhost --destination=root:"$(read -sp 'Destination password: ' && echo $REPLY)"@localhost sitedb1:sitedb2
Chriki

1
FYI: Схоже, командування Chriki працює бездоганно. Мені просто довелося додати --forceдо mysqldbcopyкоманди, оскільки я вже створив базу даних призначення. Дякую!
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
Що це додає до прийнятої відповіді? Схоже, але ви додаєте деякі відмінності, додаєте коментарі для кращого розуміння
Ярослав

Це має бути прийнятою відповіддю, оскільки це створить базу даних, також добре для автентичності. поточна прийнята відповідь скаже вам, що доступ заборонений, тоді таблиця не існує.
Рамі Дабін

14

Вам потрібно запустити команду з терміналу / командного рядка.

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

наприклад: mysqldump -u root test_db1 | mysql -u root test_db2

Це копіює test_db1 в test_db2 і надає доступ до 'root' @ 'localhost'


Мені подобається ця відповідь, вона чітка. Однак для мене mysql вимагав -p перед паролем.
lwitzel

1
Як ми також можемо копіювати функції, події тощо, створені в оригінальній базі даних? Це виглядає лише копіями таблиць.
Доган Аскан

12

Найкращий і простий спосіб - це ввести ці команди у свій термінал і встановити дозволи для кореневого користувача. Працює для мене ..!

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

1
У питанні прямо сказано, що метод експорту / імпорту вже відомий.
лав

3
Це найкращий спосіб зробити це. Також працює з великими базами даних, тоді як трубопровідна версія mysqldump -u <user> -p <pwd> db_name | mysql -u <user> -p <pwd> new_db_nameможе бути проблематичною для великих баз даних.
Олексій

10

Ви можете використовувати (у псевдокоді):

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

Причина, по якій я не використовую синтаксис CREATE TABLE ... SELECT ..., полягає в збереженні індексів. Звичайно, це лише копії таблиць. Погляди та процедури не копіюються, хоча це можна зробити так само.

Див. СТВОРИТИ ТАБЛИЦЮ .


3
Це може не вдатися до цілісності посилань, оскільки залежні таблиці ще не можна скопіювати. Можливо, це може спрацювати за одну велику транзакцію.
Ondrej Galbavý

4

Спочатку створіть копію бази даних:

CREATE DATABASE duplicateddb;

Переконайтеся, що дозволи та інші є на місці та:

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

2

Ви можете зробити щось на зразок наступного:

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

1

Це твердження було додано в MySQL 5.1.7, але було визнано небезпечним і було видалено в MySQL 5.1.23. Він мав на меті дозволити оновлення баз даних до 5.1 для використання кодування, реалізованого в 5.1 для відображення назв баз даних до імен каталогів баз даних. Однак використання цього твердження може призвести до втрати вмісту бази даних, через що він був видалений. Не використовуйте RENAME DATABASE в попередніх версіях, в яких він присутній.

Щоб виконати завдання оновлення імен баз даних за допомогою нового кодування, замість цього використовуйте ALTER DATABASE db_name UPGRADE DATA DIRECTORY NAME: http://dev.mysql.com/doc/refman/5.1/uk/alter-database.html


1

Простий спосіб зробити це, якщо ви встановили phpmyadmin :

Перейдіть у свою базу даних, виберіть вкладку "операція", і ви зможете побачити блок "копіювати базу даних". Використовуйте його, і ви можете скопіювати базу даних.


1

Як згадується у відповіді Грега , mysqldump db_name | mysql new_db_nameце вільний, безпечний та простий спосіб передачі даних між базами даних. Однак це теж дуже повільно .

Якщо ви хочете створити резервну копію даних, не можете дозволити собі втратити дані (у цій чи іншій базі даних) або використовуєте інші таблиці, крім них innodb, вам слід скористатися mysqldump.

Якщо ви шукаєте щось для розвитку, маєте резервні копії всіх своїх баз даних в іншому місці, і вам зручно чистити та перевстановлювати mysql(можливо, вручну), коли все піде не так, то я, можливо, просто знайде рішення для вас.

Я не зміг знайти гарну альтернативу, тому створив сценарій, щоб зробити це сам. Я витратив багато часу на те, щоб працювати вперше, і це, чесно кажучи, мене трохи жахає, щоб зараз змінити його. Бази даних Innodb не повинні були копіюватися та вставлятись так. Невеликі зміни призводять до того, що це не виходить чудовими способами. У мене не було проблем з моменту остаточного завершення коду, але це не означає, що ви цього не зробите.

Системи, протестовані на (але можуть все-таки вийти з ладу):

  • Ubuntu 16.04, mysql за замовчуванням, innodb, окремі файли в таблиці
  • Ubuntu 18.04, mysql за замовчуванням, innodb, окремі файли в таблиці

Що це робить

  1. Отримує sudoпривілей та підтверджує, що у вас є достатньо місця для зберігання для клонування бази даних
  2. Отримує root права mysql
  3. Створюється нова база даних, названа на честь поточної гілки git
  4. Структура клонів до нової бази даних
  5. Переходить у режим відновлення для innodb
  6. Видаляє дані за замовчуванням у новій базі даних
  7. Зупиняє mysql
  8. Клонує дані до нової бази даних
  9. Починає mysql
  10. Посилання імпортованих даних у новій базі даних
  11. Вимикається з режиму відновлення для innodb
  12. Restarts mysql
  13. Надає користувачеві mysql доступ до бази даних
  14. Очищає тимчасові файли

Як вона порівнюється з mysqldump

На базі даних 3gb, використовуючи mysqldumpта займаючи mysql40-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

Якщо все пройде гладко, ви повинні побачити щось на кшталт:

Знімок екрана виводу сценарію, наприклад, бази даних


0

Окрім відповіді Грега , це найпростіший і найшвидший спосіб, якщо його new_db_nameще не існує:

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

Якщо у початковій базі даних є тригери, ви можете уникнути помилки "Тригер вже існує", перенісши заміну перед імпортом:

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

-4

Я не думаю, що існує метод для цього. Коли PHPMyAdmin робить це, він скидає БД, а потім знову вставляє його під новою назвою.

Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.