github.com/cockroachdb/cockroach@v20.2.0-alpha.1+incompatible/pkg/kv/kvserver/batcheval/cmd_query_intent.go (about) 1 // Copyright 2018 The Cockroach Authors. 2 // 3 // Use of this software is governed by the Business Source License 4 // included in the file licenses/BSL.txt. 5 // 6 // As of the Change Date specified in that file, in accordance with 7 // the Business Source License, use of this software will be governed 8 // by the Apache License, Version 2.0, included in the file 9 // licenses/APL.txt. 10 11 package batcheval 12 13 import ( 14 "context" 15 16 "github.com/cockroachdb/cockroach/pkg/kv/kvserver/batcheval/result" 17 "github.com/cockroachdb/cockroach/pkg/kv/kvserver/spanset" 18 "github.com/cockroachdb/cockroach/pkg/roachpb" 19 "github.com/cockroachdb/cockroach/pkg/storage" 20 "github.com/cockroachdb/cockroach/pkg/util/hlc" 21 "github.com/cockroachdb/cockroach/pkg/util/log" 22 ) 23 24 func init() { 25 RegisterReadOnlyCommand(roachpb.QueryIntent, declareKeysQueryIntent, QueryIntent) 26 } 27 28 func declareKeysQueryIntent( 29 _ *roachpb.RangeDescriptor, 30 header roachpb.Header, 31 req roachpb.Request, 32 latchSpans, _ *spanset.SpanSet, 33 ) { 34 // QueryIntent requests read the specified keys at the maximum timestamp in 35 // order to read any intent present, if one exists, regardless of the 36 // timestamp it was written at. 37 latchSpans.AddNonMVCC(spanset.SpanReadOnly, req.Header().Span()) 38 } 39 40 // QueryIntent checks if an intent exists for the specified transaction at the 41 // given key. If the intent is missing, the request prevents the intent from 42 // ever being written at the specified timestamp (but the actual prevention 43 // happens during the timestamp cache update). 44 // 45 // QueryIntent returns an error if the intent is missing and its ErrorIfMissing 46 // field is set to true. This error is typically an IntentMissingError, but the 47 // request is special-cased to return a SERIALIZABLE retry error if a transaction 48 // queries its own intent and finds it has been pushed. 49 func QueryIntent( 50 ctx context.Context, reader storage.Reader, cArgs CommandArgs, resp roachpb.Response, 51 ) (result.Result, error) { 52 args := cArgs.Args.(*roachpb.QueryIntentRequest) 53 h := cArgs.Header 54 reply := resp.(*roachpb.QueryIntentResponse) 55 56 // Read at the specified key at the maximum timestamp. This ensures that we 57 // see an intent if one exists, regardless of what timestamp it is written 58 // at. 59 _, intent, err := storage.MVCCGet(ctx, reader, args.Key, hlc.MaxTimestamp, storage.MVCCGetOptions{ 60 // Perform an inconsistent read so that intents are returned instead of 61 // causing WriteIntentErrors. 62 Inconsistent: true, 63 // Even if the request header contains a txn, perform the engine lookup 64 // without a transaction so that intents for a matching transaction are 65 // not returned as values (i.e. we don't want to see our own writes). 66 Txn: nil, 67 }) 68 if err != nil { 69 return result.Result{}, err 70 } 71 72 // Determine if the request is querying an intent in its own transaction. 73 ownTxn := h.Txn != nil && h.Txn.ID == args.Txn.ID 74 75 var curIntentPushed bool 76 if intent != nil { 77 // See comment on QueryIntentRequest.Txn for an explanation of this 78 // comparison. 79 // TODO(nvanbenschoten): Now that we have a full intent history, 80 // we can look at the exact sequence! That won't serve as much more 81 // than an assertion that QueryIntent is being used correctly. 82 reply.FoundIntent = (args.Txn.ID == intent.Txn.ID) && 83 (args.Txn.Epoch == intent.Txn.Epoch) && 84 (args.Txn.Sequence <= intent.Txn.Sequence) 85 86 // If we found a matching intent, check whether the intent was pushed 87 // past its expected timestamp. 88 if reply.FoundIntent { 89 // If the request is querying an intent for its own transaction, forward 90 // the timestamp we compare against to the provisional commit timestamp 91 // in the batch header. 92 cmpTS := args.Txn.WriteTimestamp 93 if ownTxn { 94 cmpTS.Forward(h.Txn.WriteTimestamp) 95 } 96 if cmpTS.Less(intent.Txn.WriteTimestamp) { 97 // The intent matched but was pushed to a later timestamp. Consider a 98 // pushed intent a missing intent. 99 curIntentPushed = true 100 log.VEventf(ctx, 2, "found pushed intent") 101 reply.FoundIntent = false 102 103 // If the request was querying an intent in its own transaction, update 104 // the response transaction. 105 if ownTxn { 106 reply.Txn = h.Txn.Clone() 107 reply.Txn.WriteTimestamp.Forward(intent.Txn.WriteTimestamp) 108 } 109 } 110 } 111 } 112 113 if !reply.FoundIntent && args.ErrorIfMissing { 114 if ownTxn && curIntentPushed { 115 // If the transaction's own intent was pushed, go ahead and 116 // return a TransactionRetryError immediately with an updated 117 // transaction proto. This is an optimization that can help 118 // the txn use refresh spans more effectively. 119 return result.Result{}, roachpb.NewTransactionRetryError(roachpb.RETRY_SERIALIZABLE, "intent pushed") 120 } 121 return result.Result{}, roachpb.NewIntentMissingError(args.Key, intent) 122 } 123 return result.Result{}, nil 124 }