[MDEV-6454] Performance degradation (suboptimal execution plan) on a query with expected range access Created: 2014-07-17  Updated: 2021-11-24  Resolved: 2014-10-22

Status: Closed
Project: MariaDB Server
Component/s: Optimizer
Affects Version/s: N/A
Fix Version/s: 10.1.1

Type: Bug Priority: Major
Reporter: Elena Stepanova Assignee: Sergei Petrunia
Resolution: Fixed Votes: 0
Labels: None

Attachments: File mdev6384_regression_1.dump     File mdev6384_regression_2.dump     File psergey-mdev6454-attempt3.diff     File psergey-mdev6454-fix-crash2.diff     File psergey-mdev6454-prototype1.diff    
Issue Links:
Relates
relates to MDEV-6480 Remove conditions for which range opt... Closed
relates to MDEV-6384 It seems like OPTIMIZER take into acc... Closed

 Description   

Observed on 10.0-mdev6384 development tree (10.0 with the patch for MDEV-6384 applied manually)
Query:

 SELECT `pk` , MAX( `col_bigint_key` ) FROM `table10000_innodb_int_autoinc`  WHERE ( `col_smallint_key`  IN ( 255 , 255 ) OR  ( `pk` = 144 ) ) AND ( `col_bigint_key`  IN ( 3 , 155 ) AND `col_bigint_key`  IN ( 255 , 8 , 0 ) ) AND ( `col_bigint_key` IS  NULL OR  ( `col_bigint_key` <> 1 ) ) OR ( `col_smallint_key` IS  NULL AND `pk`  BETWEEN 121 AND 4 + 255 ) GROUP BY 1;

Table:

CREATE TABLE `table10000_innodb_int_autoinc` (
  `col_smallint_key` smallint(6) DEFAULT NULL,
  `col_bigint_key` bigint(20) DEFAULT NULL,
  `col_varchar_64_key` varchar(64) DEFAULT NULL,
  `col_varchar_10` varchar(10) DEFAULT NULL,
  `col_varchar_10_key` varchar(10) DEFAULT NULL,
  `col_varchar_64` varchar(64) DEFAULT NULL,
  `col_bigint` bigint(20) DEFAULT NULL,
  `pk` int(11) NOT NULL AUTO_INCREMENT,
  `col_smallint` smallint(6) DEFAULT NULL,
  PRIMARY KEY (`pk`),
  KEY `col_smallint_key` (`col_smallint_key`),
  KEY `col_bigint_key` (`col_bigint_key`),
  KEY `col_varchar_64_key` (`col_varchar_64_key`),
  KEY `col_varchar_10_key` (`col_varchar_10_key`)
) ENGINE=InnoDB AUTO_INCREMENT=10001 DEFAULT CHARSET=latin1

(data dump is attached)

10.0

+------+-------------+-------------------------------+-------+-----------------------------------------+------------------+---------+------+------+----------+---------------------------------------------------------------------+
| id   | select_type | table                         | type  | possible_keys                           | key              | key_len | ref  | rows | filtered | Extra                                                               |
+------+-------------+-------------------------------+-------+-----------------------------------------+------------------+---------+------+------+----------+---------------------------------------------------------------------+
|    1 | SIMPLE      | table10000_innodb_int_autoinc | range | PRIMARY,col_smallint_key,col_bigint_key | col_smallint_key | 7       | NULL |   21 |   100.00 | Using index condition; Using where; Using temporary; Using filesort |
+------+-------------+-------------------------------+-------+-----------------------------------------+------------------+---------+------+------+----------+---------------------------------------------------------------------+

Execution:
21 rows in set (0.00 sec)

+----------------------------+-------+
| Variable_name              | Value |
+----------------------------+-------+
| Handler_commit             | 1     |
| Handler_delete             | 0     |
| Handler_discover           | 0     |
| Handler_external_lock      | 0     |
| Handler_icp_attempts       | 21    |
| Handler_icp_match          | 21    |
| Handler_mrr_init           | 0     |
| Handler_mrr_key_refills    | 0     |
| Handler_mrr_rowid_refills  | 0     |
| Handler_prepare            | 0     |
| Handler_read_first         | 0     |
| Handler_read_key           | 22    |
| Handler_read_last          | 0     |
| Handler_read_next          | 21    |
| Handler_read_prev          | 0     |
| Handler_read_rnd           | 21    |
| Handler_read_rnd_deleted   | 0     |
| Handler_read_rnd_next      | 22    |
| Handler_rollback           | 0     |
| Handler_savepoint          | 0     |
| Handler_savepoint_rollback | 0     |
| Handler_tmp_update         | 0     |
| Handler_tmp_write          | 21    |
| Handler_update             | 0     |
| Handler_write              | 0     |
+----------------------------+-------+

10.0-mdev6384

-----------

+------+-------------+-------------------------------+-------+-----------------------------------------+---------+---------+------+-------+----------+-------------+
| id   | select_type | table                         | type  | possible_keys                           | key     | key_len | ref  | rows  | filtered | Extra       |
+------+-------------+-------------------------------+-------+-----------------------------------------+---------+---------+------+-------+----------+-------------+
|    1 | SIMPLE      | table10000_innodb_int_autoinc | index | PRIMARY,col_smallint_key,col_bigint_key | PRIMARY | 4       | NULL | 10000 |     0.21 | Using where |
+------+-------------+-------------------------------+-------+-----------------------------------------+---------+---------+------+-------+----------+-------------+
1 row in set, 1 warning (0.00 sec)

Execution:
21 rows in set (0.06 sec)

+----------------------------+-------+
| Variable_name              | Value |
+----------------------------+-------+
| Handler_commit             | 1     |
| Handler_delete             | 0     |
| Handler_discover           | 0     |
| Handler_external_lock      | 0     |
| Handler_icp_attempts       | 0     |
| Handler_icp_match          | 0     |
| Handler_mrr_init           | 0     |
| Handler_mrr_key_refills    | 0     |
| Handler_mrr_rowid_refills  | 0     |
| Handler_prepare            | 0     |
| Handler_read_first         | 1     |
| Handler_read_key           | 0     |
| Handler_read_last          | 0     |
| Handler_read_next          | 10000 |
| Handler_read_prev          | 0     |
| Handler_read_rnd           | 0     |
| Handler_read_rnd_deleted   | 0     |
| Handler_read_rnd_next      | 0     |
| Handler_rollback           | 0     |
| Handler_savepoint          | 0     |
| Handler_savepoint_rollback | 0     |
| Handler_tmp_update         | 0     |
| Handler_tmp_write          | 0     |
| Handler_update             | 0     |
| Handler_write              | 0     |
+----------------------------+-------+

The effect seems to be stable. Persistent statistics doesn't help.



 Comments   
Comment by Sergei Petrunia [ 2014-07-18 ]

range optimizer:

PRIMARY
records=139
cost =

{io_count = 1.3946782975235641, avg_io_cost = 1, cpu_cost = 27.810000000000002, import_cost = 0, mem_cost = 0}

cost.total_cost() = 29.204678297523568

"col_smallint_key"
(gdb) print found_records
$69 = 21
(gdb) p cost
$70 =

{io_count = 22, avg_io_cost = 1, cpu_cost = 4.21, import_cost = 0, mem_cost = 0}

(gdb) p cost.total_cost()
$71 = 26.210000000000001

range optimizer picks range(col_smallint_key).

best_access_path():

test_if_skip_sort_order():
finds type=JT_ALL, quick select on idx=1 (col_smallint_key).

test_if_cheaper_ordering():
(gdb) print read_time
$79 = 26.211000000000002

checking key=0 (PRIMARY) // it produces the desired ordering

get_range_limit_read_cost (keynr=0, rows_limit=9709 /* = table_rows */) ...

quick_rows[0] = 139
tmp = ... = 1.3946782975235641

back to test_if_skip_sort_order()...
call to select->test_quick_select()...
... and we dont get the quick select on the PRIMARY key for some reason...

another call to test_if_skip_sort_order() ...
it makes the same choices as the previous one...

and calls select->test_quick_select() again.. which again returns no quick
select...

cursory examination inside shows that get_mm_tree()=NULL.

and then, we start a full index scan on key=PRIMARY.

Need to figure what goes on in the range optimizer.

Comment by Sergei Petrunia [ 2014-07-18 ]

Experimenting with the first call to range optimizer:

if you call
SQL_SELECT::test_quick_select( keys_to_use=

{map=31}

)
call to get_mm_tree() will produce two trees, on key=0 and key=1

while if you call
SQL_SELECT::test_quick_select( keys_to_use=

{map=1}

)
then get_mm_tree() returns NULL.

Comment by Sergei Petrunia [ 2014-07-18 ]

RE-writing the WHERE condition a bit:

WHERE 
  (col_smallint_key IN (255 , 255) OR  pk = 144) AND 
  (col_bigint_key  IN (3 , 155) AND 
  col_bigint_key IN (255, 8, 0)) AND 
  (col_bigint_key IS  NULL OR  ( col_bigint_key <> 1 ) ) 
  OR 
  (col_smallint_key IS  NULL AND pk  BETWEEN 121 AND 4 + 255 ) 
GROUP BY 1;

(gdb) p dbug_print_item(item)
  $129 = 0x19ad100 "((`j20`.`table10000_innodb_int_autoinc`.`col_smallint_key` in (255,255)) or multiple equal(144, `j20`.`table10000_innodb_int_autoinc`.`pk`))"
get_mm_tree() produces different results for this part of the condition.

with key_map=

{PRIMARY} it returns NULL (which is correct, because one
can't infer anything for "non_sargable_cond OR cond(PRIMARY)".

with key_map={all-keys} it returns and index_merge tree.

The next AND-ed condition is:

$13 = 0x19ad100 "(`j10`.`table10000_innodb_int_autoinc`.`col_bigint_key` in (3,155))"
key_map=\{PRIMARY}:  tree=NULL
key_map=\{all-keys}:  tree= {range on index#2}


The next AND-ed condition is:

$15 = 0x19ad100 "(`j10`.`table10000_innodb_int_autoinc`.`col_bigint_key` in (255,8,0))"
key_map=\{PRIMARY}:  tree=NULL
key_map=\{all-keys}:  tree= {range on index#2}


And here, after tree_and(), the variant with key_map={all-keys} get
SEL_TREE(IMPOSSIBLE). THis is correct, see above conditions on col_bigint_key.
the variant with key_map={PRIMARY}

will remain with TREE=NULL.

Comment by Sergei Petrunia [ 2014-07-18 ]

Prototype fix (removes the problem but ugly)

Comment by Sergei Petrunia [ 2014-07-18 ]

The prototype fix patch includes fix for MDEV-6384, so it should be applied against clean 10.0, this revision:

revno: 4290 [merge]
revision-id: knielsen@knielsen-hq.org-20140711100647-nf3rdaf5ep26pgty
message:
MDEV-5262, MDEV-5914, MDEV-5941, MDEV-6020: Deadlocks during parallel replication causing replication to fail.

Comment by Elena Stepanova [ 2014-07-19 ]

The test case below causes an assertion failure on revno 4290 + psergey-mdev6454-prototype1.diff:

sql/opt_range.h:605: virtual void QUICK_INDEX_SORT_SELECT::need_sorted_output(): Assertion `0' failed.
 
#6  0x00007f2a588d8621 in *__GI___assert_fail (assertion=0x10239cf "0", file=<optimized out>, line=605, function=0x1025940 "virtual void QUICK_INDEX_SORT_SELECT::need_sorted_output()") at assert.c:81
#7  0x0000000000991b41 in QUICK_INDEX_SORT_SELECT::need_sorted_output (this=0x7f2a45013a00) at sql/opt_range.h:605
#8  0x00000000006de19e in test_if_skip_sort_order (tab=0x7f2a4533d048, order=0x7f2a450dc4d8, select_limit=28, no_changes=false, map=0x7f2a450a4908) at sql/sql_select.cc:20373
#9  0x00000000006de67b in create_sort_index (thd=0x7f2a52bec070, join=0x7f2a450dc5e0, order=0x7f2a450dc4d8, filesort_limit=18446744073709551615, select_limit=18446744073709551615, is_order_by=true) at sql/sql_select.cc:20528
#10 0x00000000006b43be in JOIN::exec_inner (this=0x7f2a450dc5e0) at sql/sql_select.cc:3047
#11 0x00000000006b1ad4 in JOIN::exec (this=0x7f2a450dc5e0) at sql/sql_select.cc:2367
#12 0x00000000006b4e53 in mysql_select (thd=0x7f2a52bec070, rref_pointer_array=0x7f2a52bf06e8, tables=0x7f2a450db318, wild_num=0, fields=..., conds=0x7f2a450dc2a0, og_num=1, order=0x7f2a450dc4d8, group=0x0, having=0x0, proc_param=0x0, select_options=2147748608, result=0x7f2a450dc5c0, unit=0x7f2a52befd88, select_lex=0x7f2a52bf0470) at sql/sql_select.cc:3304
#13 0x00000000006ab443 in handle_select (thd=0x7f2a52bec070, lex=0x7f2a52befcc0, result=0x7f2a450dc5c0, setup_tables_done_option=0) at sql/sql_select.cc:373
#14 0x00000000006801f9 in execute_sqlcom_select (thd=0x7f2a52bec070, all_tables=0x7f2a450db318) at sql/sql_parse.cc:5263
#15 0x00000000006785f0 in mysql_execute_command (thd=0x7f2a52bec070) at sql/sql_parse.cc:2554
#16 0x0000000000682983 in mysql_parse (thd=0x7f2a52bec070, rawbuf=0x7f2a450db088 "SELECT pk FROM t1 WHERE pk BETWEEN 6 AND 106\nAND ( state = 'Illinois' OR code IS NULL ) ORDER BY pk", length=99, parser_state=0x7f2a5a856610) at sql/sql_parse.cc:6409
#17 0x0000000000675891 in dispatch_command (command=COM_QUERY, thd=0x7f2a52bec070, packet=0x7f2a5267c071 "SELECT pk FROM t1 WHERE pk BETWEEN 6 AND 106\nAND ( state = 'Illinois' OR code IS NULL ) ORDER BY pk", packet_length=99) at sql/sql_parse.cc:1309
#18 0x0000000000674c36 in do_command (thd=0x7f2a52bec070) at sql/sql_parse.cc:1006
#19 0x0000000000790f49 in do_handle_one_connection (thd_arg=0x7f2a52bec070) at sql/sql_connect.cc:1379
#20 0x0000000000790c9c in handle_one_connection (arg=0x7f2a52bec070) at sql/sql_connect.cc:1293
#21 0x0000000000cc285e in pfs_spawn_thread (arg=0x7f2a4ce84df0) at storage/perfschema/pfs.cc:1860
#22 0x00007f2a5a48fb50 in start_thread (arg=<optimized out>) at pthread_create.c:304
#23 0x00007f2a58987a7d in clone () at ../sysdeps/unix/sysv/linux/x86_64/clone.S:112

--source include/have_innodb.inc
 
CREATE TABLE t1 (
  state varchar(64), code varchar(2), pk int(11) NOT NULL,
  PRIMARY KEY (pk), KEY (state), KEY (code)
) ENGINE=InnoDB;
 
INSERT INTO t1 VALUES 
('North Dakota','ND',76),('Hawaii','HI',77),('Michigan','MI',78),
('Texas','TX',79),('Arkansas','AR',80),('Arizona','AZ',81),
('South Carolina','SC',82),('Illinois','IL',83),('Tennessee','TN',84),
('Kansas','KS',85),('Delaware','DE',86),('Maine','ME',87),
('Georgia','GA',88),('Alabama','AL',89),('Utah','UT',90),
('Pennsylvania','PA',91),('Delaware','DE',92),('Mississippi','MS',93),
('Wisconsin','WI',94),('Wyoming','WY',95),('Colorado','CO',96),
('Georgia','GA',97),('South Dakota','SD',98),('Alaska','AK',99),
('Arizona','AZ',100),('Nebraska','NE',101),('Washington','WA',102),
('Oklahoma','OK',103);
 
SELECT pk FROM t1 WHERE pk BETWEEN 6 AND 106
AND ( state = 'Illinois' OR code IS NULL ) ORDER BY pk;

Comment by Elena Stepanova [ 2014-07-19 ]

The following query still shows performance regression on 10.0 + revno 4290 + psergey-mdev6454-prototype1.diff:

SELECT `pk` , MAX( `col_bigint_key` ) FROM `table10000_int_autoinc`  WHERE ( `col_varchar_64_key`  LIKE CONCAT ('Idaho' , '%' )  ) AND ( `col_varchar_10_key` NOT BETWEEN 'go' AND 'cexzwbi' ) AND (  ( `col_smallint_key` < 255 ) AND `col_bigint_key`  BETWEEN -60 AND 500 ) AND ( `col_bigint_key`  IN ( 255 , 3 , 56 , 4 , -12 ) AND `pk`  BETWEEN 1 AND 70 ) AND ( `pk`  BETWEEN 1 AND 22 OR  `pk` <> 1 ) GROUP BY 1;

psergey-mdev6454-prototype1.diff:

+------+-------------+------------------------+-------+------------------------------------------------------------------------------------+---------+---------+------+------+-------------+
| id   | select_type | table                  | type  | possible_keys                                                                      | key     | key_len | ref  | rows | Extra       |
+------+-------------+------------------------+-------+------------------------------------------------------------------------------------+---------+---------+------+------+-------------+
|    1 | SIMPLE      | table10000_int_autoinc | range | PRIMARY,col_bigint_key,col_smallint_key,col_varchar_10_key,col_varchar_64_key,key1 | PRIMARY | 0       | NULL | 5000 | Using where |
+------+-------------+------------------------+-------+------------------------------------------------------------------------------------+---------+---------+------+------+-------------+
 
Execution: 
Empty set (0.07 sec)

10.0:

+------+-------------+------------------------+-------+------------------------------------------------------------------------------------+----------------+---------+------+------+---------------------------------------------------------------------+
| id   | select_type | table                  | type  | possible_keys                                                                      | key            | key_len | ref  | rows | Extra                                                               |
+------+-------------+------------------------+-------+------------------------------------------------------------------------------------+----------------+---------+------+------+---------------------------------------------------------------------+
|    1 | SIMPLE      | table10000_int_autoinc | range | PRIMARY,col_bigint_key,col_smallint_key,col_varchar_10_key,col_varchar_64_key,key1 | col_bigint_key | 13      | NULL |    5 | Using index condition; Using where; Using temporary; Using filesort |
+------+-------------+------------------------+-------+------------------------------------------------------------------------------------+----------------+---------+------+------+---------------------------------------------------------------------+
 
Execution:
Empty set (0.00 sec)

Datadump is attached as mdev6384_regression_2.dump

Comment by Sergei Petrunia [ 2014-07-19 ]

An idea for an alternative to psergey-mdev6454-prototype1.diff:
if range optimizer finds that a certain Item* $item produces a tree with SEL_TREE(IMPOSSIBLE), remove $item from the WHERE clause completely.

Comment by Sergei Petrunia [ 2014-07-19 ]

Fix the crash ( psergey-mdev6454-fix-crash2.diff)

Comment by Sergei Petrunia [ 2014-07-19 ]

Also discovered a (simple) problem with cost calculations in the prototype patch. Will fix.

Comment by Sergei Petrunia [ 2014-07-20 ]

the problem is: get_range_limit_read_cost() has this condition

  if (table->quick_keys.is_set(keynr))
  {
    double quick_rows= table->quick_rows[keynr];
    double tmp;
    if (tab && table->quick_n_ranges[keynr] == 1)

It is supposed to fire when there was a ref(const) which is equivalent to the quick select. Apparently quick_n_ranges==1 is a wrong condition.

Comment by Sergei Petrunia [ 2014-07-24 ]

A patch that should fix all regression and crash issues: psergey-mdev6454-attempt3.diff . The patch is against 10.0 tree, revision 4290, knielsen@knielsen-hq.org-20140711100647-nf3rdaf5ep26pgty.

Comment by Sergei Petrunia [ 2014-07-24 ]

elenst, could you please make another test run with psergey-mdev6454-attempt3.diff? I'm interested in both performance regressions and crashes.

Comment by Sergei Petrunia [ 2014-08-27 ]

Ok, I've now made a 10.1 tree with a fix for MDEV-6480, fix for this bug, and fix for MDEV-6384 all pushed. It is here: https://github.com/MariaDB/server/tree/bb-10.1-orderby-fixes.

elenst, could you make another testing pass?

Comment by Sergei Petrunia [ 2014-10-22 ]

Checked EXPLAINs again - there are no regressions in current 10.1. Too bad we can't add these testcases to the test suite.

Generated at Thu Feb 08 07:12:02 UTC 2024 using Jira 8.20.16#820016-sha1:9d11dbea5f4be3d4cc21f03a88dd11d8c8687422.