From e1bb49a209064dddd56f7afa02013632d3561288 Mon Sep 17 00:00:00 2001 From: Moritz Schroeder Date: Tue, 31 Mar 2026 17:42:37 +0300 Subject: [PATCH] Add configuration setting for transaction isolation level --- database/mysql/README.md | 1 + database/mysql/mysql.go | 47 +++++++++++++++++++++++++++++++++++----- 2 files changed, 42 insertions(+), 6 deletions(-) diff --git a/database/mysql/README.md b/database/mysql/README.md index a687b1dd0..c1d73abd7 100644 --- a/database/mysql/README.md +++ b/database/mysql/README.md @@ -17,6 +17,7 @@ | `x-tls-cert` | | The location of the client certificate file. Must be used with `x-tls-key`. | | `x-tls-key` | | The location of the private key file. Must be used with `x-tls-cert`. | | `x-tls-insecure-skip-verify` | | Whether or not to use SSL (true\|false) | +| `x-tx-isolation` | `TxIsolation` | Set transaction isolation level (Default: SERIALIZABLE). | ## Use with existing client diff --git a/database/mysql/mysql.go b/database/mysql/mysql.go index 7efb79bc4..003ae6e2a 100644 --- a/database/mysql/mysql.go +++ b/database/mysql/mysql.go @@ -30,11 +30,12 @@ func init() { 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") + 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") + ErrInvalidIsolationLevel = fmt.Errorf("invalid isolation level given in x-tx-isolation") ) type Config struct { @@ -42,6 +43,7 @@ type Config struct { DatabaseName string NoLock bool StatementTimeout time.Duration + TxIsolation sql.IsolationLevel } type Mysql struct { @@ -251,6 +253,15 @@ func (m *Mysql) Open(url string) (database.Driver, error) { } } + txIsolationParam := customParams["x-tx-isolation"] + txIsolation := sql.LevelSerializable + if txIsolationParam != "" { + txIsolation, err = getIsolationLevel(txIsolationParam) + if err != nil { + return nil, err + } + } + db, err := sql.Open("mysql", config.FormatDSN()) if err != nil { return nil, err @@ -261,6 +272,7 @@ func (m *Mysql) Open(url string) (database.Driver, error) { MigrationsTable: customParams["x-migrations-table"], NoLock: noLock, StatementTimeout: time.Duration(statementTimeout) * time.Millisecond, + TxIsolation: txIsolation, }) if err != nil { return nil, err @@ -354,7 +366,7 @@ func (m *Mysql) Run(migration io.Reader) error { } func (m *Mysql) SetVersion(version int, dirty bool) error { - tx, err := m.conn.BeginTx(context.Background(), &sql.TxOptions{Isolation: sql.LevelSerializable}) + tx, err := m.conn.BeginTx(context.Background(), &sql.TxOptions{Isolation: m.config.TxIsolation}) if err != nil { return &database.Error{OrigErr: err, Err: "transaction start failed"} } @@ -506,3 +518,26 @@ func readBool(input string) (value bool, valid bool) { // Not a valid bool value return } + +func getIsolationLevel(input string) (sql.IsolationLevel, error) { + switch input { + case "DEFAULT": + return sql.LevelDefault, nil + case "READ-UNCOMMITTED": + return sql.LevelReadUncommitted, nil + case "READ-COMMITTED": + return sql.LevelReadCommitted, nil + case "WRITE-COMMITTED": + return sql.LevelWriteCommitted, nil + case "REPEATABLE-READ": + return sql.LevelRepeatableRead, nil + case "SNAPSHOT": + return sql.LevelSnapshot, nil + case "SERIALIZABLE": + return sql.LevelSerializable, nil + case "LINEARIZABLE": + return sql.LevelLinearizable, nil + default: + return sql.LevelSerializable, ErrInvalidIsolationLevel + } +}