Go Database Migration in Practice: Zero-Downtime Schema Version Control with golang-migrate

技术架构

Migration de base de données en Go : Pourquoi votre projet a besoin de contrôle de version

Exécutez-vous manuellement les modifications SQL à chaque déploiement ? Les incohérences de schéma entre les environnements provoquent-elles des incidents en production ? Vous ne savez-vous pas quelles modifications rougir lorsque les choses tournent mal ? La migration de base de données est la solution centrale à ces problèmes. En 2026, golang-migrate est devenu l'outil de migration le plus populaire dans l'écosystème Go, prenant en charge plus de 15 bases de données, y compris MySQL, PostgreSQL et SQLite. Associé à CI/CD, il permet une pipeline complète : scripts de migration → suivi de version → modifications sans temps d'arrêt → rollback automatique.

Cet article passe en revue 5 modèles clés, couvrant l'ensemble du pipeline depuis l'initialisation de la migration → les modifications incrémentielles → la stratégie de temps d'arrêt nul → le mécanisme de rollback → l'intégration CI/CD.


Concepts de base

Concept Description
Migration Unité versionnée de modification du schéma de base de données
golang-migrate CLI et bibliothèque pour les migrations de base de données en Go
Mise à niveau vers le haut Exécution de la modification du schéma en avant
Migration descendante Annulation du changement de schéma
État sale Échec de l'exécution de la migration entraînant un verrouillage de la version de la base de données
Sans interruption Stratégie de modification de schéma sans interruption de service
Données de démarrage Scripts de migration pour les données initiales
Idempotent Scripts de migration pouvant être exécutés à nouveau sans erreur

Analyse du problème : 5 défis dans la migration de bases de données

  1. Drift de schéma : Structures de base de données incohérentes entre les environnements
  2. Échec de migration : Les verrous ALTER TABLE sur de grandes tables provoquant une interruption du service
  3. Difficulté de rollback : Absence de migrations Down ou migrations Down irréversibles
  4. Conflits de collaboration : Plusieurs développeurs modifiant le schéma simultanément
  5. Intégration CI : Les scripts de migration ne sont pas inclus dans les tests et déploiements automatisés

Étapes par étapes : 5 modèles de migration de base de données essentiels

Modèle 1 : Installation et Initialisation du Projet avec golang-migrate

# macOS
brew install golang-migrate

# Linux
curl -L https://github.com/golang-migrate/migrate/releases/download/v4.18.1/migrate.linux-amd64.tar.gz | tar xvz
sudo mv migrate /usr/local/bin/

# Go install
go install -tags 'postgres mysql sqlite' github.com/golang-migrate/migrate/v4/cmd/migrate@latest
migrate create -ext sql -dir db/migrations -seq init_users_table
-- db/migrations/000001_init_users_table.up.sql
CREATE TABLE IF NOT EXISTS users (
    id BIGSERIAL PRIMARY KEY,
    username VARCHAR(255) NOT NULL UNIQUE,
    email VARCHAR(255) NOT NULL UNIQUE,
    password_hash VARCHAR(255) NOT NULL,
    created_at TIMESTAMP WITH TIME ZONE DEFAULT CURRENT_TIMESTAMP,
    updated_at TIMESTAMP WITH TIME ZONE DEFAULT CURRENT_TIMESTAMP
);

CREATE INDEX idx_users_email ON users(email);
CREATE INDEX idx_users_username ON users(username);
-- db/migrations/000001_init_users_table.down.sql
DROP INDEX IF EXISTS idx_users_username;
DROP INDEX IF EXISTS idx_users_email;
DROP TABLE IF EXISTS users;

Modèle 2 : Intégration du code Go et modifications incrémentales

CBLK4

Modèle 3 : Stratégie de migration sans interruption

-- db/migrations/000002_add_phone_column.up.sql
-- Step 1: Add nullable column (no table lock)
ALTER TABLE users ADD COLUMN phone VARCHAR(20);

CBLK6

// internal/database/backfill.go
package database

import (
	"context"
	"database/sql"
	"fmt"
	"log"
	"time"
)

type BackfillConfig struct {
	BatchSize    int
	DelayBetween time.Duration
	DryRun       bool
}

func BackfillPhoneNumbers(ctx context.Context, db *sql.DB, config BackfillConfig) error {
	if config.BatchSize <= 0 {
		config.BatchSize = 1000
	}
	if config.DelayBetween == 0 {
		config.DelayBetween = 100 * time.Millisecond
	}

	var totalUpdated int64

	for {
		var updated int64
		err := db.QueryRowContext(ctx, `
			WITH batch AS (
				SELECT id FROM users WHERE phone IS NULL LIMIT $1
			)
			UPDATE users SET phone = ''
			WHERE id IN (SELECT id FROM batch)
			RETURNING 1
		`, config.BatchSize).Scan(&updated)

		if err != nil {
			return fmt.Errorf("backfill batch failed: %w", err)
		}

		if updated == 0 {
			break
		}

		totalUpdated += updated
		log.Printf("Backfilled %d rows (total: %d)", updated, totalUpdated)

		if config.DryRun {
			log.Println("Dry run mode, stopping after first batch")
			break
		}

		select {
		case <-ctx.Done():
			return ctx.Err()
		case <-time.After(config.DelayBetween):
		}
	}

	log.Printf("Backfill complete. Total rows updated: %d", totalUpdated)
	return nil
}

Pattern 4 : Mécanisme de régression et stratégie de sécurité

// internal/database/safe_migrate.go
package database

import (
	"fmt"
	"log"

	"github.com/golang-migrate/migrate/v4"
)

type SafeMigrator struct {
	migrate *migrate.Migrate
	dryRun  bool
}

func NewSafeMigrator(databaseURL string, dryRun bool) (*SafeMigrator, error) {
	m, err := migrate.New("file://db/migrations", databaseURL)
	if err != nil {
		return nil, fmt.Errorf("failed to create migrator: %w", err)
	}
	return &SafeMigrator{migrate: m, dryRun: dryRun}, nil
}

func (sm *SafeMigrator) SafeUp() error {
	currentVersion, dirty, err := sm.migrate.Version()
	if err != nil && err != migrate.ErrNilVersion {
		return fmt.Errorf("failed to get current version: %w", err)
	}

	if dirty {
		return fmt.Errorf("database is in dirty state at version %d, run force first", currentVersion)
	}

	if sm.dryRun {
		log.Printf("[DRY RUN] Would apply migrations from version %d", currentVersion)
		return nil
	}

	if err := sm.migrate.Up(); err != nil && err != migrate.ErrNoChange {
		return fmt.Errorf("migration up failed: %w", err)
	}

	newVersion, _, _ := sm.migrate.Version()
	log.Printf("Migrated from version %d to %d", currentVersion, newVersion)
	return nil
}

Modèle 5 : Intégration et Automatisation CI/CD

CBLK9


Guide des pièges à éviter

Piège 1 : Scripts de migration irréversibles

-- ❌ Wrong: Down migration loses data
ALTER TABLE users DROP COLUMN phone;

-- ✅ Correct: Down migration preserving data
ALTER TABLE users ALTER COLUMN phone DROP NOT NULL;
ALTER TABLE users ALTER COLUMN phone DROP DEFAULT;

Piège 2 : Modification directe de tables volumineuses avec ALTER TABLE

-- ❌ Wrong: Adding NOT NULL column locks large tables
ALTER TABLE users ADD COLUMN phone VARCHAR(20) NOT NULL DEFAULT '';

-- ✅ Correct: Multi-step zero-downtime migration
ALTER TABLE users ADD COLUMN phone VARCHAR(20);
-- Backfill data in application layer
ALTER TABLE users ALTER COLUMN phone SET NOT NULL;

Piège 3 : État sale non géré

# ❌ Wrong: Ignoring dirty state
migrate -path db/migrations -database "$DB_URL" up

# ✅ Correct: Force fix dirty state first
migrate -path db/migrations -database "$DB_URL" force <version>
migrate -path db/migrations -database "$DB_URL" up

Piège 4 : Syntaxe spécifique à la base de données dans les migrations

-- ❌ Wrong: PostgreSQL-specific syntax
ALTER TABLE users ALTER COLUMN phone SET NOT NULL;

-- ✅ Correct: Use standard SQL or conditional compilation
-- PostgreSQL: ALTER TABLE users ALTER COLUMN phone SET NOT NULL;
-- MySQL: ALTER TABLE users MODIFY COLUMN phone VARCHAR(20) NOT NULL;

Piège 5 : Migration et déploiement du code hors de phase

CBLK14


Dépannage des erreurs

# Message d'erreur Cause Solution
1 aucun changement La base de données est déjà à la dernière version Normal, aucune action nécessaire
2 base de données sale Migration interrompue Utilisez la commande force pour corriger la version
3 erreur de syntaxe dans la migration Erreur de syntaxe SQL Vérifiez la syntaxe SQL du fichier de migration
4 migration locked Exécution concurrente de migration S'assurer qu'un seul processus de migration s'exécute à la fois
5 la colonne existe déjà Exécution de migration en double Vérifiez le numéro de version
6 permission denied Permissions insuffisantes de l'utilisateur de la base de données Accorder les permissions CREATE/ALTER
7 connection refused Échec de connexion à la base de données Vérifiez DATABASE_URL et l'état de la base de données
8 violation de clé étrangère Contrainte de clé étrangère bloquant l'opération Gérez d'abord les données associées
9 lock timeout Délai d'expiration du verrou DDL pour les grandes tables Utiliser une stratégie sans interruption
10 fichier de migration non trouvé Erreur de chemin du fichier de migration Vérifiez le paramètre migrate -path

Optimisation avancée

  1. Test de migration : Vérifier chaque migration avec up→down→up dans CI
  2. Migration linter : Utilisez sqlfluff ou des outils personnalisés pour vérifier les normes des scripts de migration
  3. Support multi-base de données : Utilisez des étiquettes de build pour différents fichiers de migration de base de données
  4. Surveillance du remplissage des données : Suivre l'avancement du remplissage de grandes tables avec prise en charge de la reprise
  5. Intégration du déploiement bleu-vert : Coordonner les migrations avec le déploiement bleu-vert pour des mises en production sans interruption réelle

Comparaison

Dimension golang-migrate Goose Atlas Liquibase
Go native ⭐⭐⭐⭐⭐ ⭐⭐⭐⭐⭐ ⭐⭐⭐
Multi-database ⭐⭐⭐⭐⭐ ⭐⭐⭐⭐ ⭐⭐⭐⭐⭐ ⭐⭐⭐⭐⭐
Migration déclarative ⭐⭐ ⭐⭐ ⭐⭐⭐⭐⭐ ⭐⭐⭐⭐
Suivi des versions ⭐⭐⭐⭐⭐ ⭐⭐⭐⭐⭐ ⭐⭐⭐⭐ ⭐⭐⭐⭐⭐
Solution de repli ⭐⭐⭐ ⭐⭐⭐ ⭐⭐⭐⭐⭐ ⭐⭐⭐⭐
Intégration CI ⭐⭐⭐⭐ ⭐⭐⭐⭐ ⭐⭐⭐⭐⭐ ⭐⭐⭐⭐
Courbe d'apprentissage Faible Faible Moyen Élevé

Résumé : La migration de base de données est essentielle pour les projets Go en production. golang-migrate est le choix privilégié en 2026 grâce à son interface CLI claire, son intégration native avec Go et sa prise en charge multi-base de données. Principes fondamentaux : migrer avant le déploiement du code, les migrations Down doivent être inversibles, les modifications de grandes tables par étapes, vérification automatique en CI/CD.


Outils en ligne recommandés

Essayez ces outils exécutés localement dans le navigateur — aucune inscription requise →

#Go数据库迁移#golang-migrate#Schema管理#数据库版本控制#2026#技术架构