github.com/whtcorpsinc/milevadb-prod@v0.0.0-20211104133533-f57f4be3b597/interlock/batch_point_get.go (about) 1 // Copyright 2020 WHTCORPS INC, Inc. 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 // See the License for the specific language governing permissions and 12 // limitations under the License. 13 14 package interlock 15 16 import ( 17 "context" 18 "sort" 19 "sync/atomic" 20 21 "github.com/whtcorpsinc/BerolinaSQL/perceptron" 22 "github.com/whtcorpsinc/failpoint" 23 "github.com/whtcorpsinc/milevadb/blockcodec" 24 "github.com/whtcorpsinc/milevadb/causetstore/einsteindb" 25 "github.com/whtcorpsinc/milevadb/ekv" 26 "github.com/whtcorpsinc/milevadb/soliton/chunk" 27 "github.com/whtcorpsinc/milevadb/soliton/codec" 28 "github.com/whtcorpsinc/milevadb/soliton/math" 29 "github.com/whtcorpsinc/milevadb/soliton/replog" 30 "github.com/whtcorpsinc/milevadb/soliton/rowcodec" 31 "github.com/whtcorpsinc/milevadb/stochastikctx" 32 "github.com/whtcorpsinc/milevadb/stochastikctx/variable" 33 "github.com/whtcorpsinc/milevadb/types" 34 ) 35 36 // BatchPointGetInterDirc executes a bunch of point select queries. 37 type BatchPointGetInterDirc struct { 38 baseInterlockingDirectorate 39 40 tblInfo *perceptron.BlockInfo 41 idxInfo *perceptron.IndexInfo 42 handles []ekv.Handle 43 physIDs []int64 44 partPos int 45 idxVals [][]types.Causet 46 startTS uint64 47 snapshotTS uint64 48 txn ekv.Transaction 49 dagger bool 50 waitTime int64 51 inited uint32 52 values [][]byte 53 index int 54 rowCausetDecoder *rowcodec.ChunkCausetDecoder 55 keepOrder bool 56 desc bool 57 batchGetter ekv.BatchGetter 58 59 defCausumns []*perceptron.DeferredCausetInfo 60 // virtualDeferredCausetIndex records all the indices of virtual defCausumns and sort them in definition 61 // to make sure we can compute the virtual defCausumn in right order. 62 virtualDeferredCausetIndex []int 63 64 // virtualDeferredCausetRetFieldTypes records the RetFieldTypes of virtual defCausumns. 65 virtualDeferredCausetRetFieldTypes []*types.FieldType 66 67 snapshot ekv.Snapshot 68 stats *runtimeStatsWithSnapshot 69 } 70 71 // buildVirtualDeferredCausetInfo saves virtual defCausumn indices and sort them in definition order 72 func (e *BatchPointGetInterDirc) buildVirtualDeferredCausetInfo() { 73 e.virtualDeferredCausetIndex = buildVirtualDeferredCausetIndex(e.Schema(), e.defCausumns) 74 if len(e.virtualDeferredCausetIndex) > 0 { 75 e.virtualDeferredCausetRetFieldTypes = make([]*types.FieldType, len(e.virtualDeferredCausetIndex)) 76 for i, idx := range e.virtualDeferredCausetIndex { 77 e.virtualDeferredCausetRetFieldTypes[i] = e.schemaReplicant.DeferredCausets[idx].RetType 78 } 79 } 80 } 81 82 // Open implements the InterlockingDirectorate interface. 83 func (e *BatchPointGetInterDirc) Open(context.Context) error { 84 e.snapshotTS = e.startTS 85 txnCtx := e.ctx.GetStochastikVars().TxnCtx 86 if e.dagger { 87 e.snapshotTS = txnCtx.GetForUFIDelateTS() 88 } 89 txn, err := e.ctx.Txn(false) 90 if err != nil { 91 return err 92 } 93 e.txn = txn 94 var snapshot ekv.Snapshot 95 if txn.Valid() && txnCtx.StartTS == txnCtx.GetForUFIDelateTS() { 96 // We can safely reuse the transaction snapshot if startTS is equal to forUFIDelateTS. 97 // The snapshot may contains cache that can reduce RPC call. 98 snapshot = txn.GetSnapshot() 99 } else { 100 snapshot, err = e.ctx.GetStore().GetSnapshot(ekv.Version{Ver: e.snapshotTS}) 101 if err != nil { 102 return err 103 } 104 } 105 if e.runtimeStats != nil { 106 snapshotStats := &einsteindb.SnapshotRuntimeStats{} 107 e.stats = &runtimeStatsWithSnapshot{ 108 SnapshotRuntimeStats: snapshotStats, 109 } 110 snapshot.SetOption(ekv.DefCauslectRuntimeStats, snapshotStats) 111 e.ctx.GetStochastikVars().StmtCtx.RuntimeStatsDefCausl.RegisterStats(e.id, e.stats) 112 } 113 if e.ctx.GetStochastikVars().GetReplicaRead().IsFollowerRead() { 114 snapshot.SetOption(ekv.ReplicaRead, ekv.ReplicaReadFollower) 115 } 116 snapshot.SetOption(ekv.TaskID, e.ctx.GetStochastikVars().StmtCtx.TaskID) 117 var batchGetter ekv.BatchGetter = snapshot 118 if txn.Valid() { 119 batchGetter = ekv.NewBufferBatchGetter(txn.GetMemBuffer(), &PessimisticLockCacheGetter{txnCtx: txnCtx}, snapshot) 120 } 121 e.snapshot = snapshot 122 e.batchGetter = batchGetter 123 return nil 124 } 125 126 // Close implements the InterlockingDirectorate interface. 127 func (e *BatchPointGetInterDirc) Close() error { 128 if e.runtimeStats != nil && e.snapshot != nil { 129 e.snapshot.DelOption(ekv.DefCauslectRuntimeStats) 130 } 131 e.inited = 0 132 e.index = 0 133 return nil 134 } 135 136 // Next implements the InterlockingDirectorate interface. 137 func (e *BatchPointGetInterDirc) Next(ctx context.Context, req *chunk.Chunk) error { 138 req.Reset() 139 if atomic.CompareAndSwapUint32(&e.inited, 0, 1) { 140 if err := e.initialize(ctx); err != nil { 141 return err 142 } 143 } 144 145 if e.index >= len(e.values) { 146 return nil 147 } 148 for !req.IsFull() && e.index < len(e.values) { 149 handle, val := e.handles[e.index], e.values[e.index] 150 err := DecodeEventValToChunk(e.base().ctx, e.schemaReplicant, e.tblInfo, handle, val, req, e.rowCausetDecoder) 151 if err != nil { 152 return err 153 } 154 e.index++ 155 } 156 157 err := FillVirtualDeferredCausetValue(e.virtualDeferredCausetRetFieldTypes, e.virtualDeferredCausetIndex, e.schemaReplicant, e.defCausumns, e.ctx, req) 158 if err != nil { 159 return err 160 } 161 return nil 162 } 163 164 func datumsContainNull(vals []types.Causet) bool { 165 for _, val := range vals { 166 if val.IsNull() { 167 return true 168 } 169 } 170 return false 171 } 172 173 func (e *BatchPointGetInterDirc) initialize(ctx context.Context) error { 174 var handleVals map[string][]byte 175 var indexKeys []ekv.Key 176 var err error 177 batchGetter := e.batchGetter 178 if e.idxInfo != nil && !isCommonHandleRead(e.tblInfo, e.idxInfo) { 179 // `SELECT a, b FROM t WHERE (a, b) IN ((1, 2), (1, 2), (2, 1), (1, 2))` should not return duplicated rows 180 dedup := make(map[replog.MublockString]struct{}) 181 keys := make([]ekv.Key, 0, len(e.idxVals)) 182 for _, idxVals := range e.idxVals { 183 // For all x, 'x IN (null)' evaluate to null, so the query get no result. 184 if datumsContainNull(idxVals) { 185 continue 186 } 187 188 physID := getPhysID(e.tblInfo, idxVals[e.partPos].GetInt64()) 189 idxKey, err1 := EncodeUniqueIndexKey(e.ctx, e.tblInfo, e.idxInfo, idxVals, physID) 190 if err1 != nil && !ekv.ErrNotExist.Equal(err1) { 191 return err1 192 } 193 s := replog.String(idxKey) 194 if _, found := dedup[s]; found { 195 continue 196 } 197 dedup[s] = struct{}{} 198 keys = append(keys, idxKey) 199 } 200 if e.keepOrder { 201 sort.Slice(keys, func(i int, j int) bool { 202 if e.desc { 203 return keys[i].Cmp(keys[j]) > 0 204 } 205 return keys[i].Cmp(keys[j]) < 0 206 }) 207 } 208 indexKeys = keys 209 210 // SELECT * FROM t WHERE x IN (null), in this case there is no key. 211 if len(keys) == 0 { 212 return nil 213 } 214 215 // Fetch all handles. 216 handleVals, err = batchGetter.BatchGet(ctx, keys) 217 if err != nil { 218 return err 219 } 220 221 e.handles = make([]ekv.Handle, 0, len(keys)) 222 if e.tblInfo.Partition != nil { 223 e.physIDs = make([]int64, 0, len(keys)) 224 } 225 for _, key := range keys { 226 handleVal := handleVals[string(key)] 227 if len(handleVal) == 0 { 228 continue 229 } 230 handle, err1 := blockcodec.DecodeHandleInUniqueIndexValue(handleVal, e.tblInfo.IsCommonHandle) 231 if err1 != nil { 232 return err1 233 } 234 e.handles = append(e.handles, handle) 235 if e.tblInfo.Partition != nil { 236 e.physIDs = append(e.physIDs, blockcodec.DecodeBlockID(key)) 237 } 238 } 239 240 // The injection is used to simulate following scenario: 241 // 1. Stochastik A create a point get query but pause before second time `GET` ekv from backend 242 // 2. Stochastik B create an UFIDelATE query to uFIDelate the record that will be obtained in step 1 243 // 3. Then point get retrieve data from backend after step 2 finished 244 // 4. Check the result 245 failpoint.InjectContext(ctx, "batchPointGetRepeablockReadTest-step1", func() { 246 if ch, ok := ctx.Value("batchPointGetRepeablockReadTest").(chan struct{}); ok { 247 // Make `UFIDelATE` continue 248 close(ch) 249 } 250 // Wait `UFIDelATE` finished 251 failpoint.InjectContext(ctx, "batchPointGetRepeablockReadTest-step2", nil) 252 }) 253 } else if e.keepOrder { 254 sort.Slice(e.handles, func(i int, j int) bool { 255 if e.desc { 256 return e.handles[i].Compare(e.handles[j]) > 0 257 } 258 return e.handles[i].Compare(e.handles[j]) < 0 259 }) 260 } 261 262 keys := make([]ekv.Key, len(e.handles)) 263 for i, handle := range e.handles { 264 var tID int64 265 if len(e.physIDs) > 0 { 266 tID = e.physIDs[i] 267 } else { 268 if handle.IsInt() { 269 tID = getPhysID(e.tblInfo, handle.IntValue()) 270 } else { 271 _, d, err1 := codec.DecodeOne(handle.EncodedDefCaus(e.partPos)) 272 if err1 != nil { 273 return err1 274 } 275 tID = getPhysID(e.tblInfo, d.GetInt64()) 276 } 277 } 278 key := blockcodec.EncodeEventKeyWithHandle(tID, handle) 279 keys[i] = key 280 } 281 282 var values map[string][]byte 283 rc := e.ctx.GetStochastikVars().IsPessimisticReadConsistency() 284 // Lock keys (include exists and non-exists keys) before fetch all values for Repeablock Read Isolation. 285 if e.dagger && !rc { 286 lockKeys := make([]ekv.Key, len(keys), len(keys)+len(indexKeys)) 287 copy(lockKeys, keys) 288 for _, idxKey := range indexKeys { 289 // dagger the non-exist index key, using len(val) in case BatchGet result contains some zero len entries 290 if val := handleVals[string(idxKey)]; len(val) == 0 { 291 lockKeys = append(lockKeys, idxKey) 292 } 293 } 294 err = LockKeys(ctx, e.ctx, e.waitTime, lockKeys...) 295 if err != nil { 296 return err 297 } 298 } 299 // Fetch all values. 300 values, err = batchGetter.BatchGet(ctx, keys) 301 if err != nil { 302 return err 303 } 304 handles := make([]ekv.Handle, 0, len(values)) 305 var existKeys []ekv.Key 306 if e.dagger && rc { 307 existKeys = make([]ekv.Key, 0, len(values)) 308 } 309 e.values = make([][]byte, 0, len(values)) 310 for i, key := range keys { 311 val := values[string(key)] 312 if len(val) == 0 { 313 if e.idxInfo != nil && (!e.tblInfo.IsCommonHandle || !e.idxInfo.Primary) { 314 return ekv.ErrNotExist.GenWithStack("inconsistent extra index %s, handle %d not found in causet", 315 e.idxInfo.Name.O, e.handles[i]) 316 } 317 continue 318 } 319 e.values = append(e.values, val) 320 handles = append(handles, e.handles[i]) 321 if e.dagger && rc { 322 existKeys = append(existKeys, key) 323 } 324 } 325 // Lock exists keys only for Read Committed Isolation. 326 if e.dagger && rc { 327 err = LockKeys(ctx, e.ctx, e.waitTime, existKeys...) 328 if err != nil { 329 return err 330 } 331 } 332 e.handles = handles 333 return nil 334 } 335 336 // LockKeys locks the keys for pessimistic transaction. 337 func LockKeys(ctx context.Context, seCtx stochastikctx.Context, lockWaitTime int64, keys ...ekv.Key) error { 338 txnCtx := seCtx.GetStochastikVars().TxnCtx 339 lctx := newLockCtx(seCtx.GetStochastikVars(), lockWaitTime) 340 if txnCtx.IsPessimistic { 341 lctx.ReturnValues = true 342 lctx.Values = make(map[string]ekv.ReturnedValue, len(keys)) 343 } 344 err := doLockKeys(ctx, seCtx, lctx, keys...) 345 if err != nil { 346 return err 347 } 348 if txnCtx.IsPessimistic { 349 // When doLockKeys returns without error, no other goroutines access the map, 350 // it's safe to read it without mutex. 351 for _, key := range keys { 352 rv := lctx.Values[string(key)] 353 if !rv.AlreadyLocked { 354 txnCtx.SetPessimisticLockCache(key, rv.Value) 355 } 356 } 357 } 358 return nil 359 } 360 361 // PessimisticLockCacheGetter implements the ekv.Getter interface. 362 // It is used as a midbse cache to construct the BufferedBatchGetter. 363 type PessimisticLockCacheGetter struct { 364 txnCtx *variable.TransactionContext 365 } 366 367 // Get implements the ekv.Getter interface. 368 func (getter *PessimisticLockCacheGetter) Get(_ context.Context, key ekv.Key) ([]byte, error) { 369 val, ok := getter.txnCtx.GetKeyInPessimisticLockCache(key) 370 if ok { 371 return val, nil 372 } 373 return nil, ekv.ErrNotExist 374 } 375 376 func getPhysID(tblInfo *perceptron.BlockInfo, intVal int64) int64 { 377 pi := tblInfo.Partition 378 if pi == nil { 379 return tblInfo.ID 380 } 381 partIdx := math.Abs(intVal % int64(pi.Num)) 382 return pi.Definitions[partIdx].ID 383 }