Миграции базы данных

Миграции — это версионируемые SQL-скрипты (или JS-файлы), описывающие изменения схемы базы данных, которые можно применить (up) или откатить (down) в определённом порядке.

Зачем нужно

Без миграций изменения схемы БД применяются вручную на каждом окружении (dev, staging, production), что приводит к рассинхронизации схем. Миграции хранятся в git рядом с кодом: каждое изменение схемы атомарно связано с изменением кода, легко воспроизводится командой и деплоится автоматически в CI/CD.

Где используется

  • Любой проект с реляционной БД (PostgreSQL, MySQL, SQLite)
  • CI/CD — автоматическое применение миграций перед деплоем
  • Онбординг нового разработчика — npm run migrate поднимает актуальную схему
  • Rollback при проблемном деплое

Основной контент

Prisma Migrate (рекомендуется с Prisma ORM)

# Создать и применить миграцию (dev)
npx prisma migrate dev --name add_user_table

# Применить все pending миграции (production)
npx prisma migrate deploy

# Посмотреть статус миграций
npx prisma migrate status

# Откатить последнюю (не поддерживается напрямую — нужен ручной SQL)
prisma/
  migrations/
    20240101000000_init/
      migration.sql
    20240115000000_add_user_table/
      migration.sql
  schema.prisma

Knex.js Migrations (без ORM)

npm install knex
npx knex init          # создать knexfile.js
npx knex migrate:make create_users_table
// knexfile.js
module.exports = {
  development: {
    client: 'postgresql',
    connection: process.env.DATABASE_URL,
    migrations: { directory: './db/migrations' },
    seeds: { directory: './db/seeds' }
  },
  production: {
    client: 'postgresql',
    connection: process.env.DATABASE_URL,
    migrations: { directory: './db/migrations' }
  }
};
// db/migrations/20240101_create_users.js
exports.up = async (knex) => {
  await knex.schema.createTable('users', (table) => {
    table.increments('id').primary;
    table.string('name', 100).notNullable;
    table.string('email', 255).notNullable.unique;
    table.string('password_hash', 255).notNullable;
    table.enum('role', ['user', 'admin']).defaultTo('user');
    table.timestamps(true, true); // created_at, updated_at
  });
};

exports.down = async (knex) => {
  await knex.schema.dropTableIfExists('users');
};

// Добавление колонки в существующую таблицу
// db/migrations/20240115_add_avatar_to_users.js
exports.up = async (knex) => {
  await knex.schema.alterTable('users', (table) => {
    table.string('avatar_url').nullable;
    table.index(['email']); // добавить индекс
  });
};

exports.down = async (knex) => {
  await knex.schema.alterTable('users', (table) => {
    table.dropColumn('avatar_url');
    table.dropIndex(['email']);
  });
};
# Применить все pending миграции
npx knex migrate:latest

# Откатить последний batch
npx knex migrate:rollback

# Статус миграций
npx knex migrate:status

Seeds — заполнение тестовыми данными

// db/seeds/01_users.js
exports.seed = async (knex) => {
  await knex('users').del; // очистить таблицу
  await knex('users').insert([
    { name: 'Admin', email: 'admin@example.com', role: 'admin', password_hash: '...' },
    { name: 'Alice', email: 'alice@example.com', role: 'user', password_hash: '...' }
  ]);
};
npx knex seed:run

npm scripts для CI/CD

{
  "scripts": {
    "migrate": "npx knex migrate:latest",
    "migrate:rollback": "npx knex migrate:rollback",
    "seed": "npx knex seed:run",
    "db:reset": "npx knex migrate:rollback --all && npx knex migrate:latest && npx knex seed:run"
  }
}

Частые ошибки

  • Редактировать применённые миграции — если миграция уже применена на production, менять её нельзя; создавать новую
  • Не хранить rollback — функция down обязательна для возможности отката
  • Destructive миграции без backupDROP TABLE, DROP COLUMN удаляют данные необратимо; делать backup перед применением
  • Нет транзакции — миграцию нужно выполнять в транзакции: если она упадёт на половине, БД останется в неконсистентном состоянии

Связанные темы

Ресурсы