---
metadata:
  - name: generator
    content: Diplodoc Platform v5.52.0
alternate:
  - https://ydb.tech/docs/en/integrations/data-migration/import-mysql.md
  - https://ydb.tech/docs/ru/integrations/data-migration/import-mysql.md
  - href: ru/integrations/data-migration/import-mysql.md
    type: text/markdown
    title: Markdown version
  - href: ../../llms.txt
    type: text/markdown
    title: llms.txt
sourcePath: ru/core/integrations/data-migration/import-mysql.md
---
> **Documentation Index:** Fetch the complete configuration index at https://ydb.tech/docs/ru/llms.txt

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

## Введение {#intro}

Перенос схемы и данных из **MySQL** в YDB — в том числе **больших таблиц**, объём которых превышает доступную оперативную память, — выполняется [утилитой mysql2ydb](https://github.com/ydb-platform/mysql-ydb-importer).

**Цель утилиты — получить копию исходной базы данных один к одному.** Имена таблиц и колонок сохраняются как в MySQL; типы данных сопоставляются с ближайшими эквивалентами YDB (`INT` → `Int32`, `VARCHAR` → `Text`, `AUTO_INCREMENT` → `BigSerial` и т. д.). Поскольку имена переносятся без изменений, они должны быть допустимыми идентификаторами в YDB (символы, длина, зарезервированные слова). В результате должна получиться база YDB, повторяющая исходную структуру и позволяющая выполнять запросы с минимальным «переводом» с MySQL.

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

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

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

## Соответствие схемы MySQL {#schema-fidelity}

При переносе схемы 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` (запасной вариант) |

### Структурные решения {#schema-structure}

| Элемент схемы 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` | Не поддерживается при автоматическом создании схемы — см. [Таблицы без первичного ключа](#tables-without-pk) |
| Партиционирование | Только `AUTO_PARTITIONING_BY_LOAD` |

### Вторичные индексы {#secondary-indexes}

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

### Примеры {#schema-examples}

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

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

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

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

### Таблицы без первичного ключа {#tables-without-pk}

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

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

```sql
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-importer-comparison) — он добавляет синтетическую колонку `ydb_synth_key` (SHA-256 по строке). mysql2ydb синтетические ключи не создаёт.

## Эффективный доступ к MySQL {#efficient-access}

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

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

```text
MySQL: SELECT порция N   →  BulkUpsert(порция N)   →  YDB
MySQL: SELECT порция N+1 →  BulkUpsert(порция N+1) →  YDB
...
```

## Возможности {#features}

- **Копия схемы один к одному** — исходные имена таблиц и колонок; сопоставление типов MySQL с ближайшими типами YDB (`AUTO_INCREMENT` → `BigSerial`, `UNSIGNED` → `Uint32`/`Uint64`, вторичные индексы, `TINYINT(1)` → `Bool`; см. раздел [Соответствие схемы MySQL](#schema-fidelity)).
- **Создание схемы** — таблицы в YDB создаются по метаданным MySQL (`information_schema`).
- **Пофрагментная миграция данных** — таблица обрабатывается порциями фиксированного размера; объём данных может превышать доступную RAM.
- **Перезапись по ключу** — данные записываются через `BulkUpsert` или транзакционный `UPSERT`; повторный запуск не создаёт дубликаты строк.
- **Возобновление** — прогресс по каждой таблице хранится в YDB; прерванную миграцию можно продолжить с последней порции.

## Установка {#install}

Исходный код и инструкции по сборке — в [репозитории mysql-ydb-importer](https://github.com/ydb-platform/mysql-ydb-importer) на GitHub.

Для сборки требуется [Go](https://go.dev/) версии не ниже указанной в `go.mod` репозитория:

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

## Порядок использования {#use}

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

{% note warning "Таблицы без первичного ключа" %}

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

{% endnote %}

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

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

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

```ini
ssl-mode = REQUIRED
```

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

```ini
ssl-mode = REQUIRED
ssl-verify = 0
```

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

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

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

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

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

### Флаги командной строки {#flags}

| Флаг | Описание |
|------|----------|
| `-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 и пересоздать схему с нуля |

### Примеры {#examples}

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

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

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

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

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

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

## Пофрагментное чтение {#chunked-reading}

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

## Запись в YDB {#bulkupsert}

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

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

## Сравнение с YDB Importer {#ydb-importer-comparison}

Для загрузки данных в YDB есть и другие инструменты. [YDB Importer](https://ydb.tech/docs/ru/integrations/data-migration/import-jdbc.md) — универсальный JDBC-импортёр для PostgreSQL, Oracle, SQL Server и других источников: XML-конфигурация, параллельный импорт, настройка партиционирования.

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

| | [YDB Importer](import-jdbc.md) | **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` ([пример конфигурации](https://github.com/ydb-platform/ydb-importer/blob/main/scripts/sample-mysql.xml)) |
| **Имена колонок** | Как в 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 {#blob-handling}

Таблица 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-таблицей.
