golang-migrate/migrate · critical

database is dirty

Error message

database is dirty

What it means

ErrDatabaseDirty in database/mysql is returned when the schema_migrations table contains a row with a non-empty dirty flag, meaning a previous migration failed midway and the database is in an unknown/partially-applied state. Migrate refuses further migrations until someone inspects the state and clears the flag. It is also defined identically in other drivers (cassandra, pgx), so the message can come from any of them.

Source

Thrown at database/mysql/mysql.go:33

	"strconv"
	"strings"
	"sync/atomic"
	"time"

	"github.com/go-sql-driver/mysql"
	"github.com/golang-migrate/migrate/v4/database"
)

var _ database.Driver = (*Mysql)(nil) // explicit compile time type check

func init() {
	database.Register("mysql", &Mysql{})
}

var DefaultMigrationsTable = "schema_migrations"

var (
	ErrDatabaseDirty    = fmt.Errorf("database is dirty")
	ErrNilConfig        = fmt.Errorf("no config")
	ErrNoDatabaseName   = fmt.Errorf("no database name")
	ErrAppendPEM        = fmt.Errorf("failed to append PEM")
	ErrTLSCertKeyConfig = fmt.Errorf("to use TLS client authentication, both x-tls-cert and x-tls-key must not be empty")
)

type Config struct {
	MigrationsTable  string
	DatabaseName     string
	NoLock           bool
	StatementTimeout time.Duration
}

type Mysql struct {
	// mysql RELEASE_LOCK must be called from the same conn, so
	// just do everything over a single conn anyway.
	conn     *sql.Conn
	db       *sql.DB

View on GitHub (pinned to 01a9643f14)

Solutions

  1. Inspect the schema and the failing migration to manually repair or roll back partial changes
  2. Run migrate.Force(version) to reset the recorded version to the actual applied state
  3. Clear the dirty flag in schema_migrations (set dirty=0) once state is verified
  4. Fix the broken migration file before re-running migrations

Example fix

// before: blind retry fails with ErrDatabaseDirty
m.Steps(1)
// after: repair state then force
// manually verify/fix schema for version N
if err := m.Force(N); err != nil { return err }
if err := m.Up(); err != nil { return err }
Defensive patterns

Strategy: try-catch

Validate before calling

var dirty bool
row := sqlDB.QueryRow("SELECT dirty FROM schema_migrations LIMIT 1")
if err := row.Scan(&dirty); err == nil && dirty {
    return fmt.Errorf("database left dirty by a previous migration; inspect and force before migrating")
}

Try / catch

if err := m.Up(); err != nil {
    if errors.Is(err, mysql.ErrDatabaseDirty) {
        // repair schema manually, then: m.Force(actualVersion); m.Up()
    }
    return err
}

Prevention

When it happens

Trigger: A migration previously failed after the version was recorded but before cleanup; running migrate.Up/Steps on a database left dirty by a crashed process; forcing a version with force but not fixing the schema.

Common situations: Deploy pipeline killed mid-migration, SQL migration with a syntax/permission error partway through, manual intervention during a migration, shared staging database left dirty by a colleague.

Related errors


AI-assisted analysis of golang-migrate/migrate@01a9643f14 (2026-09-02). Data as JSON: /api/errors/b0bcb63b974a7a81. Report an issue: GitHub.