Импорт структуры таблиц и данных из MySQL
Введение
Перенос схемы и данных из MySQL в YDB — в том числе больших таблиц, объём которых превышает доступную оперативную память, — выполняется утилитой mysql2ydb.
Цель утилиты — получить копию исходной базы данных один к одному. Имена таблиц и колонок сохраняются как в MySQL; типы данных сопоставляются с ближайшими эквивалентами YDB (INT → Int32, VARCHAR → Text, AUTO_INCREMENT → BigSerial и т. д.). Поскольку имена переносятся без изменений, они должны быть допустимыми идентификаторами в YDB (символы, длина, зарезервированные слова). В результате должна получиться база YDB, повторяющая исходную структуру и позволяющая выполнять запросы с минимальным «переводом» с MySQL.
Каждый фрагмент данных читается из MySQL одним ограниченным запросом SELECT, записывается в YDB порцией (BulkUpsert или транзакционный UPSERT), после чего освобождается из памяти. Процесс повторяется, пока не будет обработана вся таблица — в памяти одновременно хранится не больше одной порции (при конвейерном чтении — не больше двух).
Импорт выполняется в следующем порядке:
- Утилита подключается к MySQL и по метаданным
information_schemaопределяет состав таблиц и их структуру. - В YDB создаются целевые таблицы с сопоставленными типами данных.
- Данные переносятся порциями: чтение из MySQL и запись в YDB.
- После успешной записи каждой порции в служебную таблицу состояния сохраняется позиция курсора — при сбое миграцию можно продолжить с последнего успешного фрагмента.
Соответствие схемы 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 и ограничивает объём каждого запроса:
- Постраничное чтение по первичному ключу —
WHERE (pk…) > (?) ORDER BY pk LIMIT n. Без полного сканирования таблицы в память. - Конвейер чтение → запись — одна горутина читает следующую порцию из MySQL, пока другая записывает предыдущую в YDB. На таблицу одновременно в полёте не более двух порций.
- Адаптивный размер порции — число строк ограничивается доступной RAM и средним размером строки из
information_schema. - Контрольная точка прогресса — после каждой успешно записанной порции позиция курсора и счётчик строк сохраняются в служебной таблице YDB; перезапуск продолжает с последней порции.
MySQL: SELECT порция N → BulkUpsert(порция N) → YDB
MySQL: SELECT порция N+1 → BulkUpsert(порция N+1) → YDB
...
Возможности
- Копия схемы один к одному — исходные имена таблиц и колонок; сопоставление типов MySQL с ближайшими типами YDB (
AUTO_INCREMENT→BigSerial,UNSIGNED→Uint32/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 |
TINYINT → Int32 (BOOLEAN → Bool) |
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-таблицей.