Skip to content

Commit 3f44e04

Browse files
Set a transaction isolation level for MySQL connections (#1156)
* Set transaction isolation in connections * Revert load_map.go change * Var rename * Restore comment
1 parent 5de91c9 commit 3f44e04

File tree

4 files changed

+39
-22
lines changed

4 files changed

+39
-22
lines changed

doc/requirements-and-limitations.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,8 @@ The `SUPER` privilege is required for `STOP SLAVE`, `START SLAVE` operations. Th
2020
- Switching your `binlog_format` to `ROW`, in the case where it is _not_ `ROW` and you explicitly specified `--switch-to-rbr`
2121
- If your replication is already in RBR (`binlog_format=ROW`) you can specify `--assume-rbr` to avoid the `STOP SLAVE/START SLAVE` operations, hence no need for `SUPER`.
2222

23+
- `gh-ost` uses the `REPEATABLE_READ` transaction isolation level for all MySQL connections, regardless of the server default.
24+
2325
- Running `--test-on-replica`: before the cut-over phase, `gh-ost` stops replication so that you can compare the two tables and satisfy that the migration is sound.
2426

2527
### Limitations

go/cmd/gh-ost/main.go

Lines changed: 15 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -181,7 +181,7 @@ func main() {
181181
}
182182

183183
if migrationContext.AlterStatement == "" {
184-
log.Fatalf("--alter must be provided and statement must not be empty")
184+
log.Fatal("--alter must be provided and statement must not be empty")
185185
}
186186
parser := sql.NewParserFromAlterStatement(migrationContext.AlterStatement)
187187
migrationContext.AlterStatementOptions = parser.GetAlterStatementOptions()
@@ -190,7 +190,7 @@ func main() {
190190
if parser.HasExplicitSchema() {
191191
migrationContext.DatabaseName = parser.GetExplicitSchema()
192192
} else {
193-
log.Fatalf("--database must be provided and database name must not be empty, or --alter must specify database name")
193+
log.Fatal("--database must be provided and database name must not be empty, or --alter must specify database name")
194194
}
195195
}
196196

@@ -202,48 +202,48 @@ func main() {
202202
if parser.HasExplicitTable() {
203203
migrationContext.OriginalTableName = parser.GetExplicitTable()
204204
} else {
205-
log.Fatalf("--table must be provided and table name must not be empty, or --alter must specify table name")
205+
log.Fatal("--table must be provided and table name must not be empty, or --alter must specify table name")
206206
}
207207
}
208208
migrationContext.Noop = !(*executeFlag)
209209
if migrationContext.AllowedRunningOnMaster && migrationContext.TestOnReplica {
210-
migrationContext.Log.Fatalf("--allow-on-master and --test-on-replica are mutually exclusive")
210+
migrationContext.Log.Fatal("--allow-on-master and --test-on-replica are mutually exclusive")
211211
}
212212
if migrationContext.AllowedRunningOnMaster && migrationContext.MigrateOnReplica {
213-
migrationContext.Log.Fatalf("--allow-on-master and --migrate-on-replica are mutually exclusive")
213+
migrationContext.Log.Fatal("--allow-on-master and --migrate-on-replica are mutually exclusive")
214214
}
215215
if migrationContext.MigrateOnReplica && migrationContext.TestOnReplica {
216-
migrationContext.Log.Fatalf("--migrate-on-replica and --test-on-replica are mutually exclusive")
216+
migrationContext.Log.Fatal("--migrate-on-replica and --test-on-replica are mutually exclusive")
217217
}
218218
if migrationContext.SwitchToRowBinlogFormat && migrationContext.AssumeRBR {
219-
migrationContext.Log.Fatalf("--switch-to-rbr and --assume-rbr are mutually exclusive")
219+
migrationContext.Log.Fatal("--switch-to-rbr and --assume-rbr are mutually exclusive")
220220
}
221221
if migrationContext.TestOnReplicaSkipReplicaStop {
222222
if !migrationContext.TestOnReplica {
223-
migrationContext.Log.Fatalf("--test-on-replica-skip-replica-stop requires --test-on-replica to be enabled")
223+
migrationContext.Log.Fatal("--test-on-replica-skip-replica-stop requires --test-on-replica to be enabled")
224224
}
225225
migrationContext.Log.Warning("--test-on-replica-skip-replica-stop enabled. We will not stop replication before cut-over. Ensure you have a plugin that does this.")
226226
}
227227
if migrationContext.CliMasterUser != "" && migrationContext.AssumeMasterHostname == "" {
228-
migrationContext.Log.Fatalf("--master-user requires --assume-master-host")
228+
migrationContext.Log.Fatal("--master-user requires --assume-master-host")
229229
}
230230
if migrationContext.CliMasterPassword != "" && migrationContext.AssumeMasterHostname == "" {
231-
migrationContext.Log.Fatalf("--master-password requires --assume-master-host")
231+
migrationContext.Log.Fatal("--master-password requires --assume-master-host")
232232
}
233233
if migrationContext.TLSCACertificate != "" && !migrationContext.UseTLS {
234-
migrationContext.Log.Fatalf("--ssl-ca requires --ssl")
234+
migrationContext.Log.Fatal("--ssl-ca requires --ssl")
235235
}
236236
if migrationContext.TLSCertificate != "" && !migrationContext.UseTLS {
237-
migrationContext.Log.Fatalf("--ssl-cert requires --ssl")
237+
migrationContext.Log.Fatal("--ssl-cert requires --ssl")
238238
}
239239
if migrationContext.TLSKey != "" && !migrationContext.UseTLS {
240-
migrationContext.Log.Fatalf("--ssl-key requires --ssl")
240+
migrationContext.Log.Fatal("--ssl-key requires --ssl")
241241
}
242242
if migrationContext.TLSAllowInsecure && !migrationContext.UseTLS {
243-
migrationContext.Log.Fatalf("--ssl-allow-insecure requires --ssl")
243+
migrationContext.Log.Fatal("--ssl-allow-insecure requires --ssl")
244244
}
245245
if *replicationLagQuery != "" {
246-
migrationContext.Log.Warningf("--replication-lag-query is deprecated")
246+
migrationContext.Log.Warning("--replication-lag-query is deprecated")
247247
}
248248

249249
switch *cutOver {

go/mysql/connection.go

Lines changed: 17 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
Copyright 2016 GitHub Inc.
2+
Copyright 2022 GitHub Inc.
33
See https://github.com/github/gh-ost/blob/master/LICENSE
44
*/
55

@@ -12,12 +12,14 @@ import (
1212
"fmt"
1313
"io/ioutil"
1414
"net"
15+
"strings"
1516

1617
"github.com/go-sql-driver/mysql"
1718
)
1819

1920
const (
20-
TLS_CONFIG_KEY = "ghost"
21+
transactionIsolation = "REPEATABLE-READ"
22+
TLS_CONFIG_KEY = "ghost"
2123
)
2224

2325
// ConnectionConfig is the minimal configuration required to connect to a MySQL server
@@ -112,12 +114,23 @@ func (this *ConnectionConfig) GetDBUri(databaseName string) string {
112114
// Wrap IPv6 literals in square brackets
113115
hostname = fmt.Sprintf("[%s]", hostname)
114116
}
115-
interpolateParams := true
117+
116118
// go-mysql-driver defaults to false if tls param is not provided; explicitly setting here to
117119
// simplify construction of the DSN below.
118120
tlsOption := "false"
119121
if this.tlsConfig != nil {
120122
tlsOption = TLS_CONFIG_KEY
121123
}
122-
return fmt.Sprintf("%s:%s@tcp(%s:%d)/%s?timeout=%fs&readTimeout=%fs&writeTimeout=%fs&interpolateParams=%t&autocommit=true&charset=utf8mb4,utf8,latin1&tls=%s", this.User, this.Password, hostname, this.Key.Port, databaseName, this.Timeout, this.Timeout, this.Timeout, interpolateParams, tlsOption)
124+
connectionParams := []string{
125+
"autocommit=true",
126+
"charset=utf8mb4,utf8,latin1",
127+
"interpolateParams=true",
128+
fmt.Sprintf("tls=%s", tlsOption),
129+
fmt.Sprintf("transaction_isolation=%q", transactionIsolation),
130+
fmt.Sprintf("timeout=%fs", this.Timeout),
131+
fmt.Sprintf("readTimeout=%fs", this.Timeout),
132+
fmt.Sprintf("writeTimeout=%fs", this.Timeout),
133+
}
134+
135+
return fmt.Sprintf("%s:%s@tcp(%s:%d)/%s?%s", this.User, this.Password, hostname, this.Key.Port, databaseName, strings.Join(connectionParams, "&"))
123136
}

go/mysql/connection_test.go

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
Copyright 2016 GitHub Inc.
2+
Copyright 2022 GitHub Inc.
33
See https://github.com/github/gh-ost/blob/master/LICENSE
44
*/
55

@@ -67,18 +67,20 @@ func TestGetDBUri(t *testing.T) {
6767
c.Key = InstanceKey{Hostname: "myhost", Port: 3306}
6868
c.User = "gromit"
6969
c.Password = "penguin"
70+
c.Timeout = 1.2345
7071

7172
uri := c.GetDBUri("test")
72-
test.S(t).ExpectEquals(uri, "gromit:penguin@tcp(myhost:3306)/test?timeout=0.000000s&readTimeout=0.000000s&writeTimeout=0.000000s&interpolateParams=true&autocommit=true&charset=utf8mb4,utf8,latin1&tls=false")
73+
test.S(t).ExpectEquals(uri, `gromit:penguin@tcp(myhost:3306)/test?autocommit=true&charset=utf8mb4,utf8,latin1&interpolateParams=true&tls=false&transaction_isolation="REPEATABLE-READ"&timeout=1.234500s&readTimeout=1.234500s&writeTimeout=1.234500s`)
7374
}
7475

7576
func TestGetDBUriWithTLSSetup(t *testing.T) {
7677
c := NewConnectionConfig()
7778
c.Key = InstanceKey{Hostname: "myhost", Port: 3306}
7879
c.User = "gromit"
7980
c.Password = "penguin"
81+
c.Timeout = 1.2345
8082
c.tlsConfig = &tls.Config{}
8183

8284
uri := c.GetDBUri("test")
83-
test.S(t).ExpectEquals(uri, "gromit:penguin@tcp(myhost:3306)/test?timeout=0.000000s&readTimeout=0.000000s&writeTimeout=0.000000s&interpolateParams=true&autocommit=true&charset=utf8mb4,utf8,latin1&tls=ghost")
85+
test.S(t).ExpectEquals(uri, `gromit:penguin@tcp(myhost:3306)/test?autocommit=true&charset=utf8mb4,utf8,latin1&interpolateParams=true&tls=ghost&transaction_isolation="REPEATABLE-READ"&timeout=1.234500s&readTimeout=1.234500s&writeTimeout=1.234500s`)
8486
}

0 commit comments

Comments
 (0)