From: Jorgen Loland Date: May 19 2011 12:03pm Subject: bzr commit into mysql-trunk branch (jorgen.loland:3102) Bug#11765831 List-Archive: http://lists.mysql.com/commits/137696 X-Bug: 11765831 Message-Id: <20110519120358.DA195453@atum21.norway.sun.com> MIME-Version: 1.0 Content-Type: multipart/mixed; boundary="===============3234927343553781051==" --===============3234927343553781051== MIME-Version: 1.0 Content-Type: text/plain; charset="us-ascii" Content-Transfer-Encoding: 7bit Content-Disposition: inline #At file:///export/home/jl208045/mysql/mysql-trunk-11765831/ based on revid:mayank.prasad@stripped 3102 Jorgen Loland 2011-05-19 BUG#11765831: 'RANGE ACCESS' MAY INCORRECTLY FILTER AWAY QUALIFYING ROWS The problem was that the ranges created when OR'ing two conditions could be incorrect. Without the bugfix, "I <> 6 OR (I <> 8 AND J = 5)" would create these ranges: "NULL < I < 6", "6 <= I <= 6 AND 5 <= J <= 5", "6 < I < 8", "8 <= I <= 8 AND 5 <= J <= 5", "8 < I" While the correct ranges is "NULL < I < 6", "6 <= I <= 6 AND 5 <= J <= 5", "6 < I" The problem occurs when key_or() ORs (1) "NULL < I < 6, 6 <= I <= 6 AND 5 <= J <= 5, 6 < I" with (2) "8 < I AND 5 <= J <= 5" The reason for the bug is that in key_or(), SEL_ARG *tmp is used to point to the range in (1) above that is merged with (2) while key1 points to the root of the red-black tree of (1). When merging (1) and (2), tmp refers to the "6 < I" part whereas the root is the "6 <= ... AND 5 <= J <= 5" part. key_or() decides that the tmp range needs to be split into "6 < I < 8, 8 <= I <= 8, 8 < I", in which next_key_part of the second range should be that of tmp. However, next_key_part is set to key1->next_key_part ("5 <= J <= 5") instead of tmp->next_key_part (empty). Fixing this gives the correct but not optimal ranges: "NULL < I < 6", "6 <= I <= 6 AND 5 <= J <= 5", "6 < I < 8", "8 <= I <= 8", "8 < I" A second problem can be seen above: key_or() may create adjacent ranges that could be replaced with a single range. Fixes for this is also included in the patch so that the range above becomes correct AND optimal: "NULL < I < 6", "6 <= I <= 6 AND 5 <= J <= 5", "6 < I" Merging adjacent ranges like this gives a slightly lower cost estimate for the range access. @ mysql-test/include/range.inc Add test for BUG#11765831 @ mysql-test/r/group_min_max.result BUG#11765831 merges adjacent ranges, resulting in a slightly lower cost estimate for accessing the table through range access. @ mysql-test/r/range_icp.result Add test for BUG#11765831 @ mysql-test/r/range_icp_mrr.result Add test for BUG#11765831 @ mysql-test/r/range_mrr.result Add test for BUG#11765831 @ mysql-test/r/range_mrr_cost.result Add test for BUG#11765831 @ mysql-test/r/range_none.result Add test for BUG#11765831 @ sql/opt_range.cc In key_or(): When a range in key1 was split due to a partially overlapping range in key2, the non-overlaping part incorrectly got next_key_part from the R-B tree root node instead of the currently active range. key_or() could also create adjacent ranges that could be replaced by a single continous range. This is also fixed. modified: mysql-test/include/range.inc mysql-test/r/group_min_max.result mysql-test/r/range_icp.result mysql-test/r/range_icp_mrr.result mysql-test/r/range_mrr.result mysql-test/r/range_mrr_cost.result mysql-test/r/range_none.result sql/opt_range.cc === modified file 'mysql-test/include/range.inc' --- a/mysql-test/include/range.inc 2011-02-01 12:47:39 +0000 +++ b/mysql-test/include/range.inc 2011-05-19 12:03:55 +0000 @@ -1471,3 +1471,35 @@ INSERT INTO t2 VALUES (1, 1, 2); EXPLAIN SELECT * FROM t2 WHERE a = 1 AND b >= 2 AND c >= 2; DROP TABLE t1, t2; + +--echo # +--echo # BUG#11765831: 'RANGE ACCESS' MAY INCORRECTLY FILTER +--echo # AWAY QUALIFYING ROWS +--echo # + +CREATE TABLE t10( + K INT NOT NULL AUTO_INCREMENT, + I INT, J INT, + PRIMARY KEY(K), + KEY(I,J) +); +INSERT INTO t10(I,J) VALUES (6,1),(6,2),(6,3),(6,4),(6,5), + (6,6),(6,7),(6,8),(6,9),(6,0); + +CREATE TABLE t100 LIKE t10; +INSERT INTO t100(I,J) SELECT X.I, X.K+(10*Y.K) FROM t10 AS X,t10 AS Y; + +# Insert offending value: +INSERT INTO t100(I,J) VALUES(8,26); + +let $query= SELECT * FROM t100 WHERE I <> 6 OR (I <> 8 AND J = 5); + +#Verify that 'range' access will be used +--echo +--eval EXPLAIN $query + +# Only row 101,8,26 should be returned +--echo +--eval $query + +DROP TABLE t10,t100; === modified file 'mysql-test/r/group_min_max.result' --- a/mysql-test/r/group_min_max.result 2011-05-13 12:41:14 +0000 +++ b/mysql-test/r/group_min_max.result 2011-05-19 12:03:55 +0000 @@ -876,10 +876,10 @@ id select_type table type possible_keys 1 SIMPLE t1 range NULL idx_t1_1 163 NULL 17 Using where; Using index for group-by explain select a1,a2,b, max(c) from t1 where (c > 'b1') or (c <= 'g1') group by a1,a2,b; id select_type table type possible_keys key key_len ref rows Extra -1 SIMPLE t1 range NULL idx_t1_1 163 NULL 17 Using where; Using index for group-by +1 SIMPLE t1 range NULL idx_t1_1 147 NULL 17 Using where; Using index for group-by explain select a1,a2,b,min(c),max(c) from t1 where (c > 'b1') or (c <= 'g1') group by a1,a2,b; id select_type table type possible_keys key key_len ref rows Extra -1 SIMPLE t1 range NULL idx_t1_1 163 NULL 17 Using where; Using index for group-by +1 SIMPLE t1 range NULL idx_t1_1 147 NULL 17 Using where; Using index for group-by explain select a1,a2,b,min(c),max(c) from t1 where (c > 'b111') and (c <= 'g112') group by a1,a2,b; id select_type table type possible_keys key key_len ref rows Extra 1 SIMPLE t1 range NULL idx_t1_1 163 NULL 17 Using where; Using index for group-by @@ -924,7 +924,7 @@ id select_type table type possible_keys 1 SIMPLE t2 range NULL idx_t2_1 163 NULL # Using where; Using index for group-by explain select a1,a2,b, max(c) from t2 where (c > 'b1') or (c <= 'g1') group by a1,a2,b; id select_type table type possible_keys key key_len ref rows Extra -1 SIMPLE t2 range NULL idx_t2_1 163 NULL # Using where; Using index for group-by +1 SIMPLE t2 range NULL idx_t2_1 146 NULL # Using where; Using index for group-by explain select a1,a2,b,min(c),max(c) from t2 where (c > 'b1') or (c <= 'g1') group by a1,a2,b; id select_type table type possible_keys key key_len ref rows Extra 1 SIMPLE t2 range NULL idx_t2_1 163 NULL # Using where; Using index for group-by === modified file 'mysql-test/r/range_icp.result' --- a/mysql-test/r/range_icp.result 2011-02-01 12:47:39 +0000 +++ b/mysql-test/r/range_icp.result 2011-05-19 12:03:55 +0000 @@ -1849,4 +1849,28 @@ EXPLAIN SELECT * FROM t2 WHERE a = 1 AND id select_type table type possible_keys key key_len ref rows Extra 1 SIMPLE t2 ref k,l,m,n l 5 const 69 Using index condition; Using where DROP TABLE t1, t2; +# +# BUG#11765831: 'RANGE ACCESS' MAY INCORRECTLY FILTER +# AWAY QUALIFYING ROWS +# +CREATE TABLE t10( +K INT NOT NULL AUTO_INCREMENT, +I INT, J INT, +PRIMARY KEY(K), +KEY(I,J) +); +INSERT INTO t10(I,J) VALUES (6,1),(6,2),(6,3),(6,4),(6,5), +(6,6),(6,7),(6,8),(6,9),(6,0); +CREATE TABLE t100 LIKE t10; +INSERT INTO t100(I,J) SELECT X.I, X.K+(10*Y.K) FROM t10 AS X,t10 AS Y; +INSERT INTO t100(I,J) VALUES(8,26); + +EXPLAIN SELECT * FROM t100 WHERE I <> 6 OR (I <> 8 AND J = 5); +id select_type table type possible_keys key key_len ref rows Extra +1 SIMPLE t100 range I I 10 NULL 4 Using index condition + +SELECT * FROM t100 WHERE I <> 6 OR (I <> 8 AND J = 5); +K I J +101 8 26 +DROP TABLE t10,t100; set optimizer_switch=default; === modified file 'mysql-test/r/range_icp_mrr.result' --- a/mysql-test/r/range_icp_mrr.result 2011-02-01 12:47:39 +0000 +++ b/mysql-test/r/range_icp_mrr.result 2011-05-19 12:03:55 +0000 @@ -1849,4 +1849,28 @@ EXPLAIN SELECT * FROM t2 WHERE a = 1 AND id select_type table type possible_keys key key_len ref rows Extra 1 SIMPLE t2 ref k,l,m,n l 5 const 69 Using index condition; Using where DROP TABLE t1, t2; +# +# BUG#11765831: 'RANGE ACCESS' MAY INCORRECTLY FILTER +# AWAY QUALIFYING ROWS +# +CREATE TABLE t10( +K INT NOT NULL AUTO_INCREMENT, +I INT, J INT, +PRIMARY KEY(K), +KEY(I,J) +); +INSERT INTO t10(I,J) VALUES (6,1),(6,2),(6,3),(6,4),(6,5), +(6,6),(6,7),(6,8),(6,9),(6,0); +CREATE TABLE t100 LIKE t10; +INSERT INTO t100(I,J) SELECT X.I, X.K+(10*Y.K) FROM t10 AS X,t10 AS Y; +INSERT INTO t100(I,J) VALUES(8,26); + +EXPLAIN SELECT * FROM t100 WHERE I <> 6 OR (I <> 8 AND J = 5); +id select_type table type possible_keys key key_len ref rows Extra +1 SIMPLE t100 range I I 10 NULL 4 Using index condition; Using MRR + +SELECT * FROM t100 WHERE I <> 6 OR (I <> 8 AND J = 5); +K I J +101 8 26 +DROP TABLE t10,t100; set optimizer_switch=default; === modified file 'mysql-test/r/range_mrr.result' --- a/mysql-test/r/range_mrr.result 2011-02-01 12:47:39 +0000 +++ b/mysql-test/r/range_mrr.result 2011-05-19 12:03:55 +0000 @@ -1849,4 +1849,28 @@ EXPLAIN SELECT * FROM t2 WHERE a = 1 AND id select_type table type possible_keys key key_len ref rows Extra 1 SIMPLE t2 ref k,l,m,n l 5 const 69 Using where DROP TABLE t1, t2; +# +# BUG#11765831: 'RANGE ACCESS' MAY INCORRECTLY FILTER +# AWAY QUALIFYING ROWS +# +CREATE TABLE t10( +K INT NOT NULL AUTO_INCREMENT, +I INT, J INT, +PRIMARY KEY(K), +KEY(I,J) +); +INSERT INTO t10(I,J) VALUES (6,1),(6,2),(6,3),(6,4),(6,5), +(6,6),(6,7),(6,8),(6,9),(6,0); +CREATE TABLE t100 LIKE t10; +INSERT INTO t100(I,J) SELECT X.I, X.K+(10*Y.K) FROM t10 AS X,t10 AS Y; +INSERT INTO t100(I,J) VALUES(8,26); + +EXPLAIN SELECT * FROM t100 WHERE I <> 6 OR (I <> 8 AND J = 5); +id select_type table type possible_keys key key_len ref rows Extra +1 SIMPLE t100 range I I 10 NULL 4 Using where; Using MRR + +SELECT * FROM t100 WHERE I <> 6 OR (I <> 8 AND J = 5); +K I J +101 8 26 +DROP TABLE t10,t100; set optimizer_switch=default; === modified file 'mysql-test/r/range_mrr_cost.result' --- a/mysql-test/r/range_mrr_cost.result 2011-02-01 12:47:39 +0000 +++ b/mysql-test/r/range_mrr_cost.result 2011-05-19 12:03:55 +0000 @@ -1849,4 +1849,28 @@ EXPLAIN SELECT * FROM t2 WHERE a = 1 AND id select_type table type possible_keys key key_len ref rows Extra 1 SIMPLE t2 ref k,l,m,n l 5 const 69 Using where DROP TABLE t1, t2; +# +# BUG#11765831: 'RANGE ACCESS' MAY INCORRECTLY FILTER +# AWAY QUALIFYING ROWS +# +CREATE TABLE t10( +K INT NOT NULL AUTO_INCREMENT, +I INT, J INT, +PRIMARY KEY(K), +KEY(I,J) +); +INSERT INTO t10(I,J) VALUES (6,1),(6,2),(6,3),(6,4),(6,5), +(6,6),(6,7),(6,8),(6,9),(6,0); +CREATE TABLE t100 LIKE t10; +INSERT INTO t100(I,J) SELECT X.I, X.K+(10*Y.K) FROM t10 AS X,t10 AS Y; +INSERT INTO t100(I,J) VALUES(8,26); + +EXPLAIN SELECT * FROM t100 WHERE I <> 6 OR (I <> 8 AND J = 5); +id select_type table type possible_keys key key_len ref rows Extra +1 SIMPLE t100 range I I 10 NULL 4 Using where + +SELECT * FROM t100 WHERE I <> 6 OR (I <> 8 AND J = 5); +K I J +101 8 26 +DROP TABLE t10,t100; set optimizer_switch=default; === modified file 'mysql-test/r/range_none.result' --- a/mysql-test/r/range_none.result 2011-02-01 12:47:39 +0000 +++ b/mysql-test/r/range_none.result 2011-05-19 12:03:55 +0000 @@ -1848,4 +1848,28 @@ EXPLAIN SELECT * FROM t2 WHERE a = 1 AND id select_type table type possible_keys key key_len ref rows Extra 1 SIMPLE t2 ref k,l,m,n l 5 const 69 Using where DROP TABLE t1, t2; +# +# BUG#11765831: 'RANGE ACCESS' MAY INCORRECTLY FILTER +# AWAY QUALIFYING ROWS +# +CREATE TABLE t10( +K INT NOT NULL AUTO_INCREMENT, +I INT, J INT, +PRIMARY KEY(K), +KEY(I,J) +); +INSERT INTO t10(I,J) VALUES (6,1),(6,2),(6,3),(6,4),(6,5), +(6,6),(6,7),(6,8),(6,9),(6,0); +CREATE TABLE t100 LIKE t10; +INSERT INTO t100(I,J) SELECT X.I, X.K+(10*Y.K) FROM t10 AS X,t10 AS Y; +INSERT INTO t100(I,J) VALUES(8,26); + +EXPLAIN SELECT * FROM t100 WHERE I <> 6 OR (I <> 8 AND J = 5); +id select_type table type possible_keys key key_len ref rows Extra +1 SIMPLE t100 range I I 10 NULL 4 Using where + +SELECT * FROM t100 WHERE I <> 6 OR (I <> 8 AND J = 5); +K I J +101 8 26 +DROP TABLE t10,t100; set optimizer_switch=default; === modified file 'sql/opt_range.cc' --- a/sql/opt_range.cc 2011-05-18 10:43:46 +0000 +++ b/sql/opt_range.cc 2011-05-19 12:03:55 +0000 @@ -7069,11 +7069,53 @@ key_or(RANGE_OPT_PARAM *param, SEL_ARG * This is the case ("cmp>=0" means that tmp.max >= key2.min): key2: [----] tmp: [------------*****] + */ + + if (!tmp->next_key_part) + { + /* + tmp->next_key_part is empty: cut the range that is covered + by tmp from key2. + Reason: (key2->next_key_part OR tmp->next_key_part) will be + empty and therefore equal to tmp->next_key_part. Thus, this + part of the key2 range is completely covered by tmp. + */ + if (tmp->cmp_max_to_max(key2) >= 0) + { + /* + tmp covers the entire range in key2. + key2: [----] + tmp: [-----------------] + Move on to next range in key2 + */ + key2->increment_use_count(-1); // Free not used tree + key2=key2->next; + continue; + } + else + { + /* + This is the case: + key2: [-------] + tmp: [---------] + + Result: + key2: [---] + tmp: [---------] + */ + key2->copy_max_to_min(tmp); + continue; + } + } + + /* The ranges are overlapping but have not been merged because - next_key_part of tmp and key2 are different + next_key_part of tmp and key2 differ. + key2: [----] + tmp: [------------*****] - Result: + Split tmp in two where key2 starts: key2: [----] key1: [--------][--*****] ^ ^ @@ -7082,7 +7124,7 @@ key_or(RANGE_OPT_PARAM *param, SEL_ARG * SEL_ARG *new_arg=tmp->clone_first(key2); if (!new_arg) return 0; // OOM - if ((new_arg->next_key_part= key1->next_key_part)) + if ((new_arg->next_key_part= tmp->next_key_part)) new_arg->increment_use_count(key1->use_count+1); tmp->copy_min_to_min(key2); key1=key1->insert(new_arg); @@ -7191,12 +7233,21 @@ key_or(RANGE_OPT_PARAM *param, SEL_ARG * ^ ^ new_arg tmp Steps: + 0) If tmp->next_key_part is empty: do nothing. Reason: + (key2_cpy->next_key_part OR tmp->next_key_part) will be + empty and therefore equal to tmp->next_key_part. Thus, + the range in key2_cpy is completely covered by tmp 1) Make new_arg with range [tmp.min, key2_cpy.max]. new_arg->next_key_part is OR between next_key_part of tmp and key2_cpy 2) Make tmp the range [key2.max, tmp.max] 3) Insert new_arg into key1 */ + if (!tmp->next_key_part) // Step 0 + { + key2_cpy.increment_use_count(-1); // Free not used tree + break; + } SEL_ARG *new_arg=tmp->clone_last(&key2_cpy); if (!new_arg) return 0; // OOM --===============3234927343553781051== MIME-Version: 1.0 Content-Type: text/bzr-bundle; charset="us-ascii"; name="bzr/jorgen.loland@stripped" Content-Transfer-Encoding: 7bit Content-Disposition: inline # Bazaar merge directive format 2 (Bazaar 0.90) # revision_id: jorgen.loland@stripped\ # qn7eprkad9jqwu5j # target_branch: file:///export/home/jl208045/mysql/mysql-trunk-\ # 11765831/ # testament_sha1: fc63b2c121f4e04324ea24bdb60dee01b1a94855 # timestamp: 2011-05-19 14:03:58 +0200 # source_branch: file:///export/home/jl208045/mysql/mysql-5.5/ # base_revision_id: mayank.prasad@stripped\ # bdxv4udzrmqsjmhq # # Begin bundle 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 --===============3234927343553781051==--