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) {}