github.com/cockroachdb/cockroach@v20.2.0-alpha.1+incompatible/pkg/sql/delete_range.go (about) 1 // Copyright 2019 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 sql 12 13 import ( 14 "bytes" 15 "context" 16 17 "github.com/cockroachdb/cockroach/pkg/kv" 18 "github.com/cockroachdb/cockroach/pkg/roachpb" 19 "github.com/cockroachdb/cockroach/pkg/sql/row" 20 "github.com/cockroachdb/cockroach/pkg/sql/sem/tree" 21 "github.com/cockroachdb/cockroach/pkg/sql/sqlbase" 22 "github.com/cockroachdb/cockroach/pkg/util/log" 23 "github.com/cockroachdb/errors" 24 ) 25 26 // deleteRangeNode implements DELETE on a primary index satisfying certain 27 // conditions that permit the direct use of the DeleteRange kv operation, 28 // instead of many point deletes. 29 // 30 // Note: deleteRangeNode can't autocommit in the general case, because it has to 31 // delete in batches, and it won't know whether or not there is more work to do 32 // until after a batch is returned. This property precludes using auto commit. 33 // However, if the optimizer can prove that only a small number of rows will 34 // be deleted, it'll enable autoCommit for delete range. 35 type deleteRangeNode struct { 36 // interleavedFastPath is true if we can take the fast path despite operating 37 // on an interleaved table. 38 interleavedFastPath bool 39 // spans are the spans to delete. 40 spans roachpb.Spans 41 // desc is the table descriptor the delete is operating on. 42 desc *sqlbase.ImmutableTableDescriptor 43 // interleavedDesc are the table descriptors of any child interleaved tables 44 // the delete is operating on. 45 interleavedDesc []*sqlbase.ImmutableTableDescriptor 46 // fetcher is around to decode the returned keys from the DeleteRange, so that 47 // we can count the number of rows deleted. 48 fetcher row.Fetcher 49 50 // autoCommitEnabled is set to true if the optimizer proved that we can safely 51 // use autocommit - so that the number of possible returned keys from this 52 // operation is low. If this is true, we won't attempt to run the delete in 53 // batches and will just send one big delete with a commit statement attached. 54 autoCommitEnabled bool 55 56 // rowCount will be set to the count of rows deleted. 57 rowCount int 58 } 59 60 var _ planNode = &deleteRangeNode{} 61 var _ planNodeFastPath = &deleteRangeNode{} 62 var _ batchedPlanNode = &deleteRangeNode{} 63 64 // BatchedNext implements the batchedPlanNode interface. 65 func (d *deleteRangeNode) BatchedNext(params runParams) (bool, error) { 66 return false, nil 67 } 68 69 // BatchedCount implements the batchedPlanNode interface. 70 func (d *deleteRangeNode) BatchedCount() int { 71 return d.rowCount 72 } 73 74 // BatchedValues implements the batchedPlanNode interface. 75 func (d *deleteRangeNode) BatchedValues(rowIdx int) tree.Datums { 76 panic("invalid") 77 } 78 79 // FastPathResults implements the planNodeFastPath interface. 80 func (d *deleteRangeNode) FastPathResults() (int, bool) { 81 return d.rowCount, true 82 } 83 84 // startExec implements the planNode interface. 85 func (d *deleteRangeNode) startExec(params runParams) error { 86 if err := params.p.cancelChecker.Check(); err != nil { 87 return err 88 } 89 if d.interleavedFastPath { 90 for i := range d.spans { 91 d.spans[i].EndKey = d.spans[i].EndKey.PrefixEnd() 92 } 93 } 94 95 allTables := make([]row.FetcherTableArgs, len(d.interleavedDesc)+1) 96 allTables[0] = row.FetcherTableArgs{ 97 Desc: d.desc, 98 Index: &d.desc.PrimaryIndex, 99 Spans: d.spans, 100 } 101 for i, interleaved := range d.interleavedDesc { 102 allTables[i+1] = row.FetcherTableArgs{ 103 Desc: interleaved, 104 Index: &interleaved.PrimaryIndex, 105 Spans: d.spans, 106 } 107 } 108 if err := d.fetcher.Init( 109 params.ExecCfg().Codec, 110 false, /* reverse */ 111 // TODO(nvanbenschoten): it might make sense to use a FOR_UPDATE locking 112 // strength here. Consider hooking this in to the same knob that will 113 // control whether we perform locking implicitly during DELETEs. 114 sqlbase.ScanLockingStrength_FOR_NONE, 115 false, /* returnRangeInfo */ 116 false, /* isCheck */ 117 params.p.alloc, 118 allTables..., 119 ); err != nil { 120 return err 121 } 122 ctx := params.ctx 123 log.VEvent(ctx, 2, "fast delete: skipping scan") 124 spans := make([]roachpb.Span, len(d.spans)) 125 copy(spans, d.spans) 126 if !d.autoCommitEnabled { 127 // Without autocommit, we're going to run each batch one by one, respecting 128 // a max span request keys size. We use spans as a queue of spans to delete. 129 // It'll be edited if there are any resume spans encountered (if any request 130 // hits the key limit). 131 for len(spans) != 0 { 132 b := params.p.txn.NewBatch() 133 d.deleteSpans(params, b, spans) 134 b.Header.MaxSpanRequestKeys = TableTruncateChunkSize 135 if err := params.p.txn.Run(ctx, b); err != nil { 136 return err 137 } 138 139 spans = spans[:0] 140 var err error 141 if spans, err = d.processResults(b.Results, spans); err != nil { 142 return err 143 } 144 } 145 } else { 146 log.Event(ctx, "autocommit enabled") 147 // With autocommit, we're going to run the deleteRange in a single batch 148 // without a limit, since limits and deleteRange aren't compatible with 1pc 149 // transactions / autocommit. This isn't inherently safe, because without a 150 // limit, this command could technically use up unlimited memory. However, 151 // the optimizer only enables autoCommit if the maximum possible number of 152 // keys to delete in this command are low, so we're made safe. 153 b := params.p.txn.NewBatch() 154 d.deleteSpans(params, b, spans) 155 if err := params.p.txn.CommitInBatch(ctx, b); err != nil { 156 return err 157 } 158 if resumeSpans, err := d.processResults(b.Results, nil /* resumeSpans */); err != nil { 159 return err 160 } else if len(resumeSpans) != 0 { 161 // This shouldn't ever happen - we didn't pass a limit into the batch. 162 return errors.AssertionFailedf("deleteRange without a limit unexpectedly returned resumeSpans") 163 } 164 } 165 166 // Possibly initiate a run of CREATE STATISTICS. 167 params.ExecCfg().StatsRefresher.NotifyMutation(d.desc.ID, d.rowCount) 168 169 return nil 170 } 171 172 // deleteSpans adds each input span to a DelRange command in the given batch. 173 func (d *deleteRangeNode) deleteSpans(params runParams, b *kv.Batch, spans roachpb.Spans) { 174 ctx := params.ctx 175 traceKV := params.p.ExtendedEvalContext().Tracing.KVTracingEnabled() 176 for _, span := range spans { 177 if traceKV { 178 log.VEventf(ctx, 2, "DelRange %s - %s", span.Key, span.EndKey) 179 } 180 b.DelRange(span.Key, span.EndKey, true /* returnKeys */) 181 } 182 } 183 184 // processResults parses the results of a DelRangeResponse, incrementing the 185 // rowCount we're going to return for each row. If any resume spans are 186 // encountered during result processing, they're appended to the resumeSpans 187 // input parameter. 188 func (d *deleteRangeNode) processResults( 189 results []kv.Result, resumeSpans []roachpb.Span, 190 ) (roachpb.Spans, error) { 191 for _, r := range results { 192 var prev []byte 193 for _, keyBytes := range r.Keys { 194 // If prefix is same, don't bother decoding key. 195 if len(prev) > 0 && bytes.HasPrefix(keyBytes, prev) { 196 continue 197 } 198 199 after, ok, _, err := d.fetcher.ReadIndexKey(keyBytes) 200 if err != nil { 201 return nil, err 202 } 203 if !ok { 204 return nil, errors.AssertionFailedf("key did not match descriptor") 205 } 206 k := keyBytes[:len(keyBytes)-len(after)] 207 if !bytes.Equal(k, prev) { 208 prev = k 209 d.rowCount++ 210 } 211 } 212 if r.ResumeSpan != nil && r.ResumeSpan.Valid() { 213 resumeSpans = append(resumeSpans, *r.ResumeSpan) 214 } 215 } 216 return resumeSpans, nil 217 } 218 219 // Next implements the planNode interface. 220 func (*deleteRangeNode) Next(params runParams) (bool, error) { 221 panic("invalid") 222 } 223 224 // Values implements the planNode interface. 225 func (*deleteRangeNode) Values() tree.Datums { 226 panic("invalid") 227 } 228 229 // Close implements the planNode interface. 230 func (*deleteRangeNode) Close(ctx context.Context) {}