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
- Drift de schéma : Structures de base de données incohérentes entre les environnements
- Échec de migration : Les verrous ALTER TABLE sur de grandes tables provoquant une interruption du service
- Difficulté de rollback : Absence de migrations Down ou migrations Down irréversibles
- Conflits de collaboration : Plusieurs développeurs modifiant le schéma simultanément
- 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
- Test de migration : Vérifier chaque migration avec up→down→up dans CI
- Migration linter : Utilisez sqlfluff ou des outils personnalisés pour vérifier les normes des scripts de migration
- Support multi-base de données : Utilisez des étiquettes de build pour différents fichiers de migration de base de données
- Surveillance du remplissage des données : Suivre l'avancement du remplissage de grandes tables avec prise en charge de la reprise
- 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
- Formateur JSON : /fr/json/format
- Calculateur de hachage : /fr/encode/hash
- cURL to Code : /fr/dev/curl-to-code
Essayez ces outils exécutés localement dans le navigateur — aucune inscription requise →