diff --git a/database/mysql/README.md b/database/mysql/README.md index a687b1dd0..94008defb 100644 --- a/database/mysql/README.md +++ b/database/mysql/README.md @@ -7,6 +7,7 @@ | `x-migrations-table` | `MigrationsTable` | Name of the migrations table | | `x-no-lock` | `NoLock` | Set to `true` to skip `GET_LOCK`/`RELEASE_LOCK` statements. Useful for [multi-master MySQL flavors](https://www.percona.com/doc/percona-xtradb-cluster/LATEST/features/pxc-strict-mode.html#explicit-table-locking). Only run migrations from one host when this is enabled. | | `x-statement-timeout` | `StatementTimeout` | Abort any statement that takes more than the specified number of milliseconds, functionally similar to [Server-side SELECT statement timeouts](https://dev.mysql.com/blog-archive/server-side-select-statement-timeouts/) but enforced by the client. Available for all versions of MySQL, not just >=5.7. | +| `x-try-lock-timeout` | `TryLockTimeoutSec` | Timeout in seconds to try to obtain migrations lock. A negative value means infinite timeout. Default value is `10` | | `dbname` | `DatabaseName` | The name of the database to connect to | | `user` | | The user to sign in as | | `password` | | The user's password | diff --git a/database/mysql/mysql.go b/database/mysql/mysql.go index c7e7ef617..e27f397c0 100644 --- a/database/mysql/mysql.go +++ b/database/mysql/mysql.go @@ -29,6 +29,8 @@ func init() { database.Register("mysql", &Mysql{}) } +const DefaultTryLockTimeoutSec = 10 + var DefaultMigrationsTable = "schema_migrations" var ( @@ -40,10 +42,11 @@ var ( ) type Config struct { - MigrationsTable string - DatabaseName string - NoLock bool - StatementTimeout time.Duration + MigrationsTable string + DatabaseName string + NoLock bool + StatementTimeout time.Duration + TryLockTimeoutSec int } type Mysql struct { @@ -253,16 +256,26 @@ func (m *Mysql) Open(url string) (database.Driver, error) { } } + tryLockTimeoutParam := customParams["x-try-lock-timeout"] + tryLockTimeout := DefaultTryLockTimeoutSec + if tryLockTimeoutParam != "" { + tryLockTimeout, err = strconv.Atoi(tryLockTimeoutParam) + if err != nil { + return nil, fmt.Errorf("could not parse x-try-lock-timeout as int: %w", err) + } + } + db, err := sql.Open("mysql", config.FormatDSN()) if err != nil { return nil, err } mx, err := WithInstance(db, &Config{ - DatabaseName: config.DBName, - MigrationsTable: customParams["x-migrations-table"], - NoLock: noLock, - StatementTimeout: time.Duration(statementTimeout) * time.Millisecond, + DatabaseName: config.DBName, + MigrationsTable: customParams["x-migrations-table"], + NoLock: noLock, + StatementTimeout: time.Duration(statementTimeout) * time.Millisecond, + TryLockTimeoutSec: tryLockTimeout, }) if err != nil { return nil, err @@ -295,9 +308,14 @@ func (m *Mysql) Lock() error { return err } - query := "SELECT GET_LOCK(?, 10)" + tryLockTimeout := DefaultTryLockTimeoutSec + if m.config.TryLockTimeoutSec != 0 { + tryLockTimeout = m.config.TryLockTimeoutSec + } + + query := "SELECT GET_LOCK(?, ?)" var success bool - if err := m.conn.QueryRowContext(context.Background(), query, aid).Scan(&success); err != nil { + if err := m.conn.QueryRowContext(context.Background(), query, aid, tryLockTimeout).Scan(&success); err != nil { return &database.Error{OrigErr: err, Err: "try lock failed", Query: []byte(query)} }