github.com/KinWaiYuen/client-go/v2@v2.5.4/txnkv/transaction/commit.go (about) 1 // Copyright 2021 TiKV Authors 2 // 3 // Licensed under the Apache License, Version 2.0 (the "License"); 4 // you may not use this file except in compliance with the License. 5 // You may obtain a copy of the License at 6 // 7 // http://www.apache.org/licenses/LICENSE-2.0 8 // 9 // Unless required by applicable law or agreed to in writing, software 10 // distributed under the License is distributed on an "AS IS" BASIS, 11 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 // See the License for the specific language governing permissions and 13 // limitations under the License. 14 15 // NOTE: The code in this file is based on code from the 16 // TiDB project, licensed under the Apache License v 2.0 17 // 18 // https://github.com/pingcap/tidb/tree/cc5e161ac06827589c4966674597c137cc9e809c/store/tikv/commit.go 19 // 20 21 // Copyright 2020 PingCAP, Inc. 22 // 23 // Licensed under the Apache License, Version 2.0 (the "License"); 24 // you may not use this file except in compliance with the License. 25 // You may obtain a copy of the License at 26 // 27 // http://www.apache.org/licenses/LICENSE-2.0 28 // 29 // Unless required by applicable law or agreed to in writing, software 30 // distributed under the License is distributed on an "AS IS" BASIS, 31 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 32 // See the License for the specific language governing permissions and 33 // limitations under the License. 34 35 package transaction 36 37 import ( 38 "encoding/hex" 39 "time" 40 41 tikverr "github.com/KinWaiYuen/client-go/v2/error" 42 "github.com/KinWaiYuen/client-go/v2/internal/client" 43 "github.com/KinWaiYuen/client-go/v2/internal/locate" 44 "github.com/KinWaiYuen/client-go/v2/internal/logutil" 45 "github.com/KinWaiYuen/client-go/v2/internal/retry" 46 "github.com/KinWaiYuen/client-go/v2/metrics" 47 "github.com/KinWaiYuen/client-go/v2/tikvrpc" 48 "github.com/opentracing/opentracing-go" 49 "github.com/pingcap/errors" 50 "github.com/pingcap/kvproto/pkg/kvrpcpb" 51 "github.com/prometheus/client_golang/prometheus" 52 "go.uber.org/zap" 53 ) 54 55 type actionCommit struct{ retry bool } 56 57 var _ twoPhaseCommitAction = actionCommit{} 58 59 func (actionCommit) String() string { 60 return "commit" 61 } 62 63 func (actionCommit) tiKVTxnRegionsNumHistogram() prometheus.Observer { 64 return metrics.TxnRegionsNumHistogramCommit 65 } 66 67 func (actionCommit) handleSingleBatch(c *twoPhaseCommitter, bo *retry.Backoffer, batch batchMutations) error { 68 keys := batch.mutations.GetKeys() 69 req := tikvrpc.NewRequest(tikvrpc.CmdCommit, &kvrpcpb.CommitRequest{ 70 StartVersion: c.startTS, 71 Keys: keys, 72 CommitVersion: c.commitTS, 73 }, kvrpcpb.Context{Priority: c.priority, SyncLog: c.syncLog, 74 ResourceGroupTag: c.resourceGroupTag, DiskFullOpt: c.diskFullOpt, 75 MaxExecutionDurationMs: uint64(client.MaxWriteExecutionTime.Milliseconds())}) 76 77 tBegin := time.Now() 78 attempts := 0 79 80 sender := locate.NewRegionRequestSender(c.store.GetRegionCache(), c.store.GetTiKVClient()) 81 for { 82 attempts++ 83 if time.Since(tBegin) > slowRequestThreshold { 84 logutil.BgLogger().Warn("slow commit request", zap.Uint64("startTS", c.startTS), zap.Stringer("region", &batch.region), zap.Int("attempts", attempts)) 85 tBegin = time.Now() 86 } 87 88 resp, err := sender.SendReq(bo, req, batch.region, client.ReadTimeoutShort) 89 // If we fail to receive response for the request that commits primary key, it will be undetermined whether this 90 // transaction has been successfully committed. 91 // Under this circumstance, we can not declare the commit is complete (may lead to data lost), nor can we throw 92 // an error (may lead to the duplicated key error when upper level restarts the transaction). Currently the best 93 // solution is to populate this error and let upper layer drop the connection to the corresponding mysql client. 94 if batch.isPrimary && sender.GetRPCError() != nil && !c.isAsyncCommit() { 95 c.setUndeterminedErr(errors.Trace(sender.GetRPCError())) 96 } 97 98 // Unexpected error occurs, return it. 99 if err != nil { 100 return errors.Trace(err) 101 } 102 103 regionErr, err := resp.GetRegionError() 104 if err != nil { 105 return errors.Trace(err) 106 } 107 if regionErr != nil { 108 // For other region error and the fake region error, backoff because 109 // there's something wrong. 110 // For the real EpochNotMatch error, don't backoff. 111 if regionErr.GetEpochNotMatch() == nil || locate.IsFakeRegionError(regionErr) { 112 err = bo.Backoff(retry.BoRegionMiss, errors.New(regionErr.String())) 113 if err != nil { 114 return errors.Trace(err) 115 } 116 } 117 same, err := batch.relocate(bo, c.store.GetRegionCache()) 118 if err != nil { 119 return errors.Trace(err) 120 } 121 if same { 122 continue 123 } 124 err = c.doActionOnMutations(bo, actionCommit{true}, batch.mutations) 125 return errors.Trace(err) 126 } 127 128 if resp.Resp == nil { 129 return errors.Trace(tikverr.ErrBodyMissing) 130 } 131 commitResp := resp.Resp.(*kvrpcpb.CommitResponse) 132 // Here we can make sure tikv has processed the commit primary key request. So 133 // we can clean undetermined error. 134 if batch.isPrimary && !c.isAsyncCommit() { 135 c.setUndeterminedErr(nil) 136 } 137 if keyErr := commitResp.GetError(); keyErr != nil { 138 if rejected := keyErr.GetCommitTsExpired(); rejected != nil { 139 logutil.Logger(bo.GetCtx()).Info("2PC commitTS rejected by TiKV, retry with a newer commitTS", 140 zap.Uint64("txnStartTS", c.startTS), 141 zap.Stringer("info", logutil.Hex(rejected))) 142 143 // Do not retry for a txn which has a too large MinCommitTs 144 // 3600000 << 18 = 943718400000 145 if rejected.MinCommitTs-rejected.AttemptedCommitTs > 943718400000 { 146 err := errors.Errorf("2PC MinCommitTS is too large, we got MinCommitTS: %d, and AttemptedCommitTS: %d", 147 rejected.MinCommitTs, rejected.AttemptedCommitTs) 148 return errors.Trace(err) 149 } 150 151 // Update commit ts and retry. 152 commitTS, err := c.store.GetTimestampWithRetry(bo, c.txn.GetScope()) 153 if err != nil { 154 logutil.Logger(bo.GetCtx()).Warn("2PC get commitTS failed", 155 zap.Error(err), 156 zap.Uint64("txnStartTS", c.startTS)) 157 return errors.Trace(err) 158 } 159 160 c.mu.Lock() 161 c.commitTS = commitTS 162 c.mu.Unlock() 163 // Update the commitTS of the request and retry. 164 req.Commit().CommitVersion = commitTS 165 continue 166 } 167 168 c.mu.RLock() 169 defer c.mu.RUnlock() 170 err = tikverr.ExtractKeyErr(keyErr) 171 if c.mu.committed { 172 // No secondary key could be rolled back after it's primary key is committed. 173 // There must be a serious bug somewhere. 174 hexBatchKeys := func(keys [][]byte) []string { 175 var res []string 176 for _, k := range keys { 177 res = append(res, hex.EncodeToString(k)) 178 } 179 return res 180 } 181 logutil.Logger(bo.GetCtx()).Error("2PC failed commit key after primary key committed", 182 zap.Error(err), 183 zap.Uint64("txnStartTS", c.startTS), 184 zap.Uint64("commitTS", c.commitTS), 185 zap.Strings("keys", hexBatchKeys(keys))) 186 return errors.Trace(err) 187 } 188 // The transaction maybe rolled back by concurrent transactions. 189 logutil.Logger(bo.GetCtx()).Debug("2PC failed commit primary key", 190 zap.Error(err), 191 zap.Uint64("txnStartTS", c.startTS)) 192 return err 193 } 194 break 195 } 196 197 c.mu.Lock() 198 defer c.mu.Unlock() 199 // Group that contains primary key is always the first. 200 // We mark transaction's status committed when we receive the first success response. 201 c.mu.committed = true 202 return nil 203 } 204 205 func (c *twoPhaseCommitter) commitMutations(bo *retry.Backoffer, mutations CommitterMutations) error { 206 if span := opentracing.SpanFromContext(bo.GetCtx()); span != nil && span.Tracer() != nil { 207 span1 := span.Tracer().StartSpan("twoPhaseCommitter.commitMutations", opentracing.ChildOf(span.Context())) 208 defer span1.Finish() 209 bo.SetCtx(opentracing.ContextWithSpan(bo.GetCtx(), span1)) 210 } 211 212 return c.doActionOnMutations(bo, actionCommit{}, mutations) 213 }