vitess.io/vitess@v0.16.2/go/vt/vttablet/tabletmanager/vreplication/vdbclient.go (about) 1 /* 2 Copyright 2019 The Vitess Authors. 3 4 Licensed under the Apache License, Version 2.0 (the "License"); 5 you may not use this file except in compliance with the License. 6 You may obtain a copy of the License at 7 8 http://www.apache.org/licenses/LICENSE-2.0 9 10 Unless required by applicable law or agreed to in writing, software 11 distributed under the License is distributed on an "AS IS" BASIS, 12 WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 See the License for the specific language governing permissions and 14 limitations under the License. 15 */ 16 17 package vreplication 18 19 import ( 20 "io" 21 "time" 22 23 "context" 24 25 "vitess.io/vitess/go/mysql" 26 "vitess.io/vitess/go/sqltypes" 27 "vitess.io/vitess/go/vt/binlog/binlogplayer" 28 "vitess.io/vitess/go/vt/log" 29 ) 30 31 // vdbClient is a wrapper on binlogplayer.DBClient. 32 // It allows us to retry a failed transactions on lock errors. 33 type vdbClient struct { 34 binlogplayer.DBClient 35 stats *binlogplayer.Stats 36 InTransaction bool 37 startTime time.Time 38 queries []string 39 } 40 41 func newVDBClient(dbclient binlogplayer.DBClient, stats *binlogplayer.Stats) *vdbClient { 42 return &vdbClient{ 43 DBClient: dbclient, 44 stats: stats, 45 } 46 } 47 48 func (vc *vdbClient) Begin() error { 49 if vc.InTransaction { 50 return nil 51 } 52 if err := vc.DBClient.Begin(); err != nil { 53 return err 54 } 55 vc.queries = append(vc.queries, "begin") 56 vc.InTransaction = true 57 vc.startTime = time.Now() 58 return nil 59 } 60 61 func (vc *vdbClient) Commit() error { 62 if err := vc.DBClient.Commit(); err != nil { 63 return err 64 } 65 vc.InTransaction = false 66 vc.queries = nil 67 vc.stats.Timings.Record(binlogplayer.BlplTransaction, vc.startTime) 68 return nil 69 } 70 71 func (vc *vdbClient) Rollback() error { 72 if !vc.InTransaction { 73 return nil 74 } 75 if err := vc.DBClient.Rollback(); err != nil { 76 return err 77 } 78 vc.InTransaction = false 79 // Don't reset queries to allow for vplayer to retry. 80 return nil 81 } 82 83 func (vc *vdbClient) ExecuteFetch(query string, maxrows int) (*sqltypes.Result, error) { 84 defer vc.stats.Timings.Record(binlogplayer.BlplQuery, time.Now()) 85 86 if !vc.InTransaction { 87 vc.queries = []string{query} 88 } else { 89 vc.queries = append(vc.queries, query) 90 } 91 return vc.DBClient.ExecuteFetch(query, maxrows) 92 } 93 94 // Execute is ExecuteFetch without the maxrows. 95 func (vc *vdbClient) Execute(query string) (*sqltypes.Result, error) { 96 // Number of rows should never exceed relayLogMaxItems. 97 return vc.ExecuteFetch(query, relayLogMaxItems) 98 } 99 100 func (vc *vdbClient) ExecuteWithRetry(ctx context.Context, query string) (*sqltypes.Result, error) { 101 qr, err := vc.Execute(query) 102 for err != nil { 103 if sqlErr, ok := err.(*mysql.SQLError); ok && sqlErr.Number() == mysql.ERLockDeadlock || sqlErr.Number() == mysql.ERLockWaitTimeout { 104 log.Infof("retryable error: %v, waiting for %v and retrying", sqlErr, dbLockRetryDelay) 105 if err := vc.Rollback(); err != nil { 106 return nil, err 107 } 108 time.Sleep(dbLockRetryDelay) 109 // Check context here. Otherwise this can become an infinite loop. 110 select { 111 case <-ctx.Done(): 112 return nil, io.EOF 113 default: 114 } 115 qr, err = vc.Retry() 116 continue 117 } 118 return qr, err 119 } 120 return qr, nil 121 } 122 123 func (vc *vdbClient) Retry() (*sqltypes.Result, error) { 124 var qr *sqltypes.Result 125 for _, q := range vc.queries { 126 if q == "begin" { 127 if err := vc.Begin(); err != nil { 128 return nil, err 129 } 130 continue 131 } 132 // Number of rows should never exceed relayLogMaxItems. 133 result, err := vc.DBClient.ExecuteFetch(q, relayLogMaxItems) 134 if err != nil { 135 return nil, err 136 } 137 qr = result 138 } 139 return qr, nil 140 }