Skip to content

Commit

Permalink
Avoid sorting in more situations.
Browse files Browse the repository at this point in the history
Consider the query:

  SELECT v FROM t WHERE k = 1 ORDER BY v

If there is an index on (k, v) we know that an exact match has been
performed on k and thus the results are already ordered on v and thus no
sorting is required.

Added an additional return value to planNode.Ordering() to indicate the
prefix of the ordering for which an exact match has been performed. A
bit of code reorg allowed sortNode.wrap() to take advantage of this new
info.

See #2636.
  • Loading branch information
petermattis committed Sep 28, 2015
1 parent ef44d75 commit 4b94a3d
Show file tree
Hide file tree
Showing 10 changed files with 80 additions and 72 deletions.
9 changes: 7 additions & 2 deletions sql/distinct.go
Original file line number Diff line number Diff line change
Expand Up @@ -33,11 +33,16 @@ func (*planner) distinct(n *parser.Select, p planNode) planNode {
planNode: p,
suffixSeen: make(map[string]struct{}),
}
if len(p.Ordering()) != 0 {
ordering, prefix := p.Ordering()
if len(ordering) != 0 {
d.columnsInOrder = make([]bool, len(p.Columns()))
}
for _, p := range p.Ordering() {
for _, p := range ordering {
if p == 0 {
if prefix > 0 {
prefix--
continue
}
break
}
if p < 0 {
Expand Down
2 changes: 1 addition & 1 deletion sql/group.go
Original file line number Diff line number Diff line change
Expand Up @@ -110,7 +110,7 @@ func (n *groupNode) Columns() []string {
return n.columns
}

func (n *groupNode) Ordering() []int {
func (n *groupNode) Ordering() ([]int, int) {
return n.plan.Ordering()
}

Expand Down
8 changes: 4 additions & 4 deletions sql/join.go
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,7 @@ type indexJoinNode struct {
err error
}

func makeIndexJoin(indexScan *scanNode) (*indexJoinNode, error) {
func makeIndexJoin(indexScan *scanNode, exactPrefix int) (*indexJoinNode, error) {
// Copy the index scan node into a new table scan node and reset the fields
// that were set up for the index scan.
table := &scanNode{}
Expand All @@ -47,7 +47,7 @@ func makeIndexJoin(indexScan *scanNode) (*indexJoinNode, error) {
table.spans = nil
table.reverse = false
table.isSecondaryIndex = false
table.initOrdering()
table.initOrdering(0)

// We want to the index scan to keep the same render target indexes for
// columns which are part of the primary key or part of the index. This
Expand Down Expand Up @@ -88,7 +88,7 @@ func makeIndexJoin(indexScan *scanNode) (*indexJoinNode, error) {
}
}

indexScan.initOrdering()
indexScan.initOrdering(exactPrefix)

primaryKeyPrefix := proto.Key(MakeIndexKeyPrefix(table.desc.ID, table.index.ID))

Expand All @@ -104,7 +104,7 @@ func (n *indexJoinNode) Columns() []string {
return n.table.Columns()
}

func (n *indexJoinNode) Ordering() []int {
func (n *indexJoinNode) Ordering() ([]int, int) {
return n.index.Ordering()
}

Expand Down
9 changes: 6 additions & 3 deletions sql/plan.go
Original file line number Diff line number Diff line change
Expand Up @@ -158,9 +158,12 @@ type planNode interface {
// guaranteed to be equal to the length of the tuple returned by Values().
Columns() []string
// The indexes of the columns the output is ordered by. Indexes are 1-based
// and negative indexes indicate descending ordering. The []int result may be
// nil if no ordering has been performed.
Ordering() []int
// and negative indexes indicate descending ordering. The ordering return
// value may be nil if no ordering has been performed. The prefix return
// value indicates the prefix of the ordering for which an exact match has
// been performed via filtering. This prefix may safely be ignored for
// ordering considerations.
Ordering() (ordering []int, prefix int)
// Values returns the values at the current row. The result is only valid
// until the next call to Next().
Values() parser.DTuple
Expand Down
8 changes: 5 additions & 3 deletions sql/scan.go
Original file line number Diff line number Diff line change
Expand Up @@ -148,6 +148,7 @@ type scanNode struct {
columns []string
columnIDs []ColumnID
ordering []int
exactPrefix int
err error
indexKey []byte // the index key of the current row
kvs []client.KeyValue // the raw key/value pairs
Expand All @@ -171,8 +172,8 @@ func (n *scanNode) Columns() []string {
return n.columns
}

func (n *scanNode) Ordering() []int {
return n.ordering
func (n *scanNode) Ordering() ([]int, int) {
return n.ordering, n.exactPrefix
}

func (n *scanNode) Values() parser.DTuple {
Expand Down Expand Up @@ -406,10 +407,11 @@ func (n *scanNode) initTargets(targets parser.SelectExprs) error {

// initOrdering initializes the ordering info using the selected index. This
// must be called after index selection is performed.
func (n *scanNode) initOrdering() {
func (n *scanNode) initOrdering(exactPrefix int) {
if n.index == nil {
return
}
n.exactPrefix = exactPrefix
n.columnIDs = n.index.fullColumnIDs()
n.ordering = n.computeOrdering(n.columnIDs)
if n.reverse {
Expand Down
54 changes: 11 additions & 43 deletions sql/select.go
Original file line number Diff line number Diff line change
Expand Up @@ -68,7 +68,7 @@ func (p *planner) Select(n *parser.Select) (planNode, error) {
if group != nil {
ordering = group.desiredOrdering
} else if sort != nil {
ordering = sort.Ordering()
ordering, _ = sort.Ordering()
}
plan, err := p.selectIndex(scan, ordering)
if err != nil {
Expand All @@ -94,7 +94,7 @@ func (p *planner) Select(n *parser.Select) (planNode, error) {
func (p *planner) selectIndex(s *scanNode, ordering []int) (planNode, error) {
if s.desc == nil || (s.filter == nil && ordering == nil) {
// No table or no where-clause and no ordering.
s.initOrdering()
s.initOrdering(0)
return s, nil
}

Expand Down Expand Up @@ -194,10 +194,10 @@ func (p *planner) selectIndex(s *scanNode, ordering []int) (planNode, error) {
}

if c.covering {
s.initOrdering()
s.initOrdering(c.exactPrefix)
return s, nil
}
return makeIndexJoin(s)
return makeIndexJoin(s, c.exactPrefix)
}

type indexConstraint struct {
Expand Down Expand Up @@ -243,6 +243,7 @@ type indexInfo struct {
cost float64
covering bool // indicates whether the index covers the required qvalues
reverse bool
exactPrefix int
}

func (v *indexInfo) init(s *scanNode) {
Expand Down Expand Up @@ -285,50 +286,17 @@ func (v *indexInfo) analyzeRanges(exprs []parser.Exprs) {
// if it matches the ordering requested by the query. Non-matching orderings
// increase the cost of using the index.
func (v *indexInfo) analyzeOrdering(scan *scanNode, ordering []int) {
// Compute the ordering provided by the index.
indexOrdering := scan.computeOrdering(v.index.fullColumnIDs())

// Compute the prefix of the index for which we have exact constraints. This
// prefix is inconsequential for ordering because the values are identical.
prefix := exactPrefix(v.constraints)
v.exactPrefix = exactPrefix(v.constraints)

// Compute the ordering provided by the index.
indexOrdering := scan.computeOrdering(v.index.fullColumnIDs())

// Compute how much of the index ordering matches the requested ordering for
// both forward and reverse scans.
fwdMatch, fwdPrefix, fwdIndexOrdering := 0, prefix, indexOrdering
for fwdMatch < len(ordering) && fwdMatch < len(fwdIndexOrdering) {
if ordering[fwdMatch] == fwdIndexOrdering[fwdMatch] {
// The index ordering matched the desired ordering.
fwdPrefix = 0
fwdMatch++
continue
}
// The index ordering did not match the desired ordering. Check if we're
// still considering a prefix of the index for which there was an exact
// match (and thus ordering is inconsequential).
if fwdPrefix == 0 {
break
}
fwdPrefix--
fwdIndexOrdering = fwdIndexOrdering[1:]
}

revMatch, revPrefix, revIndexOrdering := 0, prefix, indexOrdering
for revMatch < len(ordering) && revMatch < len(revIndexOrdering) {
if ordering[revMatch] == -revIndexOrdering[revMatch] {
// The index ordering matched the desired ordering.
revPrefix = 0
revMatch++
continue
}
// The index ordering did not match the desired ordering. Check if we're
// still considering a prefix of the index for which there was an exact
// match (and thus ordering is inconsequential).
if revPrefix == 0 {
break
}
revPrefix--
revIndexOrdering = revIndexOrdering[1:]
}
fwdMatch := computeOrderingMatch(ordering, indexOrdering, v.exactPrefix, +1)
revMatch := computeOrderingMatch(ordering, indexOrdering, v.exactPrefix, -1)

// Weight the cost by how much of the ordering matched.
//
Expand Down
46 changes: 33 additions & 13 deletions sql/sort.go
Original file line number Diff line number Diff line change
Expand Up @@ -137,11 +137,11 @@ func (n *sortNode) Columns() []string {
return n.columns
}

func (n *sortNode) Ordering() []int {
func (n *sortNode) Ordering() ([]int, int) {
if n == nil {
return nil
return nil, 0
}
return n.ordering
return n.ordering, 0
}

func (n *sortNode) Values() parser.DTuple {
Expand Down Expand Up @@ -192,16 +192,15 @@ func (n *sortNode) wrap(plan planNode) planNode {
if n != nil {
// Check to see if the requested ordering is compatible with the existing
// ordering.
existingOrdering := plan.Ordering()
for i := range n.ordering {
if i >= len(existingOrdering) || n.ordering[i] != existingOrdering[i] {
if log.V(2) {
log.Infof("Sort: %d != %d", existingOrdering, n.ordering)
}
n.plan = plan
n.needSort = true
return n
}
existingOrdering, prefix := plan.Ordering()
if log.V(2) {
log.Infof("Sort: existing=%d (%d) desired=%d", existingOrdering, prefix, n.ordering)
}
match := computeOrderingMatch(n.ordering, existingOrdering, prefix, +1)
if match < len(n.ordering) {
n.plan = plan
n.needSort = true
return n
}

if len(n.columns) < len(plan.Columns()) {
Expand Down Expand Up @@ -236,3 +235,24 @@ func (n *sortNode) initValues() bool {
n.plan = v
return true
}

func computeOrderingMatch(desired, existing []int, prefix, reverse int) int {
match := 0
for match < len(desired) && match < len(existing) {
if desired[match] == reverse*existing[match] {
// The existing ordering matched the desired ordering.
prefix = 0
match++
continue
}
// The existing ordering did not match the desired ordering. Check if we're
// still considering a prefix of the existing ordering for which there was
// an exact match (and thus ordering is inconsequential).
if prefix == 0 {
break
}
prefix--
existing = existing[1:]
}
return match
}
2 changes: 1 addition & 1 deletion sql/testdata/aggregate
Original file line number Diff line number Diff line change
Expand Up @@ -180,7 +180,7 @@ EXPLAIN SELECT MIN(x) FROM xyz WHERE (y, z) = (2, 3)
1 scan xyz@zyx /3/2-/3/3

query ITT
EXPLAIN SELECT MAX(x) FROM xyz WHERE (y, z) = (2, 3)
EXPLAIN SELECT MAX(x) FROM xyz WHERE (z, y) = (3, 2)
----
0 group MAX(x)
1 revscan xyz@zyx /3/2-/3/3
10 changes: 10 additions & 0 deletions sql/testdata/order_by
Original file line number Diff line number Diff line change
Expand Up @@ -234,3 +234,13 @@ query I
SELECT a FROM abc ORDER BY a DESC OFFSET 1
----
1

query I
EXPLAIN SELECT c FROM abc WHERE b = 2 ORDER BY c
----
0 scan abc@bc /2-/3

query I
EXPLAIN SELECT c FROM abc WHERE b = 2 ORDER BY c DESC
----
0 revscan abc@bc /2-/3
4 changes: 2 additions & 2 deletions sql/values.go
Original file line number Diff line number Diff line change
Expand Up @@ -77,8 +77,8 @@ func (n *valuesNode) Columns() []string {
return n.columns
}

func (n *valuesNode) Ordering() []int {
return nil
func (n *valuesNode) Ordering() ([]int, int) {
return nil, 0
}

func (n *valuesNode) Values() parser.DTuple {
Expand Down

0 comments on commit 4b94a3d

Please sign in to comment.