From b58f8cc79b86abfbb5e7d1f3ccc4275b310e6c6b Mon Sep 17 00:00:00 2001 From: Yuchen Pei Date: Wed, 21 Oct 2026 17:34:50 +1100 Subject: [PATCH] MDEV-41366 Only allow exact access for iterated partition index scan with prefix The idea of Case 2 in can_skip_merging_scans is that when partition column key prefix is fixed, and the infix that is also the "partition by range" column, we can scan each partition in order. To that end, we restrict access flags to exact accesses. Also reset m_unordered_prefix_len in the single-partition "unordered" scan case as it skips can_skip_merging_scans, which would otherwise leave a stale length for handle_unordered_prev. Also include HA_READ_KEY_OR_PREV in reverse_order flags, so that it can be considered and rejected for Case 2. --- .../main/partition_index_scan,desc.rdiff | 24 +- mysql-test/main/partition_index_scan.result | 235 +++++++++++++++++- mysql-test/main/partition_index_scan.test | 183 +++++++++++++- sql/ha_partition.cc | 23 +- 4 files changed, 449 insertions(+), 16 deletions(-) diff --git a/mysql-test/main/partition_index_scan,desc.rdiff b/mysql-test/main/partition_index_scan,desc.rdiff index 99093b20a3982..b54723f1cb10f 100644 --- a/mysql-test/main/partition_index_scan,desc.rdiff +++ b/mysql-test/main/partition_index_scan,desc.rdiff @@ -287,8 +287,8 @@ show status like 'handler_read_%'; Variable_name Value Handler_read_first 0 --Handler_read_key 6 -+Handler_read_key 7 +-Handler_read_key 8 ++Handler_read_key 11 Handler_read_last 0 Handler_read_next 0 Handler_read_prev 0 @@ -301,9 +301,12 @@ ) PARTITION BY RANGE COLUMNS(d) ( PARTITION `p1` VALUES LESS THAN (10), PARTITION `p2` VALUES LESS THAN (20), -@@ -688,8 +688,8 @@ +@@ -686,10 +686,10 @@ + show status like 'handler_read_%'; + Variable_name Value Handler_read_first 0 - Handler_read_key 1 +-Handler_read_key 1 ++Handler_read_key 2 Handler_read_last 0 -Handler_read_next 2 -Handler_read_prev 0 @@ -312,6 +315,15 @@ Handler_read_retry 0 Handler_read_rnd 0 Handler_read_rnd_deleted 0 +@@ -698,7 +698,7 @@ + set @js='$out'; + select json_extract(@js,'$**.partitions_index_scan_method'); + json_extract(@js,'$**.partitions_index_scan_method') +-["iterate_over_partitions"] ++["merge_ordered_scans"] + ## Case 2: Unordered scans in ha_partition::index_read_map + flush status; + select * from t1 where c = 120 ORDER BY d DESC LIMIT 1; @@ -725,8 +725,8 @@ Handler_read_first 0 Handler_read_key 1 @@ -479,7 +491,7 @@ Handler_read_prev 0 Handler_read_retry 0 Handler_read_rnd 0 -@@ -1202,18 +1202,18 @@ +@@ -1202,20 +1202,20 @@ # # MDEV-40479: Reverse prefix scan across partitions # @@ -503,3 +515,5 @@ +11 +15 DROP TABLE t1; + # + # MDEV-41366 Wrong result with Case 2 of non-merging partition index scan diff --git a/mysql-test/main/partition_index_scan.result b/mysql-test/main/partition_index_scan.result index 4450568bdc5fb..dba3b683765f6 100644 --- a/mysql-test/main/partition_index_scan.result +++ b/mysql-test/main/partition_index_scan.result @@ -630,8 +630,8 @@ select json_extract(@js,'$**.partitions_index_scan_method'); json_extract(@js,'$**.partitions_index_scan_method') ["iterate_over_partitions"] DROP TABLE t1; -## Case 2: calls to ha_partition::read_range_first and -## ha_partition::index_read_map, via loose index scan +## Case 2 loose index scan: calls to read_range_first iterates; +## index_read_map with AFTER_KEY is merged CREATE TABLE t1 ( a INT, b INT, @@ -651,7 +651,7 @@ a MIN(b) show status like 'handler_read_%'; Variable_name Value Handler_read_first 0 -Handler_read_key 6 +Handler_read_key 8 Handler_read_last 0 Handler_read_next 0 Handler_read_prev 0 @@ -662,7 +662,7 @@ Handler_read_rnd_next 0 set @js='$out'; select json_extract(@js,'$**.partitions_index_scan_method'); json_extract(@js,'$**.partitions_index_scan_method') -["iterate_over_partitions"] +["both"] DROP TABLE t1; CREATE TABLE t1 ( c int, @@ -676,7 +676,8 @@ PARTITION `p4` VALUES LESS THAN (40), PARTITION `p5` VALUES LESS THAN MAXVALUE ); insert into t1 select t2.seq, t3.seq from seq_100_to_200 t2, seq_1_to_50 t3; -## Case 2: Unordered scans with MRR +## Case 2: iterated partition scans with MRR with ASC key; +## merged scan with index_read_map with DESC key flush status; select * from t1 where c=113 and d between 25 and 35 order by d limit 3; c d @@ -1217,3 +1218,227 @@ c 11 6 DROP TABLE t1; +# +# MDEV-41366 Wrong result with Case 2 of non-merging partition index scan +# +# prefix-only PREFIX_LAST_OR_PREV, merged scan +CREATE TABLE t1 (a INT, b INT, c INT, PRIMARY KEY(a,c), KEY(b,c)) ENGINE=InnoDB +PARTITION BY RANGE(c) (PARTITION p0 VALUES LESS THAN (10), +PARTITION p1 VALUES LESS THAN (20)); +INSERT INTO t1 VALUES (1,1,5),(2,0,12); +SELECT c FROM t1 FORCE INDEX(b) WHERE b=1 AND c<=15 ORDER BY c DESC; +c +5 +set @js='$out'; +select json_extract(@js,'$**.partitions_index_scan_method'); +json_extract(@js,'$**.partitions_index_scan_method') +["merge_ordered_scans"] +DROP TABLE t1; +# same but with desc key +CREATE TABLE t1 (a INT, b INT, c INT, PRIMARY KEY(a,c), KEY(b desc,c desc)) ENGINE=InnoDB +PARTITION BY RANGE(c) (PARTITION p0 VALUES LESS THAN (10), +PARTITION p1 VALUES LESS THAN (20)); +INSERT INTO t1 VALUES (1,1,5),(2,0,12); +SELECT c FROM t1 FORCE INDEX(b) WHERE b=0 AND c>=2 ORDER BY c; +c +12 +set @js='$out'; +select json_extract(@js,'$**.partitions_index_scan_method'); +json_extract(@js,'$**.partitions_index_scan_method') +["merge_ordered_scans"] +DROP TABLE t1; +# prefix-only KEY_OR_NEXT / PREFIX_LAST_OR_PREV (opt_sum) +# merged scan +CREATE TABLE t1 (a INT, b INT, c INT NOT NULL, PRIMARY KEY(a,c), KEY k(b,c)) +ENGINE=InnoDB +PARTITION BY RANGE(c) (PARTITION p0 VALUES LESS THAN (10), +PARTITION p1 VALUES LESS THAN (20), +PARTITION p2 VALUES LESS THAN (30)); +# no b=1 in p0 or p2 +INSERT INTO t1 VALUES (1,2,5),(2,1,15),(3,0,22); +EXPLAIN +SELECT MIN(c) FROM t1 WHERE b=1; +id select_type table type possible_keys key key_len ref rows Extra +1 SIMPLE NULL NULL NULL NULL NULL NULL NULL Select tables optimized away +FLUSH STATUS; +SELECT MIN(c) FROM t1 WHERE b=1; +MIN(c) +15 +# 3 (merged scan: p0, p1, p2) +SHOW STATUS LIKE 'Handler_read_key'; +Variable_name Value +Handler_read_key 3 +EXPLAIN +SELECT MAX(c) FROM t1 WHERE b=1; +id select_type table type possible_keys key key_len ref rows Extra +1 SIMPLE NULL NULL NULL NULL NULL NULL NULL Select tables optimized away +FLUSH STATUS; +SELECT MAX(c) FROM t1 WHERE b=1; +MAX(c) +15 +# 3 (merged scan: p0, p1, p2) +SHOW STATUS LIKE 'Handler_read_key'; +Variable_name Value +Handler_read_key 3 +DROP TABLE t1; +# index_first with prefix does not qualify Case 2 +# neither does index_read_map with AFTER_KEY +CREATE TABLE t1 (a INT, b INT, KEY(a,b)) ENGINE=InnoDB +PARTITION BY RANGE COLUMNS(b) (PARTITION p1 VALUES LESS THAN (100), +PARTITION p2 VALUES LESS THAN (200), +PARTITION p3 VALUES LESS THAN MAXVALUE); +INSERT INTO t1 SELECT seq % 3 + 1, seq FROM seq_1_to_300; +ANALYZE TABLE t1 PERSISTENT FOR ALL; +Table Op Msg_type Msg_text +test.t1 analyze status Engine-independent statistics collected +test.t1 analyze status OK +EXPLAIN SELECT a, MIN(b) FROM t1 GROUP BY a; +id select_type table type possible_keys key key_len ref rows Extra +1 SIMPLE t1 range NULL a 10 NULL 4 Using index for group-by +SELECT a, MIN(b) FROM t1 GROUP BY a; +a MIN(b) +1 3 +2 1 +3 2 +set @js='$out'; +select json_extract(@js,'$**.partitions_index_scan_method'); +json_extract(@js,'$**.partitions_index_scan_method') +["merge_ordered_scans"] +DROP TABLE t1; +# index_first with prefix does not qualify Case 2 +# neither does index_read_map with AFTER_KEY +CREATE TABLE t1 (a INT, c INT, KEY(a,c)) ENGINE=InnoDB +PARTITION BY RANGE COLUMNS(c) (PARTITION p1 VALUES LESS THAN (100), +PARTITION p2 VALUES LESS THAN (200), +PARTITION p3 VALUES LESS THAN MAXVALUE); +INSERT INTO t1 SELECT 1, seq FROM seq_1_to_99; +INSERT INTO t1 SELECT 2, seq FROM seq_100_to_199; +INSERT INTO t1 SELECT 3, seq FROM seq_1_to_99; +ANALYZE TABLE t1 PERSISTENT FOR ALL; +Table Op Msg_type Msg_text +test.t1 analyze status Engine-independent statistics collected +test.t1 analyze status OK +EXPLAIN SELECT a, MIN(c) FROM t1 GROUP BY a; +id select_type table type possible_keys key key_len ref rows Extra +1 SIMPLE t1 range NULL a 10 NULL 4 Using index for group-by +SELECT a, MIN(c) FROM t1 GROUP BY a; +a MIN(c) +1 1 +2 100 +3 1 +set @js='$out'; +select json_extract(@js,'$**.partitions_index_scan_method'); +json_extract(@js,'$**.partitions_index_scan_method') +["merge_ordered_scans"] +DROP TABLE t1; +# AFTER_KEY / BEFORE_KEY merged scan +CREATE TABLE t1 (a INT, b INT, c INT NOT NULL, PRIMARY KEY(a,c), KEY k(b,c)) +ENGINE=InnoDB +PARTITION BY RANGE(c) (PARTITION p0 VALUES LESS THAN (10), +PARTITION p1 VALUES LESS THAN (20), +PARTITION p2 VALUES LESS THAN (30)); +INSERT INTO t1 VALUES +(1,1,5),(2,2,7),(3,2,10),(4,1,15),(5,2,18),(6,0,22),(7,1,25); +EXPLAIN +SELECT MIN(c) FROM t1 WHERE b=1 AND c>5; +id select_type table type possible_keys key key_len ref rows Extra +1 SIMPLE NULL NULL NULL NULL NULL NULL NULL Select tables optimized away +FLUSH STATUS; +SELECT MIN(c) FROM t1 WHERE b=1 AND c>5; +MIN(c) +15 +# 3 (merged: p0, p1, p2) +SHOW STATUS LIKE 'Handler_read_key'; +Variable_name Value +Handler_read_key 3 +EXPLAIN +SELECT MAX(c) FROM t1 WHERE b=1 AND c<25; +id select_type table type possible_keys key key_len ref rows Extra +1 SIMPLE NULL NULL NULL NULL NULL NULL NULL Select tables optimized away +FLUSH STATUS; +SELECT MAX(c) FROM t1 WHERE b=1 AND c<25; +MAX(c) +15 +# 3 (merged: p0, p1, p2) +SHOW STATUS LIKE 'Handler_read_key'; +Variable_name Value +Handler_read_key 3 +DROP TABLE t1; +# KEY_OR_PREV needs a reverse partition scan. +# Ordered scan (HASH) +CREATE TABLE t1 (a INT, KEY k(a)) ENGINE=InnoDB +PARTITION BY HASH(a) PARTITIONS 3; +INSERT INTO t1 SELECT seq FROM seq_1_to_9; +HANDLER t1 OPEN; +HANDLER t1 READ k <= (5); +a +5 +HANDLER t1 READ k PREV; +a +4 +HANDLER t1 READ k PREV; +a +3 +HANDLER t1 READ k <= (0); +a +HANDLER t1 CLOSE; +DROP TABLE t1; +# KEY_OR_PREV merged scan +CREATE TABLE t1 (a INT, b INT, c INT, PRIMARY KEY(a,c), KEY k(b,c)) +ENGINE=InnoDB +PARTITION BY RANGE(c) (PARTITION p0 VALUES LESS THAN (10), +PARTITION p1 VALUES LESS THAN (20)); +INSERT INTO t1 VALUES (1,1,5),(2,2,7),(3,1,15); +HANDLER t1 OPEN; +HANDLER t1 READ k <= (1,17); +a b c +3 1 15 +HANDLER t1 READ k PREV; +a b c +1 1 5 +HANDLER t1 CLOSE; +DROP TABLE t1; +# A range spanning several prefix values: +# QUICK_SELECT_DESC reads PREFIX_LAST_OR_PREV (2,15), keypart_map=b,c +CREATE TABLE t1 (a INT, b INT, c INT, PRIMARY KEY(a,c), KEY k(b,c)) ENGINE=InnoDB +PARTITION BY RANGE(c) (PARTITION p0 VALUES LESS THAN (10), +PARTITION p1 VALUES LESS THAN (20)); +INSERT INTO t1 VALUES (1,1,7),(2,2,7),(3,1,12),(4,2,12); +EXPLAIN SELECT b, c FROM t1 FORCE INDEX(k) +WHERE b BETWEEN 1 AND 2 AND c BETWEEN 5 AND 15 ORDER BY b DESC, c DESC; +id select_type table type possible_keys key key_len ref rows Extra +1 SIMPLE t1 range k k 9 NULL 4 Using where; Using index +SELECT b, c FROM t1 FORCE INDEX(k) +WHERE b BETWEEN 1 AND 2 AND c BETWEEN 5 AND 15 ORDER BY b DESC, c DESC; +b c +2 12 +2 7 +1 12 +1 7 +set @js='$out'; +select json_extract(@js,'$**.partitions_index_scan_method'); +json_extract(@js,'$**.partitions_index_scan_method') +["merge_ordered_scans"] +DROP TABLE t1; +# reset m_unordered_prefix_len: single partition scan skips +# can_skip_merging_scans() +CREATE TABLE t1 (a INT, b INT, c INT, PRIMARY KEY(a,c), +KEY k(b,c), KEY kc(c,b)) ENGINE=InnoDB +PARTITION BY RANGE(c) (PARTITION p0 VALUES LESS THAN (10), +PARTITION p1 VALUES LESS THAN (20)); +INSERT INTO t1 VALUES (1,1,5),(2,1,12),(3,2,13); +HANDLER t1 OPEN; +# 2 partitions, Case 2 on k(b,c): m_unordered_prefix_len = len(b) +HANDLER t1 READ k = (1); +a b c +1 1 5 +# full key of kc binds c: single partition p1, reset +HANDLER t1 READ kc = (13,2); +a b c +3 2 13 +# expect (2,1,12) +HANDLER t1 READ kc PREV; +a b c +2 1 12 +HANDLER t1 CLOSE; +DROP TABLE t1; diff --git a/mysql-test/main/partition_index_scan.test b/mysql-test/main/partition_index_scan.test index 571dc014b7a0d..642a07fe3891d 100644 --- a/mysql-test/main/partition_index_scan.test +++ b/mysql-test/main/partition_index_scan.test @@ -1,5 +1,6 @@ --source include/have_partition.inc --source include/have_sequence.inc +--source include/have_innodb.inc --source include/no_view_protocol.inc --disable_ps2_protocol --disable_cursor_protocol @@ -251,8 +252,8 @@ select json_extract(@js,'$**.partitions_index_scan_method'); DROP TABLE t1; ---echo ## Case 2: calls to ha_partition::read_range_first and ---echo ## ha_partition::index_read_map, via loose index scan +--echo ## Case 2 loose index scan: calls to read_range_first iterates; +--echo ## index_read_map with AFTER_KEY is merged eval CREATE TABLE t1 ( a INT, @@ -293,7 +294,8 @@ CREATE TABLE t1 ( insert into t1 select t2.seq, t3.seq from seq_100_to_200 t2, seq_1_to_50 t3; ---echo ## Case 2: Unordered scans with MRR +--echo ## Case 2: iterated partition scans with MRR with ASC key; +--echo ## merged scan with index_read_map with DESC key flush status; select * from t1 where c=113 and d between 25 and 35 order by d limit 3; show status like 'handler_read_%'; @@ -572,5 +574,180 @@ SELECT c FROM t1 WHERE b = 1 ORDER BY c $opposite_direction; DROP TABLE t1; +--echo # +--echo # MDEV-41366 Wrong result with Case 2 of non-merging partition index scan +--echo # + +--echo # prefix-only PREFIX_LAST_OR_PREV, merged scan +CREATE TABLE t1 (a INT, b INT, c INT, PRIMARY KEY(a,c), KEY(b,c)) ENGINE=InnoDB +PARTITION BY RANGE(c) (PARTITION p0 VALUES LESS THAN (10), + PARTITION p1 VALUES LESS THAN (20)); +INSERT INTO t1 VALUES (1,1,5),(2,0,12); +let $q= +SELECT c FROM t1 FORCE INDEX(b) WHERE b=1 AND c<=15 ORDER BY c DESC; +eval $q; +let $out=`analyze format=json $q`; +evalp set @js='$out'; +select json_extract(@js,'$**.partitions_index_scan_method'); + +DROP TABLE t1; + +--echo # same but with desc key +CREATE TABLE t1 (a INT, b INT, c INT, PRIMARY KEY(a,c), KEY(b desc,c desc)) ENGINE=InnoDB +PARTITION BY RANGE(c) (PARTITION p0 VALUES LESS THAN (10), + PARTITION p1 VALUES LESS THAN (20)); +INSERT INTO t1 VALUES (1,1,5),(2,0,12); +let $q= +SELECT c FROM t1 FORCE INDEX(b) WHERE b=0 AND c>=2 ORDER BY c; +eval $q; +let $out=`analyze format=json $q`; +evalp set @js='$out'; +select json_extract(@js,'$**.partitions_index_scan_method'); + +DROP TABLE t1; + +--echo # prefix-only KEY_OR_NEXT / PREFIX_LAST_OR_PREV (opt_sum) +--echo # merged scan +CREATE TABLE t1 (a INT, b INT, c INT NOT NULL, PRIMARY KEY(a,c), KEY k(b,c)) + ENGINE=InnoDB +PARTITION BY RANGE(c) (PARTITION p0 VALUES LESS THAN (10), + PARTITION p1 VALUES LESS THAN (20), + PARTITION p2 VALUES LESS THAN (30)); +--echo # no b=1 in p0 or p2 +INSERT INTO t1 VALUES (1,2,5),(2,1,15),(3,0,22); + +EXPLAIN +SELECT MIN(c) FROM t1 WHERE b=1; +FLUSH STATUS; +SELECT MIN(c) FROM t1 WHERE b=1; +--echo # 3 (merged scan: p0, p1, p2) +SHOW STATUS LIKE 'Handler_read_key'; + +EXPLAIN +SELECT MAX(c) FROM t1 WHERE b=1; +FLUSH STATUS; +SELECT MAX(c) FROM t1 WHERE b=1; +--echo # 3 (merged scan: p0, p1, p2) +SHOW STATUS LIKE 'Handler_read_key'; +DROP TABLE t1; + +--echo # index_first with prefix does not qualify Case 2 +--echo # neither does index_read_map with AFTER_KEY +CREATE TABLE t1 (a INT, b INT, KEY(a,b)) ENGINE=InnoDB +PARTITION BY RANGE COLUMNS(b) (PARTITION p1 VALUES LESS THAN (100), + PARTITION p2 VALUES LESS THAN (200), + PARTITION p3 VALUES LESS THAN MAXVALUE); +INSERT INTO t1 SELECT seq % 3 + 1, seq FROM seq_1_to_300; +ANALYZE TABLE t1 PERSISTENT FOR ALL; +let $q= +SELECT a, MIN(b) FROM t1 GROUP BY a; +eval EXPLAIN $q; +eval $q; +let $out=`analyze format=json $q`; +evalp set @js='$out'; +select json_extract(@js,'$**.partitions_index_scan_method'); +DROP TABLE t1; + +--echo # index_first with prefix does not qualify Case 2 +--echo # neither does index_read_map with AFTER_KEY +CREATE TABLE t1 (a INT, c INT, KEY(a,c)) ENGINE=InnoDB +PARTITION BY RANGE COLUMNS(c) (PARTITION p1 VALUES LESS THAN (100), + PARTITION p2 VALUES LESS THAN (200), + PARTITION p3 VALUES LESS THAN MAXVALUE); +INSERT INTO t1 SELECT 1, seq FROM seq_1_to_99; +INSERT INTO t1 SELECT 2, seq FROM seq_100_to_199; +INSERT INTO t1 SELECT 3, seq FROM seq_1_to_99; +ANALYZE TABLE t1 PERSISTENT FOR ALL; +let $q= +SELECT a, MIN(c) FROM t1 GROUP BY a; +eval EXPLAIN $q; +eval $q; +let $out=`analyze format=json $q`; +evalp set @js='$out'; +select json_extract(@js,'$**.partitions_index_scan_method'); +DROP TABLE t1; + +--echo # AFTER_KEY / BEFORE_KEY merged scan +CREATE TABLE t1 (a INT, b INT, c INT NOT NULL, PRIMARY KEY(a,c), KEY k(b,c)) + ENGINE=InnoDB +PARTITION BY RANGE(c) (PARTITION p0 VALUES LESS THAN (10), + PARTITION p1 VALUES LESS THAN (20), + PARTITION p2 VALUES LESS THAN (30)); +INSERT INTO t1 VALUES + (1,1,5),(2,2,7),(3,2,10),(4,1,15),(5,2,18),(6,0,22),(7,1,25); + +EXPLAIN +SELECT MIN(c) FROM t1 WHERE b=1 AND c>5; +FLUSH STATUS; +SELECT MIN(c) FROM t1 WHERE b=1 AND c>5; +--echo # 3 (merged: p0, p1, p2) +SHOW STATUS LIKE 'Handler_read_key'; + +EXPLAIN +SELECT MAX(c) FROM t1 WHERE b=1 AND c<25; +FLUSH STATUS; +SELECT MAX(c) FROM t1 WHERE b=1 AND c<25; +--echo # 3 (merged: p0, p1, p2) +SHOW STATUS LIKE 'Handler_read_key'; +DROP TABLE t1; + +--echo # KEY_OR_PREV needs a reverse partition scan. +--echo # Ordered scan (HASH) +CREATE TABLE t1 (a INT, KEY k(a)) ENGINE=InnoDB +PARTITION BY HASH(a) PARTITIONS 3; +INSERT INTO t1 SELECT seq FROM seq_1_to_9; +HANDLER t1 OPEN; +HANDLER t1 READ k <= (5); +HANDLER t1 READ k PREV; +HANDLER t1 READ k PREV; +HANDLER t1 READ k <= (0); +HANDLER t1 CLOSE; +DROP TABLE t1; + +--echo # KEY_OR_PREV merged scan +CREATE TABLE t1 (a INT, b INT, c INT, PRIMARY KEY(a,c), KEY k(b,c)) +ENGINE=InnoDB +PARTITION BY RANGE(c) (PARTITION p0 VALUES LESS THAN (10), + PARTITION p1 VALUES LESS THAN (20)); +INSERT INTO t1 VALUES (1,1,5),(2,2,7),(3,1,15); +HANDLER t1 OPEN; +HANDLER t1 READ k <= (1,17); +HANDLER t1 READ k PREV; +HANDLER t1 CLOSE; +DROP TABLE t1; + +--echo # A range spanning several prefix values: +--echo # QUICK_SELECT_DESC reads PREFIX_LAST_OR_PREV (2,15), keypart_map=b,c +CREATE TABLE t1 (a INT, b INT, c INT, PRIMARY KEY(a,c), KEY k(b,c)) ENGINE=InnoDB +PARTITION BY RANGE(c) (PARTITION p0 VALUES LESS THAN (10), + PARTITION p1 VALUES LESS THAN (20)); +INSERT INTO t1 VALUES (1,1,7),(2,2,7),(3,1,12),(4,2,12); +let $q= +SELECT b, c FROM t1 FORCE INDEX(k) +WHERE b BETWEEN 1 AND 2 AND c BETWEEN 5 AND 15 ORDER BY b DESC, c DESC; +eval EXPLAIN $q; +eval $q; +let $out=`analyze format=json $q`; +evalp set @js='$out'; +select json_extract(@js,'$**.partitions_index_scan_method'); +DROP TABLE t1; + +--echo # reset m_unordered_prefix_len: single partition scan skips +--echo # can_skip_merging_scans() +CREATE TABLE t1 (a INT, b INT, c INT, PRIMARY KEY(a,c), + KEY k(b,c), KEY kc(c,b)) ENGINE=InnoDB +PARTITION BY RANGE(c) (PARTITION p0 VALUES LESS THAN (10), + PARTITION p1 VALUES LESS THAN (20)); +INSERT INTO t1 VALUES (1,1,5),(2,1,12),(3,2,13); +HANDLER t1 OPEN; +--echo # 2 partitions, Case 2 on k(b,c): m_unordered_prefix_len = len(b) +HANDLER t1 READ k = (1); +--echo # full key of kc binds c: single partition p1, reset +HANDLER t1 READ kc = (13,2); +--echo # expect (2,1,12) +HANDLER t1 READ kc PREV; +HANDLER t1 CLOSE; +DROP TABLE t1; + --enable_cursor_protocol --enable_ps2_protocol diff --git a/sql/ha_partition.cc b/sql/ha_partition.cc index 343ab395ffc32..143a2bf56ef81 100644 --- a/sql/ha_partition.cc +++ b/sql/ha_partition.cc @@ -5991,7 +5991,8 @@ int ha_partition::common_index_read(uchar *buf, bool have_start_key) } if (have_start_key && - (m_start_key.flag == HA_READ_PREFIX_LAST || + (m_start_key.flag == HA_READ_KEY_OR_PREV || + m_start_key.flag == HA_READ_PREFIX_LAST || m_start_key.flag == HA_READ_PREFIX_LAST_OR_PREV || m_start_key.flag == HA_READ_BEFORE_KEY)) { @@ -7515,8 +7516,23 @@ bool ha_partition::can_skip_merging_scans() (key_part->key_part_flag & HA_REVERSE_SORT) ? true : false; if (m_index_scan_type == partition_index_read) { - return - m_start_key.key && (m_start_key.keypart_map & prefix) == prefix; + switch (m_start_key.flag) { + case HA_READ_AFTER_KEY: + case HA_READ_BEFORE_KEY: + case HA_READ_KEY_OR_NEXT: + case HA_READ_KEY_OR_PREV: + case HA_READ_PREFIX_LAST_OR_PREV: + return false; + case HA_READ_KEY_EXACT: + case HA_READ_PREFIX: + case HA_READ_PREFIX_LAST: + return m_start_key.key && + (m_start_key.keypart_map & prefix) == prefix; + default: + /* SPATIAL keys have a single key part, so Case 2 cannot apply */ + DBUG_ASSERT(0); + return false; + } } else if (m_index_scan_type == partition_read_range) { @@ -7595,6 +7611,7 @@ int ha_partition::partition_scan_set_up(uchar * buf, bool idx_read_flag) DBUG_PRINT("info", ("index scan using the single partition %u", (uint) m_part_spec.start_part)); m_ordered_scan_ongoing= FALSE; + m_unordered_prefix_len= 0; } else {