@@ -16,6 +16,7 @@ package analyzer
16
16
17
17
import (
18
18
"fmt"
19
+ "slices"
19
20
"sort"
20
21
"strings"
21
22
"time"
@@ -203,6 +204,9 @@ func getCostedIndexScan(ctx *sql.Context, statsProv sql.StatsProvider, rt sql.Ta
203
204
if ! ok {
204
205
stat , err = uniformDistStatisticsForIndex (ctx , statsProv , iat , idx )
205
206
}
207
+ if err != nil {
208
+ return nil , nil , nil , err
209
+ }
206
210
err := c .cost (root , stat , idx )
207
211
if err != nil {
208
212
return nil , nil , nil , err
@@ -446,6 +450,8 @@ type indexCoster struct {
446
450
// prefix key of the best indexScan
447
451
bestPrefix int
448
452
underlyingName string
453
+ // whether the column following the prefix key is limited to a subrange
454
+ hasRange bool
449
455
}
450
456
451
457
// cost tries to build the lowest cardinality index scan for an expression
@@ -459,10 +465,11 @@ func (c *indexCoster) cost(f indexFilter, stat sql.Statistic, idx sql.Index) err
459
465
var prefix int
460
466
var err error
461
467
var ok bool
468
+ hasRange := false
462
469
463
470
switch f := f .(type ) {
464
471
case * iScanAnd :
465
- newHist , newFds , filters , prefix , err = c .costIndexScanAnd (c .ctx , f , stat , stat .Histogram (), ordinals , idx )
472
+ newHist , newFds , filters , prefix , hasRange , err = c .costIndexScanAnd (c .ctx , f , stat , stat .Histogram (), ordinals , idx )
466
473
if err != nil {
467
474
return err
468
475
}
@@ -491,12 +498,12 @@ func (c *indexCoster) cost(f indexFilter, stat sql.Statistic, idx sql.Index) err
491
498
newFds = & sql.FuncDepSet {}
492
499
}
493
500
494
- c .updateBest (stat , newHist , newFds , filters , prefix )
501
+ c .updateBest (stat , newHist , newFds , filters , prefix , hasRange )
495
502
496
503
return nil
497
504
}
498
505
499
- func (c * indexCoster ) updateBest (s sql.Statistic , hist []sql.HistogramBucket , fds * sql.FuncDepSet , filters sql.FastIntSet , prefix int ) {
506
+ func (c * indexCoster ) updateBest (s sql.Statistic , hist []sql.HistogramBucket , fds * sql.FuncDepSet , filters sql.FastIntSet , prefix int , hasRange bool ) {
500
507
if s == nil || filters .Len () == 0 {
501
508
return
502
509
}
@@ -510,6 +517,7 @@ func (c *indexCoster) updateBest(s sql.Statistic, hist []sql.HistogramBucket, fd
510
517
c .bestCnt = rowCnt
511
518
c .bestFilters = filters
512
519
c .bestPrefix = prefix
520
+ c .hasRange = hasRange
513
521
}
514
522
}()
515
523
@@ -534,6 +542,26 @@ func (c *indexCoster) updateBest(s sql.Statistic, hist []sql.HistogramBucket, fd
534
542
return
535
543
}
536
544
545
+ // If one index uses a strict superset of the filters of the other, we should always pick the superset.
546
+ // This is true even if the index with more filters isn't unique.
547
+ if prefix > c .bestPrefix && slices .Equal (c .bestStat .Columns ()[:c .bestPrefix ], s .Columns ()[:c .bestPrefix ]) {
548
+ update = true
549
+ return
550
+ }
551
+
552
+ if prefix == c .bestPrefix && slices .Equal (c .bestStat .Columns ()[:c .bestPrefix ], s .Columns ()[:c .bestPrefix ]) && hasRange && ! c .hasRange {
553
+ update = true
554
+ return
555
+ }
556
+
557
+ if c .bestPrefix > prefix && slices .Equal (c .bestStat .Columns ()[:prefix ], s .Columns ()[:prefix ]) {
558
+ return
559
+ }
560
+
561
+ if c .bestPrefix == prefix && slices .Equal (c .bestStat .Columns ()[:prefix ], s .Columns ()[:prefix ]) && ! hasRange && c .hasRange {
562
+ return
563
+ }
564
+
537
565
bestKey , bok := best .StrictKey ()
538
566
cmpKey , cok := cmp .StrictKey ()
539
567
if cok && ! bok {
@@ -575,6 +603,10 @@ func (c *indexCoster) updateBest(s sql.Statistic, hist []sql.HistogramBucket, fd
575
603
return
576
604
}
577
605
606
+ if filters .Len () < c .bestFilters .Len () {
607
+ return
608
+ }
609
+
578
610
if s .ColSet ().Len ()- filters .Len () < c .bestStat .ColSet ().Len ()- c .bestFilters .Len () {
579
611
// prefer 1 range filter over 1 column index (1 - 1 = 0)
580
612
// vs. 1 range filter over 2 column index (2 - 1 = 1)
@@ -1199,7 +1231,7 @@ func ordinalsForStat(stat sql.Statistic) map[string]int {
1199
1231
// updated statistic, the subset of applicable filters, the maximum prefix
1200
1232
// key created by a subset of equality filters (from conjunction only),
1201
1233
// or an error if applicable.
1202
- func (c * indexCoster ) costIndexScanAnd (ctx * sql.Context , filter * iScanAnd , s sql.Statistic , buckets []sql.HistogramBucket , ordinals map [string ]int , idx sql.Index ) ([]sql.HistogramBucket , * sql.FuncDepSet , sql.FastIntSet , int , error ) {
1234
+ func (c * indexCoster ) costIndexScanAnd (ctx * sql.Context , filter * iScanAnd , s sql.Statistic , buckets []sql.HistogramBucket , ordinals map [string ]int , idx sql.Index ) ([]sql.HistogramBucket , * sql.FuncDepSet , sql.FastIntSet , int , bool , error ) {
1203
1235
// first step finds the conjunctions that match index prefix columns.
1204
1236
// we divide into eqFilters and rangeFilters
1205
1237
@@ -1210,13 +1242,13 @@ func (c *indexCoster) costIndexScanAnd(ctx *sql.Context, filter *iScanAnd, s sql
1210
1242
for _ , or := range filter .orChildren {
1211
1243
childStat , _ , ok , err := c .costIndexScanOr (or .(* iScanOr ), s , buckets , ordinals , idx )
1212
1244
if err != nil {
1213
- return nil , nil , sql.FastIntSet {}, 0 , err
1245
+ return nil , nil , sql.FastIntSet {}, 0 , false , err
1214
1246
}
1215
1247
// if valid, INTERSECT
1216
1248
if ok {
1217
1249
ret , err = stats .Intersect (c .ctx , ret , childStat , s .Types ())
1218
1250
if err != nil {
1219
- return nil , nil , sql.FastIntSet {}, 0 , err
1251
+ return nil , nil , sql.FastIntSet {}, 0 , false , err
1220
1252
}
1221
1253
exact .Add (int (or .Id ()))
1222
1254
}
@@ -1237,12 +1269,8 @@ func (c *indexCoster) costIndexScanAnd(ctx *sql.Context, filter *iScanAnd, s sql
1237
1269
conjFDs = conj .getFds ()
1238
1270
}
1239
1271
1240
- if exact .Len ()+ conj .applied .Len () == filter .childCnt () {
1241
- // matched all filters
1242
- return conj .hist , conjFDs , sql .NewFastIntSet (int (filter .id )), conj .missingPrefix , nil
1243
- }
1244
-
1245
- return conj .hist , conjFDs , exact .Union (conj .applied ), conj .missingPrefix , nil
1272
+ hasRange := conj .ineqCols .Contains (conj .missingPrefix )
1273
+ return conj .hist , conjFDs , exact .Union (conj .applied ), conj .missingPrefix , hasRange , nil
1246
1274
}
1247
1275
1248
1276
func (c * indexCoster ) costIndexScanOr (filter * iScanOr , s sql.Statistic , buckets []sql.HistogramBucket , ordinals map [string ]int , idx sql.Index ) ([]sql.HistogramBucket , * sql.FuncDepSet , bool , error ) {
@@ -1253,7 +1281,7 @@ func (c *indexCoster) costIndexScanOr(filter *iScanOr, s sql.Statistic, buckets
1253
1281
for _ , child := range filter .children {
1254
1282
switch child := child .(type ) {
1255
1283
case * iScanAnd :
1256
- childBuckets , _ , ids , _ , err := c .costIndexScanAnd (c .ctx , child , s , buckets , ordinals , idx )
1284
+ childBuckets , _ , ids , _ , _ , err := c .costIndexScanAnd (c .ctx , child , s , buckets , ordinals , idx )
1257
1285
if err != nil {
1258
1286
return nil , nil , false , err
1259
1287
}
@@ -1664,6 +1692,7 @@ type conjCollector struct {
1664
1692
ordinals map [string ]int
1665
1693
missingPrefix int
1666
1694
constant sql.FastIntSet
1695
+ ineqCols sql.FastIntSet
1667
1696
eqVals []interface {}
1668
1697
nullable []bool
1669
1698
applied sql.FastIntSet
@@ -1732,6 +1761,7 @@ func (c *conjCollector) addEq(ctx *sql.Context, col string, val interface{}, nul
1732
1761
1733
1762
func (c * conjCollector ) addIneq (ctx * sql.Context , op IndexScanOp , col string , val interface {}) error {
1734
1763
ord := c .ordinals [col ]
1764
+ c .ineqCols .Add (ord )
1735
1765
if ord > 0 {
1736
1766
return nil
1737
1767
}
0 commit comments