id select_type table type possible_keys key key_len ref rows Extra
1 SIMPLE t indexNULL aa 5 NULL 9 Using index
Handler_read_first 0
Handler_read_key 0
Handler_read_last 1
Handler_read_next 0
Handler_read_prev 9
Handler_read_retry 0
Handler_read_rnd 0
Handler_read_rnd_deleted 0
Handler_read_rnd_next 0
MySQL 8.0.23
explain select * from t orderby a desc;
id select_type table partitions type possible_keys key key_len ref rows filtered Extra
1 SIMPLE t NULLindexNULL ad 5 NULL 10 100.00 Using index
Handler_read_first 1
Handler_read_key 1
Handler_read_last 0
Handler_read_next 9
Handler_read_prev 0
Handler_read_rnd 0
Handler_read_rnd_next 0
ANALYZE/statistics collection doesn't change the outcome.
Same happens on MariaDB with MyISAM/Aria (ASC index is chosen), but it's not comparable to MySQL because MySQL doesn't support DESC indexes on MyISAM tables.
Attachments
Issue Links
is caused by
MDEV-13756Implement descending index: KEY (a DESC, b ASC)
Closed
relates to
MDEV-30845filesort still select when desc/asc index matches ORDER BY
MySQL's [WL#1074|https://dev.mysql.com/worklog/task/?id=1074] mentions a limitation:
{quote}
when user has two indexes, one ASC and another DESC over the same column, optimizer won't necessary will be able to pick the right index
{quote}
I suppose it applies to MariaDB as well, even though MDEV-13756 doesn't say so.
However, there might still be room for improvement. At least in some cases MySQL is able to pick up the DESC index out of two, while MariaDB isn't:
{code:sql}
# Remove the include if you run the test on MySQL 8.0
--source include/have_innodb.inc
create table t (a int, key aa(a), key ad(a desc)) engine=InnoDB;
insert into t values (1),(5),(2),(8),(4),(6),(7),(9),(3);
explain select * from t order by a desc;
flush status;
select * from t order by a desc;
show status like 'Handler_read%';
# Cleanup
drop table t;
{code}
{code:sql|title=MariaDB preview-10.8-MDEV-13756-desc-indexes 43444ff5}
explain select * from t order by a desc;
id select_type table type possible_keys key key_len ref rows Extra
1 SIMPLE t index NULL aa 5 NULL 9 Using index
{code:sql|title=MySQL 8.0.23}
explain select * from t order by a desc;
id select_type table partitions type possible_keys key key_len ref rows filtered Extra
1 SIMPLE t NULL index NULL ad 5 NULL 10 100.00 Using index
ANALYZE/statistics collection doesn't change the outcome.
Same happens on MariaDB with MyISAM/Aria, but it's not comparable to MySQL because MySQL doesn't support DESC indexes on MyISAM tables.
MySQL's [WL#1074|https://dev.mysql.com/worklog/task/?id=1074] mentions a limitation:
{quote}
when user has two indexes, one ASC and another DESC over the same column, optimizer won't necessary will be able to pick the right index
{quote}
I suppose it applies to MariaDB as well, even though MDEV-13756 doesn't say so.
However, there might still be room for improvement. At least in some cases MySQL is able to pick up the DESC index out of two, while MariaDB isn't:
{code:sql}
# Remove the include if you run the test on MySQL 8.0
--source include/have_innodb.inc
create table t (a int, key aa(a), key ad(a desc)) engine=InnoDB;
insert into t values (1),(5),(2),(8),(4),(6),(7),(9),(3);
explain select * from t order by a desc;
flush status;
select * from t order by a desc;
show status like 'Handler_read%';
# Cleanup
drop table t;
{code}
{code:sql|title=MariaDB preview-10.8-MDEV-13756-desc-indexes 43444ff5}
explain select * from t order by a desc;
id select_type table type possible_keys key key_len ref rows Extra
1 SIMPLE t index NULL aa 5 NULL 9 Using index
{code:sql|title=MySQL 8.0.23}
explain select * from t order by a desc;
id select_type table partitions type possible_keys key key_len ref rows filtered Extra
1 SIMPLE t NULL index NULL ad 5 NULL 10 100.00 Using index
ANALYZE/statistics collection doesn't change the outcome.
Same happens on MariaDB with MyISAM/Aria (ASC index is chosen), but it's not comparable to MySQL because MySQL doesn't support DESC indexes on MyISAM tables.
But a universal fix should be cost based, with a new handler method to retrieve the cost of the reverse scan, proper estimation for index_scan_time, etc.
psergei, what do you think, shall we have a direction heuristics here until a cost based choice is implemented?
Sergei Golubchik
added a comment - We cannot do it in 10.8 properly. A simple heuristics would solve this particular case:
--- a/sql/sql_select.cc
+++ b/sql/sql_select.cc
@@ -29143,6 +29143,7 @@ test_if_cheaper_ordering(const JOIN_TAB *tab, ORDER *order, TABLE *table,
(select_limit <= MY_MIN(quick_records,best_records) ?
keyinfo->user_defined_key_parts < best_key_parts :
quick_records < best_records) ||
+ direction > best_key_direction ||
(!is_best_covering && is_covering))
{
possible_key.add("chosen", true);
But a universal fix should be cost based, with a new handler method to retrieve the cost of the reverse scan, proper estimation for index_scan_time , etc.
psergei , what do you think, shall we have a direction heuristics here until a cost based choice is implemented?
// We assume forward scan is faster than backward even if the
// key is longer. This should be taken into account in cost
// calculation.
direction > best_key_direction) {
best_key = nr;
best_key_parts = keyinfo->user_defined_key_parts;
if (saved_best_key_parts) *saved_best_key_parts = used_key_parts;
best_records = quick_records;
is_best_covering = is_covering;
best_key_direction = direction;
best_select_limit = select_limit;
Sergei Petrunia
added a comment - Note: in MySQL-8.4 it's still done with a basic heuristic:
if (best_key < 0 ||
(select_limit <= min(quick_records, best_records)
? keyinfo->user_defined_key_parts < best_key_parts
: quick_records < best_records) ||
// We assume forward scan is faster than backward even if the
// key is longer. This should be taken into account in cost
// calculation.
direction > best_key_direction) {
best_key = nr;
best_key_parts = keyinfo->user_defined_key_parts;
if (saved_best_key_parts) *saved_best_key_parts = used_key_parts;
best_records = quick_records;
is_best_covering = is_covering;
best_key_direction = direction;
best_select_limit = select_limit;
- - If there is no ref key and no usable keys has yet been found and
- there is either a group by or a FORCE_INDEX
- If the new cost is better than read_time
*/
- if (range_cost < read_time)
+ if (range_cost < read_time ||
+ (range_cost == read_time && direction > best_key_direction))
{
read_time= range_cost;
possible_key.add("chosen", true);
To integrate the direction matching in the cost based model, it seems we need a KEY_PREV_FIND_COST with a higher cost than KEY_NEXT_FIND_COST below, which will be used in the calculation when the key direction is the opposite of the order direction
Yuchen Pei
added a comment - - edited We can adapt the heuristics to the cost based model, see the following commit
upstream/bb-11.1-mdev-27419 cdeaefee21ce425211fdedd0092397ad0ac7dc2b MDEV-27419 [demo/check-ci] choose desc key for desc ordering
modified sql/sql_select.cc
@@ -32369,11 +32369,10 @@ test_if_cheaper_ordering(const JOIN_TAB *tab, ORDER *order, TABLE *table,
/*
We will try use the key if:
- - If there is no ref key and no usable keys has yet been found and
- there is either a group by or a FORCE_INDEX
- If the new cost is better than read_time
*/
- if (range_cost < read_time)
+ if (range_cost < read_time ||
+ (range_cost == read_time && direction > best_key_direction))
{
read_time= range_cost;
possible_key.add("chosen", true);
To integrate the direction matching in the cost based model, it seems we need a KEY_PREV_FIND_COST with a higher cost than KEY_NEXT_FIND_COST below, which will be used in the calculation when the key direction is the opposite of the order direction
IO_AND_CPU_COST handler::ha_keyread_time(uint index, ulong ranges,
ha_rows rows,
ulonglong blocks)
{
if (rows < ranges)
rows= ranges;
IO_AND_CPU_COST cost= keyread_time(index, ranges, rows, blocks);
cost.cpu+= ranges * KEY_LOOKUP_COST + (rows - ranges) * KEY_NEXT_FIND_COST;
return cost;
}
Here's a partial stack leading to the function:
0 in handler::ha_keyread_time of /home/ycp/source/mariadb-server/11.1/src/sql/handler.cc:3439
1 in cost_for_index_read of /home/ycp/source/mariadb-server/11.1/src/sql/sql_select.cc:8171
2 in get_range_limit_read_cost of /home/ycp/source/mariadb-server/11.1/src/sql/sql_select.cc:32034
3 in test_if_cheaper_ordering of /home/ycp/source/mariadb-server/11.1/src/sql/sql_select.cc:32355
4 in test_if_skip_sort_order of /home/ycp/source/mariadb-server/11.1/src/sql/sql_select.cc:26813
5 in JOIN::optimize_stage2 of /home/ycp/source/mariadb-server/11.1/src/sql/sql_select.cc:3433
Any thoughts monty and serg ?
People
Yuchen Pei
Elena Stepanova
Votes:
0Vote for this issue
Watchers:
4Start watching this issue
Dates
Created:
Updated:
Git Integration
Error rendering 'com.xiplink.jira.git.jira_git_plugin:git-issue-webpanel'. Please contact your Jira administrators.
{"report":{"fcp":1064.1000001430511,"ttfb":419.30000019073486,"pageVisibility":"visible","entityId":106345,"key":"jira.project.issue.view-issue","isInitial":true,"threshold":1000,"elementTimings":{},"userDeviceMemory":8,"userDeviceProcessors":64,"apdex":0.5,"journeyId":"009425ad-3309-478e-a699-0fc2b722e79e","navigationType":0,"readyForUser":1192.9000000953674,"redirectCount":0,"resourceLoadedEnd":1202.9000000953674,"resourceLoadedStart":425,"resourceTiming":[{"duration":114.40000009536743,"initiatorType":"link","name":"https://jira.mariadb.org/s/2c21342762a6a02add1c328bed317ffd-CDN/lu2cib/820016/12ta74/0a8bac35585be7fc6c9cc5a0464cd4cf/_/download/contextbatch/css/_super/batch.css","startTime":425,"connectEnd":0,"connectStart":0,"domainLookupEnd":0,"domainLookupStart":0,"fetchStart":425,"redirectEnd":0,"redirectStart":0,"requestStart":0,"responseEnd":539.4000000953674,"responseStart":0,"secureConnectionStart":0},{"duration":114.70000004768372,"initiatorType":"link","name":"https://jira.mariadb.org/s/7ebd35e77e471bc30ff0eba799ebc151-CDN/lu2cib/820016/12ta74/494e4c556ecbb29f90a3d3b4f09cb99c/_/download/contextbatch/css/jira.browse.project,project.issue.navigator,jira.view.issue,jira.general,jira.global,atl.general,-_super/batch.css?agile_global_admin_condition=true&jag=true&jira.create.linked.issue=true&slack-enabled=true&whisper-enabled=true","startTime":425.2000000476837,"connectEnd":0,"connectStart":0,"domainLookupEnd":0,"domainLookupStart":0,"fetchStart":425.2000000476837,"redirectEnd":0,"redirectStart":0,"requestStart":0,"responseEnd":539.9000000953674,"responseStart":0,"secureConnectionStart":0},{"duration":168.59999990463257,"initiatorType":"script","name":"https://jira.mariadb.org/s/0917945aaa57108d00c5076fea35e069-CDN/lu2cib/820016/12ta74/0a8bac35585be7fc6c9cc5a0464cd4cf/_/download/contextbatch/js/_super/batch.js?locale=en","startTime":425.40000009536743,"connectEnd":425.40000009536743,"connectStart":425.40000009536743,"domainLookupEnd":425.40000009536743,"domainLookupStart":425.40000009536743,"fetchStart":425.40000009536743,"redirectEnd":0,"redirectStart":0,"requestStart":425.40000009536743,"responseEnd":594,"responseStart":594,"secureConnectionStart":425.40000009536743},{"duration":289.7000000476837,"initiatorType":"script","name":"https://jira.mariadb.org/s/2d8175ec2fa4c816e8023260bd8c1786-CDN/lu2cib/820016/12ta74/494e4c556ecbb29f90a3d3b4f09cb99c/_/download/contextbatch/js/jira.browse.project,project.issue.navigator,jira.view.issue,jira.general,jira.global,atl.general,-_super/batch.js?agile_global_admin_condition=true&jag=true&jira.create.linked.issue=true&locale=en&slack-enabled=true&whisper-enabled=true","startTime":425.5,"connectEnd":425.5,"connectStart":425.5,"domainLookupEnd":425.5,"domainLookupStart":425.5,"fetchStart":425.5,"redirectEnd":0,"redirectStart":0,"requestStart":425.5,"responseEnd":715.2000000476837,"responseStart":715.2000000476837,"secureConnectionStart":425.5},{"duration":293.5,"initiatorType":"script","name":"https://jira.mariadb.org/s/a9324d6758d385eb45c462685ad88f1d-CDN/lu2cib/820016/12ta74/c92c0caa9a024ae85b0ebdbed7fb4bd7/_/download/contextbatch/js/atl.global,-_super/batch.js?locale=en","startTime":425.80000019073486,"connectEnd":425.80000019073486,"connectStart":425.80000019073486,"domainLookupEnd":425.80000019073486,"domainLookupStart":425.80000019073486,"fetchStart":425.80000019073486,"redirectEnd":0,"redirectStart":0,"requestStart":425.80000019073486,"responseEnd":719.3000001907349,"responseStart":719.3000001907349,"secureConnectionStart":425.80000019073486},{"duration":293.60000014305115,"initiatorType":"script","name":"https://jira.mariadb.org/s/d41d8cd98f00b204e9800998ecf8427e-CDN/lu2cib/820016/12ta74/1.0/_/download/batch/jira.webresources:calendar-en/jira.webresources:calendar-en.js","startTime":426,"connectEnd":426,"connectStart":426,"domainLookupEnd":426,"domainLookupStart":426,"fetchStart":426,"redirectEnd":0,"redirectStart":0,"requestStart":426,"responseEnd":719.6000001430511,"responseStart":719.6000001430511,"secureConnectionStart":426},{"duration":293.7999999523163,"initiatorType":"script","name":"https://jira.mariadb.org/s/d41d8cd98f00b204e9800998ecf8427e-CDN/lu2cib/820016/12ta74/1.0/_/download/batch/jira.webresources:calendar-localisation-moment/jira.webresources:calendar-localisation-moment.js","startTime":426.2000000476837,"connectEnd":426.2000000476837,"connectStart":426.2000000476837,"domainLookupEnd":426.2000000476837,"domainLookupStart":426.2000000476837,"fetchStart":426.2000000476837,"redirectEnd":0,"redirectStart":0,"requestStart":426.2000000476837,"responseEnd":720,"responseStart":720,"secureConnectionStart":426.2000000476837},{"duration":310.5,"initiatorType":"link","name":"https://jira.mariadb.org/s/b04b06a02d1959df322d9cded3aeecc1-CDN/lu2cib/820016/12ta74/a2ff6aa845ffc9a1d22fe23d9ee791fc/_/download/contextbatch/css/jira.global.look-and-feel,-_super/batch.css","startTime":426.30000019073486,"connectEnd":0,"connectStart":0,"domainLookupEnd":0,"domainLookupStart":0,"fetchStart":426.30000019073486,"redirectEnd":0,"redirectStart":0,"requestStart":0,"responseEnd":736.8000001907349,"responseStart":0,"secureConnectionStart":0},{"duration":294,"initiatorType":"script","name":"https://jira.mariadb.org/rest/api/1.0/shortcuts/820016/47140b6e0a9bc2e4913da06536125810/shortcuts.js?context=issuenavigation&context=issueaction","startTime":426.5,"connectEnd":426.5,"connectStart":426.5,"domainLookupEnd":426.5,"domainLookupStart":426.5,"fetchStart":426.5,"redirectEnd":0,"redirectStart":0,"requestStart":426.5,"responseEnd":720.5,"responseStart":720.5,"secureConnectionStart":426.5},{"duration":310.2999999523163,"initiatorType":"link","name":"https://jira.mariadb.org/s/3ac36323ba5e4eb0af2aa7ac7211b4bb-CDN/lu2cib/820016/12ta74/d176f0986478cc64f24226b3d20c140d/_/download/contextbatch/css/com.atlassian.jira.projects.sidebar.init,-_super,-project.issue.navigator,-jira.view.issue/batch.css?jira.create.linked.issue=true","startTime":426.7000000476837,"connectEnd":0,"connectStart":0,"domainLookupEnd":0,"domainLookupStart":0,"fetchStart":426.7000000476837,"redirectEnd":0,"redirectStart":0,"requestStart":0,"responseEnd":737,"responseStart":0,"secureConnectionStart":0},{"duration":294.2999999523163,"initiatorType":"script","name":"https://jira.mariadb.org/s/5d5e8fe91fbc506585e83ea3b62ccc4b-CDN/lu2cib/820016/12ta74/d176f0986478cc64f24226b3d20c140d/_/download/contextbatch/js/com.atlassian.jira.projects.sidebar.init,-_super,-project.issue.navigator,-jira.view.issue/batch.js?jira.create.linked.issue=true&locale=en","startTime":426.80000019073486,"connectEnd":426.80000019073486,"connectStart":426.80000019073486,"domainLookupEnd":426.80000019073486,"domainLookupStart":426.80000019073486,"fetchStart":426.80000019073486,"redirectEnd":0,"redirectStart":0,"requestStart":426.80000019073486,"responseEnd":721.1000001430511,"responseStart":721.1000001430511,"secureConnectionStart":426.80000019073486},{"duration":591.2000000476837,"initiatorType":"script","name":"https://jira.mariadb.org/s/d41d8cd98f00b204e9800998ecf8427e-CDN/lu2cib/820016/12ta74/1.0/_/download/batch/jira.webresources:bigpipe-js/jira.webresources:bigpipe-js.js","startTime":433.10000014305115,"connectEnd":433.10000014305115,"connectStart":433.10000014305115,"domainLookupEnd":433.10000014305115,"domainLookupStart":433.10000014305115,"fetchStart":433.10000014305115,"redirectEnd":0,"redirectStart":0,"requestStart":433.10000014305115,"responseEnd":1024.3000001907349,"responseStart":1024.3000001907349,"secureConnectionStart":433.10000014305115},{"duration":640.7000000476837,"initiatorType":"script","name":"https://jira.mariadb.org/s/d41d8cd98f00b204e9800998ecf8427e-CDN/lu2cib/820016/12ta74/1.0/_/download/batch/jira.webresources:bigpipe-init/jira.webresources:bigpipe-init.js","startTime":433.2000000476837,"connectEnd":433.2000000476837,"connectStart":433.2000000476837,"domainLookupEnd":433.2000000476837,"domainLookupStart":433.2000000476837,"fetchStart":433.2000000476837,"redirectEnd":0,"redirectStart":0,"requestStart":433.2000000476837,"responseEnd":1073.9000000953674,"responseStart":1073.9000000953674,"secureConnectionStart":433.2000000476837},{"duration":275.7000000476837,"initiatorType":"xmlhttprequest","name":"https://jira.mariadb.org/rest/webResources/1.0/resources","startTime":749,"connectEnd":749,"connectStart":749,"domainLookupEnd":749,"domainLookupStart":749,"fetchStart":749,"redirectEnd":0,"redirectStart":0,"requestStart":749,"responseEnd":1024.7000000476837,"responseStart":1024.7000000476837,"secureConnectionStart":749},{"duration":176.09999990463257,"initiatorType":"script","name":"https://www.google-analytics.com/analytics.js","startTime":1057.4000000953674,"connectEnd":0,"connectStart":0,"domainLookupEnd":0,"domainLookupStart":0,"fetchStart":1057.4000000953674,"redirectEnd":0,"redirectStart":0,"requestStart":0,"responseEnd":1233.5,"responseStart":0,"secureConnectionStart":0},{"duration":122.40000009536743,"initiatorType":"script","name":"https://jira.mariadb.org/s/d41d8cd98f00b204e9800998ecf8427e-CDN/lu2cib/820016/12ta74/e65b778d185daf5aee24936755b43da6/_/download/contextbatch/js/browser-metrics-plugin.contrib,-_super,-atl.general/batch.js?agile_global_admin_condition=true&jag=true&slack-enabled=true&whisper-enabled=true","startTime":1080.5,"connectEnd":1080.5,"connectStart":1080.5,"domainLookupEnd":1080.5,"domainLookupStart":1080.5,"fetchStart":1080.5,"redirectEnd":0,"redirectStart":0,"requestStart":1080.5,"responseEnd":1202.9000000953674,"responseStart":1202.9000000953674,"secureConnectionStart":1080.5}],"fetchStart":0,"domainLookupStart":0,"domainLookupEnd":0,"connectStart":0,"connectEnd":0,"requestStart":206,"responseStart":419,"responseEnd":430,"domLoading":422,"domInteractive":1282,"domContentLoadedEventStart":1282,"domContentLoadedEventEnd":1349,"domComplete":1819,"loadEventStart":1819,"loadEventEnd":1820,"userAgent":"Mozilla/5.0 AppleWebKit/537.36 (KHTML, like Gecko; compatible; ClaudeBot/1.0; +claudebot@anthropic.com)","marks":[{"name":"bigPipe.sidebar-id.start","time":1239.9000000953674},{"name":"bigPipe.sidebar-id.end","time":1240.6000001430511},{"name":"bigPipe.activity-panel-pipe-id.start","time":1240.8000001907349},{"name":"bigPipe.activity-panel-pipe-id.end","time":1246},{"name":"activityTabFullyLoaded","time":1372.9000000953674}],"measures":[],"correlationId":"ca344956dbde0e","effectiveType":"4g","downlink":10,"rtt":0,"serverDuration":145,"dbReadsTimeInMs":24,"dbConnsTimeInMs":35,"applicationHash":"9d11dbea5f4be3d4cc21f03a88dd11d8c8687422","experiments":[]}}
We cannot do it in 10.8 properly. A simple heuristics would solve this particular case:
--- a/sql/sql_select.cc
+++ b/sql/sql_select.cc
@@ -29143,6 +29143,7 @@ test_if_cheaper_ordering(const JOIN_TAB *tab, ORDER *order, TABLE *table,
(select_limit <= MY_MIN(quick_records,best_records) ?
keyinfo->user_defined_key_parts < best_key_parts :
quick_records < best_records) ||
+ direction > best_key_direction ||
(!is_best_covering && is_covering))
{
But a universal fix should be cost based, with a new handler method to retrieve the cost of the reverse scan, proper estimation for index_scan_time, etc.
psergei, what do you think, shall we have a direction heuristics here until a cost based choice is implemented?