Импорт структуры таблиц и данных из MySQL

Введение

Перенос схемы и данных из MySQL в YDB — в том числе больших таблиц, объём которых превышает доступную оперативную память, — выполняется утилитой mysql2ydb.

Цель утилиты — получить копию исходной базы данных один к одному. Имена таблиц и колонок сохраняются как в MySQL; типы данных сопоставляются с ближайшими эквивалентами YDB (INTInt32, VARCHARText, AUTO_INCREMENTBigSerial и т. д.). Поскольку имена переносятся без изменений, они должны быть допустимыми идентификаторами в YDB (символы, длина, зарезервированные слова). В результате должна получиться база YDB, повторяющая исходную структуру и позволяющая выполнять запросы с минимальным «переводом» с MySQL.

Каждый фрагмент данных читается из MySQL одним ограниченным запросом SELECT, записывается в YDB порцией (BulkUpsert или транзакционный UPSERT), после чего освобождается из памяти. Процесс повторяется, пока не будет обработана вся таблица — в памяти одновременно хранится не больше одной порции (при конвейерном чтении — не больше двух).

Импорт выполняется в следующем порядке:

  1. Утилита подключается к MySQL и по метаданным information_schema определяет состав таблиц и их структуру.
  2. В YDB создаются целевые таблицы с сопоставленными типами данных.
  3. Данные переносятся порциями: чтение из MySQL и запись в YDB.
  4. После успешной записи каждой порции в служебную таблицу состояния сохраняется позиция курсора — при сбое миграцию можно продолжить с последнего успешного фрагмента.

Соответствие схемы MySQL

При переносе схемы mysql2ydb стремится к структурному соответствию: те же имена таблиц и колонок, индексы там, где YDB их поддерживает, типы, при которых значения сохраняются без потерь (беззнаковые целые остаются беззнаковыми, логические типы — логическими и т. д.).

Утилита читает метаданные MySQL из information_schema и формирует DDL YDB напрямую. Основные сопоставления типов:

Тип MySQL Тип в YDB
INT, MEDIUMINT, SMALLINT, TINYINT Int32
BIGINT Int64
INT UNSIGNED, MEDIUMINT UNSIGNED, SMALLINT UNSIGNED, TINYINT UNSIGNED Uint32
BIGINT UNSIGNED Uint64
TINYINT(1) Bool
FLOAT Float
DOUBLE, REAL Double
BIT Uint64 (в том числе BIT(1) — оборачивается в Uint64, не в Bool)
CHAR, VARCHAR, TEXT, MEDIUMTEXT, LONGTEXT Text
ENUM, SET, JSON Text
BINARY, VARBINARY, BLOB, MEDIUMBLOB, LONGBLOB String (байты inline в той же колонке)
DATE Date
DATETIME, TIMESTAMP Timestamp — значение сохраняется как UTC. У DATETIME (тип без часового пояса) трактовка зависит от time_zone MySQL во время чтения; учитывайте это при миграции исторических данных.
DECIMAL(p, s), NUMERIC(p, s) Decimal(22, 9) для всех — исходные p/s не сохраняются; значения, не помещающиеся в 22 цифры, будут потеряны
YEAR Uint16
Прочие типы Text (запасной вариант)

Структурные решения

Элемент схемы MySQL Решение mysql2ydb
Имена таблиц и колонок Как в MySQL (users, orders, …); должны быть допустимыми идентификаторами YDB
AUTO_INCREMENT BigSerial + ALTER SEQUENCE … START WITH из TABLES.AUTO_INCREMENT
Вторичные KEY / UNIQUE KEY INDEX … GLOBAL ASYNC / GLOBAL UNIQUE SYNC в CREATE TABLE
Таблица без PRIMARY KEY Не поддерживается при автоматическом создании схемы — см. Таблицы без первичного ключа
Партиционирование Только AUTO_PARTITIONING_BY_LOAD

Вторичные индексы

Неуникальные ключи MySQL становятся INDEX … GLOBAL ASYNC, уникальные (кроме первичного) — INDEX … GLOBAL UNIQUE SYNC. Таблицы с синхронными уникальными индексами при загрузке данных автоматически переключаются на транзакционный UPSERT, поскольку BulkUpsert поддерживает только асинхронные индексы.

Примеры

AUTO_INCREMENT — колонка MySQL id BIGINT AUTO_INCREMENT превращается в:

`id` BigSerial NOT NULL,
PRIMARY KEY (`id`)

После CREATE TABLE, если известно значение information_schema.TABLES.AUTO_INCREMENT:

ALTER SEQUENCE `<db>/<table>/_serial_column_id` START WITH <next_value> RESTART

Таблицы без первичного ключа

В YDB у каждой таблицы должен быть PRIMARY KEY. mysql2ydb берёт ключ только из колонок MySQL с COLUMN_KEY = 'PRI'; индексы UNIQUE KEY становятся вторичными GLOBAL UNIQUE SYNC, но не подставляются вместо первичного ключа. Если у таблицы нет PRIMARY KEY, создание схемы завершается ошибкой (PRIMARY KEY () — недопустимый DDL), и перенос данных не начинается.

Перед миграцией для таких таблиц нужно задать ключ в MySQL, например:

ALTER TABLE my_table ADD COLUMN id BIGINT AUTO_INCREMENT PRIMARY KEY FIRST;

или назначить PRIMARY KEY на существующую колонку (или набор колонок), если он действительно уникален.

Если добавить PRIMARY KEY в MySQL нельзя, но схему в YDB можно подготовить вручную, создайте таблицу с нужным ключом сами и запустите утилиту с флагом -data-only. Чтение из MySQL при отсутствии PRIMARY KEY в метаданных пойдёт через LIMIT/OFFSET: на каждой странице MySQL заново сканирует все предыдущие строки, поэтому время миграции растёт квадратично от размера таблицы — для больших таблиц лучше предварительно добавить ключ в MySQL. Ключевые колонки в YDB должны присутствовать среди колонок MySQL.

Если нужен автоматический импорт без ключа в MySQL, рассмотрите YDB Importer — он добавляет синтетическую колонку ydb_synth_key (SHA-256 по строке). mysql2ydb синтетические ключи не создаёт.

Эффективный доступ к MySQL

Утилита минимизирует обращения к MySQL и ограничивает объём каждого запроса:

  1. Постраничное чтение по первичному ключуWHERE (pk…) > (?) ORDER BY pk LIMIT n. Без полного сканирования таблицы в память.
  2. Конвейер чтение → запись — одна горутина читает следующую порцию из MySQL, пока другая записывает предыдущую в YDB. На таблицу одновременно в полёте не более двух порций.
  3. Адаптивный размер порции — число строк ограничивается доступной RAM и средним размером строки из information_schema.
  4. Контрольная точка прогресса — после каждой успешно записанной порции позиция курсора и счётчик строк сохраняются в служебной таблице YDB; перезапуск продолжает с последней порции.
MySQL: SELECT порция N   →  BulkUpsert(порция N)   →  YDB
MySQL: SELECT порция N+1 →  BulkUpsert(порция N+1) →  YDB
...

Возможности

  • Копия схемы один к одному — исходные имена таблиц и колонок; сопоставление типов MySQL с ближайшими типами YDB (AUTO_INCREMENTBigSerial, UNSIGNEDUint32/Uint64, вторичные индексы, TINYINT(1)Bool; см. раздел Соответствие схемы MySQL).
  • Создание схемы — таблицы в YDB создаются по метаданным MySQL (information_schema).
  • Пофрагментная миграция данных — таблица обрабатывается порциями фиксированного размера; объём данных может превышать доступную RAM.
  • Перезапись по ключу — данные записываются через BulkUpsert или транзакционный UPSERT; повторный запуск не создаёт дубликаты строк.
  • Возобновление — прогресс по каждой таблице хранится в YDB; прерванную миграцию можно продолжить с последней порции.

Установка

Исходный код и инструкции по сборке — в репозитории mysql-ydb-importer на GitHub.

Для сборки требуется Go версии не ниже указанной в go.mod репозитория:

git clone https://github.com/ydb-platform/mysql-ydb-importer.git
cd mysql-ydb-importer
go build -o mysql2ydb ./cmd/mysql2ydb

Порядок использования

Параметры подключения к MySQL по умолчанию читаются из файла ~/.my.cnf (как у клиента mysql), секция [client]. Флаг -mysql переопределяет конфигурационный файл.

Таблицы без первичного ключа

Таблицы без PRIMARY KEY в MySQL не поддерживаются при автоматическом создании схемы. Перед запуском добавьте первичный ключ в MySQL или подготовьте схему вручную и используйте -data-only (см. раздел Таблицы без первичного ключа).

Пример ~/.my.cnf:

[client]
user = myuser
password = mypass
host = localhost
port = 3306
database = mydb

Если сервер требует защищённое соединение (require_secure_transport=ON), добавьте в [client]:

ssl-mode = REQUIRED

Для самоподписанного сертификата (без проверки):

ssl-mode = REQUIRED
ssl-verify = 0

Альтернативный вариант — ssl=1 и ssl-verify=0.

Минимальный запуск при наличии ~/.my.cnf (обязателен только -ydb):

./mysql2ydb -ydb "grpc://localhost:2136"

С явным DSN MySQL (игнорирует ~/.my.cnf):

./mysql2ydb -mysql "user:$MYSQL_PASSWORD@tcp(localhost:3306)/mydb" -ydb "grpc://localhost:2136" -batch-size 10000

Флаги командной строки

Флаг Описание
-mysql DSN MySQL (если задан — переопределяет ~/.my.cnf)
-ydb Конечная точка YDB (обязательный)
-ydb-database Путь к базе данных YDB (по умолчанию local)
-schema-only Только создать схему, без переноса данных
-data-only Только перенести данные (схема в YDB должна уже существовать — в том числе созданная вручную)
-batch-size Целевой размер порции в строках (по умолчанию 10 000)
-max-chunk-rows Жёсткий верхний предел строк в порции (по умолчанию 25 000)
-parallel-tables Число таблиц для параллельного переноса (по умолчанию 1)
-tables Список таблиц через запятую (по умолчанию — все)
-force Перенести все таблицы заново, игнорируя сохранённое состояние
-force-recreate Удалить все таблицы в YDB и пересоздать схему с нуля

Примеры

Только схема:

./mysql2ydb -mysql "..." -ydb "grpc://localhost:2136" -schema-only

Только данные (после создания схемы):

./mysql2ydb -mysql "..." -ydb "grpc://localhost:2136" -data-only -batch-size 5000

Выбранные таблицы:

./mysql2ydb -mysql "..." -ydb "grpc://localhost:2136" -tables "users,orders"

Пофрагментное чтение

  • Для таблиц с первичным ключом в MySQL используется постраничное чтение по курсору: WHERE (pk > ?) ORDER BY pk LIMIT batch_size. В памяти одновременно находится не больше одной порции.
  • Если в MySQL нет PRIMARY KEY, но схема в YDB уже создана вручную (режим -data-only), применяется LIMIT batch_size OFFSET offset — см. раздел Таблицы без первичного ключа.

Запись в YDB

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

Запросы к YDB помечены как идемпотентные, поэтому SDK автоматически повторяет их при сетевых сбоях. Отсутствие дубликатов при перезапуске миграции обеспечивается перезаписью строк по ключу и сохранением прогресса в служебной таблице (см. раздел Возможности, пункт «Возобновление»).

Сравнение с YDB Importer

Для загрузки данных в YDB есть и другие инструменты. YDB Importer — универсальный JDBC-импортёр для PostgreSQL, Oracle, SQL Server и других источников: XML-конфигурация, параллельный импорт, настройка партиционирования.

mysql2ydb решает более узкую задачу — перенос базы MySQL «один к одному» одним бинарным файлом, без JVM. Для миграции только из MySQL с большими таблицами достаточно ~/.my.cnf и флага -ydb; прогресс сохраняется в служебной таблице YDB.

YDB Importer mysql2ydb
Среда выполнения Java + JDBC-драйверы + XML-конфигурация Один бинарный файл на Go, нативный протокол MySQL
Область применения Множество JDBC-источников Только MySQL
Размер порции по умолчанию 1 000 строк (max-batch-rows) 10 000 строк (-batch-size), с автонастройкой
Большие таблицы Параллельные диапазоны, буферы партиций Постраничное чтение по курсору, размер порции с учётом RAM
Возобновление после сбоя Повторный запуск импорта Контрольная точка на каждую порцию в служебной таблице YDB
Настройка JAR-файлы JDBC, XML-конфигурация ~/.my.cnf и два флага

YDB Importer рассчитан на импорт из многих JDBC-источников в настраиваемую структуру YDB (table-name-format, blob-name-format, режимы преобразования дат, опциональные синтетические ключи). Ниже — практические отличия при импорте одной и той же базы MySQL двумя инструментами:

Возможность MySQL mysql2ydb (копия один к одному) ydb-importer
Имена таблиц Как в MySQL (users, orders, …) Переименование по шаблону, например mysql1/${schema}/${table}mysql1/mydb/users (пример конфигурации)
Имена колонок Как в MySQL Санитизация: пробелы, ., /, `_
AUTO_INCREMENT BigSerial + ALTER SEQUENCE … START WITH из TABLES.AUTO_INCREMENT Обычный Int32/Int64; без BigSerial и сброса последовательности
INT UNSIGNED, BIGINT UNSIGNED, … Uint32 / Uint64 JDBC сопоставляет целые со знаковыми Int32/Int64
TINYINT(1) Bool TINYINTInt32 (BOOLEANBool)
BIT Uint64 Bool (JDBC Types.BIT)
Вторичные KEY / UNIQUE KEY INDEX … GLOBAL ASYNC / GLOBAL UNIQUE SYNC в CREATE TABLE В DDL только PRIMARY KEY; вторичные индексы не создаются
ENUM, SET, JSON Text Text (аналогично)
BLOB / BINARY Та же колонка, значение inline в String (байты) Колонка становится Int64 (идентификатор blob); данные выносятся в отдельную таблицу ${schema}/${table}_${field} со строками (id, pos, val) блоками по 64 КБ
TEXT / CLOB (большой текст) Inline Text в основной таблице Опционально отдельная CLOB-таблица (блоки по 32 К символов) или inline Text в зависимости от настроек
Таблица без первичного ключа Не поддерживаетсяCREATE TABLE завершается ошибкой; добавьте PRIMARY KEY в MySQL перед миграцией Добавляется колонка ydb_synth_key Text как PK (SHA-256 по строке); дубликаты схлопываются в одну строку
DATE Date Date32 по умолчанию (conv-date=DATE_NEW)
DATETIME / TIMESTAMP Timestamp Datetime64 / Timestamp64 по умолчанию
TIME Text (запасной вариант) Int32 (секунды с полуночи)
DECIMAL(p,s) Decimal(22, 9) для всех Decimal(p,s) при allow-custom-decimal=true
YEAR Uint16 Не покрыто в тестах типов MySQL
Партиционирование Только AUTO_PARTITIONING_BY_LOAD PARTITION_AT_KEYS, разбиение по партициям источника, HASH для column store и др.

Работа с BLOB

Таблица MySQL attachments (id INT, data MEDIUMBLOB):

Колонка data в основной таблице Где лежат байты
mysql2ydb data String В той же строке
ydb-importer data Int64 (ссылка) Отдельная таблица …/attachments_data с порционными строками

С mysql2ydb можно выполнить SELECT data FROM attachments WHERE id = 1 так же, как в MySQL. С ydb-importer потребуется соединение основной таблицы с дополнительной blob-таблицей.

Следующая