Клонирование базы данных MySQL в том же экземпляре MySql


163

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

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

а затем импортируйте его в sitedb2. Есть ли более простой способ без сброса первой базы данных в файл sql?


Возможный дубликат базы данных Clone MySQL
bummi

Ответы:


331

Как говорится в руководстве в разделе « Копирование баз данных», вы можете передать дамп прямо в клиент 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
Если ваша база данных имеет размер в гигабайты, это, вероятно, не принесет вам многого. Я думаю, что к чему приходит OP, так это то, что они не хотят экстернализировать копию: можно ли это сделать исключительно в mysql?
cletus

3
Я бы сказал, что чем больше БД, тем больше она приносит вам ... В MySQL afaik это невозможно сделать (кроме как вручную, по одной таблице / представлению за раз)
Грег

44
Сначала мне пришлось создать 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. Мне предлагается ввести оба пароля, но я могу ввести только один. Контекстное выглядит следующим образом : Enter password: Enter password: . После выдачи первого pw процесс ждет вечно.
Торстен

71

Использование утилит MySQL

Утилиты MySQL содержат прекрасный инструмент, mysqldbcopyкоторый по умолчанию копирует БД, включая все связанные объекты («таблицы, представления, триггеры, события, процедуры, функции и гранты уровня базы данных») и данные с одного сервера БД на тот же или на другой. Сервер БД. Существует множество опций для настройки того, что фактически копируется.

Итак, чтобы ответить на вопрос OP:

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

1
Это сработало для меня, mysqldumpрешение на основе не удалось.
saji89 08

1
В моем случае мне пришлось указать порт следующим образом: --source = root: your_password @ localhost: 3307 (иначе я получил бы ошибку отказа в доступе)
pbz

5
Надо sudo apt-get install mysql-utilities, но это очень аккуратно. Могу ли я не указывать пароль, и мне будет предложено его ввести?
ADTC

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

1
К вашему сведению: Похоже, команда Чрики работает безупречно. Мне просто нужно было добавить --forceв mysqldbcopyкоманду, потому что я уже создал целевую базу данных. Благодарность!
Niavlys 06

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

3
Что это добавляет к принятому ответу? Аналогично, но вы добавили отличия, добавили комментарии для лучшего понимания
Ярослав

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

15

Лучший и простой способ - ввести эти команды в свой терминал и установить разрешения для пользователя root. Работает для меня..!

:~$> 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может вызывать проблемы с большими базами данных.
Alex

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
Как мы можем также скопировать функции, события и т. Д., Созданные в исходной базе данных? Это выглядит только копирует таблицы.
Доган Аскан

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 ..., заключается в сохранении индексов. Конечно, это копирует только таблицы. Представления и процедуры не копируются, хотя это можно сделать таким же образом.

См. СОЗДАТЬ ТАБЛИЦУ .


4
Это могло произойти из-за целостности ссылок, так как зависимые таблицы еще не могли быть скопированы. Возможно, это сработает в одной большой транзакции.
Ондрей Галбавы

5

Сначала создайте дубликат базы данных:

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/en/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. Перезапускает mysql
  13. Предоставляет пользователю mysql доступ к базе данных
  14. Очищает временные файлы

Как это сравнить с 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

# 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

-5

Я не думаю, что есть способ сделать это. Когда PHPMyAdmin делает это, он выгружает БД, а затем повторно вставляет ее под новым именем.

Используя наш сайт, вы подтверждаете, что прочитали и поняли нашу Политику в отношении файлов cookie и Политику конфиденциальности.
Licensed under cc by-sa 3.0 with attribution required.