|
49 | 49 |
|
50 | 50 | #include "../jrd/optimizer/Optimizer.h"
|
51 | 51 |
|
| 52 | +#include <cmath> |
| 53 | + |
52 | 54 | using namespace Firebird;
|
53 | 55 | using namespace Jrd;
|
54 | 56 |
|
@@ -366,25 +368,103 @@ InversionCandidate* Retrieval::getInversion()
|
366 | 368 | return invCandidate;
|
367 | 369 | }
|
368 | 370 |
|
369 |
| -IndexTableScan* Retrieval::getNavigation() |
| 371 | +IndexTableScan* Retrieval::getNavigation(const InversionCandidate* candidate) |
370 | 372 | {
|
371 | 373 | if (!navigationCandidate)
|
372 | 374 | return nullptr;
|
373 | 375 |
|
374 |
| - IndexScratch* const scratch = navigationCandidate->scratch; |
| 376 | + const auto scratch = navigationCandidate->scratch; |
| 377 | + |
| 378 | + const auto streamCardinality = csb->csb_rpt[stream].csb_cardinality; |
| 379 | + |
| 380 | + // If the table looks like empty during preparation time, we cannot be sure about |
| 381 | + // its real cardinality during execution. So, unless we have some index-based |
| 382 | + // filtering applied, let's better be pessimistic and avoid external sorting |
| 383 | + // due to likely cardinality under-estimation. |
| 384 | + const bool avoidSorting = (streamCardinality <= MINIMUM_CARDINALITY && !candidate->inversion); |
| 385 | + |
| 386 | + if (!(scratch->index->idx_runtime_flags & idx_plan_navigate) && !avoidSorting) |
| 387 | + { |
| 388 | + // Check whether the navigational index scan is cheaper than the external sort |
| 389 | + // and give up if it's not worth the efforts. |
| 390 | + // |
| 391 | + // We ignore candidate->cost in the calculations below as it belongs |
| 392 | + // to both parts being compared. |
| 393 | + |
| 394 | + fb_assert(candidate); |
| 395 | + |
| 396 | + // Restore the original selectivity of the inversion, |
| 397 | + // i.e. before the navigation candidate was accounted |
| 398 | + auto selectivity = candidate->selectivity / navigationCandidate->selectivity; |
| 399 | + |
| 400 | + // Non-indexed booleans are checked before sorting, so they improve the selectivity |
| 401 | + |
| 402 | + double factor = MAXIMUM_SELECTIVITY; |
| 403 | + for (auto iter = optimizer->getConjuncts(outerFlag, innerFlag); iter.hasData(); ++iter) |
| 404 | + { |
| 405 | + if (!(iter & Optimizer::CONJUNCT_USED) && |
| 406 | + !candidate->matches.exist(iter) && |
| 407 | + iter->computable(csb, stream, true) && |
| 408 | + iter->containsStream(stream)) |
| 409 | + { |
| 410 | + factor *= Optimizer::getSelectivity(*iter); |
| 411 | + } |
| 412 | + } |
| 413 | + |
| 414 | + Optimizer::adjustSelectivity(selectivity, factor, streamCardinality); |
| 415 | + |
| 416 | + // Don't consider external sorting if optimization for first rows is requested |
| 417 | + // and we have no local filtering applied |
| 418 | + |
| 419 | + if (!optimizer->favorFirstRows() || selectivity < MAXIMUM_SELECTIVITY) |
| 420 | + { |
| 421 | + // Estimate amount of records to be sorted |
| 422 | + const auto cardinality = streamCardinality * selectivity; |
| 423 | + |
| 424 | + // We optimistically assume that records will be cached during sorting |
| 425 | + const auto sortCost = |
| 426 | + // record copying (to the sort buffer and back) |
| 427 | + cardinality * COST_FACTOR_MEMCOPY * 2 + |
| 428 | + // quicksort algorithm is O(n*log(n)) in average |
| 429 | + cardinality * log2(cardinality) * COST_FACTOR_QUICKSORT; |
| 430 | + |
| 431 | + // During navigation we fetch an index leaf page per every record being returned, |
| 432 | + // thus add the estimated cardinality to the cost |
| 433 | + auto navigationCost = navigationCandidate->cost + |
| 434 | + streamCardinality * candidate->selectivity; |
| 435 | + |
| 436 | + if (optimizer->favorFirstRows()) |
| 437 | + { |
| 438 | + // Reset the cost to represent a single record retrieval |
| 439 | + navigationCost = DEFAULT_INDEX_COST; |
| 440 | + |
| 441 | + // We know that some local filtering is applied, so we need |
| 442 | + // to adjust the cost as we need to walk the index |
| 443 | + // until the first matching record is found |
| 444 | + const auto fullIndexCost = navigationCandidate->scratch->cardinality; |
| 445 | + const auto ratio = MAXIMUM_SELECTIVITY / selectivity; |
| 446 | + const auto fraction = ratio / streamCardinality; |
| 447 | + const auto walkCost = fullIndexCost * fraction * navigationCandidate->selectivity; |
| 448 | + navigationCost += walkCost; |
| 449 | + } |
| 450 | + |
| 451 | + if (sortCost < navigationCost) |
| 452 | + return nullptr; |
| 453 | + } |
| 454 | + } |
375 | 455 |
|
376 | 456 | // Looks like we can do a navigational walk. Flag that
|
377 | 457 | // we have used this index for navigation, and allocate
|
378 | 458 | // a navigational rsb for it.
|
379 | 459 | scratch->index->idx_runtime_flags |= idx_navigate;
|
380 | 460 |
|
381 |
| - const USHORT key_length = |
382 |
| - ROUNDUP(BTR_key_length(tdbb, relation, scratch->index), sizeof(SLONG)); |
| 461 | + const auto indexNode = makeIndexScanNode(scratch); |
383 | 462 |
|
384 |
| - InversionNode* const index_node = makeIndexScanNode(scratch); |
| 463 | + const USHORT keyLength = |
| 464 | + ROUNDUP(BTR_key_length(tdbb, relation, scratch->index), sizeof(SLONG)); |
385 | 465 |
|
386 | 466 | return FB_NEW_POOL(getPool())
|
387 |
| - IndexTableScan(csb, getAlias(), stream, relation, index_node, key_length, |
| 467 | + IndexTableScan(csb, getAlias(), stream, relation, indexNode, keyLength, |
388 | 468 | navigationCandidate->selectivity);
|
389 | 469 | }
|
390 | 470 |
|
|
0 commit comments