From: Jorgen Loland Date: May 6 2011 1:57pm Subject: bzr commit into mysql-trunk branch (jorgen.loland:3382) Bug#11765831 List-Archive: http://lists.mysql.com/commits/136853 X-Bug: 11765831 Message-Id: <20110506135739.A2D4AB8E@atum21.norway.sun.com> MIME-Version: 1.0 Content-Type: multipart/mixed; boundary="===============5394318218463894653==" --===============5394318218463894653== 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:jorgen.loland@stripped 3382 Jorgen Loland 2011-05-06 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-06 13:57:35 +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-04-27 09:10:45 +0000 +++ b/mysql-test/r/group_min_max.result 2011-05-06 13:57:35 +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-06 13:57:35 +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-06 13:57:35 +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-06 13:57:35 +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-06 13:57:35 +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-06 13:57:35 +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-06 13:26:31 +0000 +++ b/sql/opt_range.cc 2011-05-06 13:57:35 +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 has no next_key_part, thus + tmp->next_key_part == (tmp->next_key_part OR key2->next_key_part) + == empty + + The range that is covered by tmp can be cut from key2 + */ + 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 --===============5394318218463894653== 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\ # qw9qba2ohe6b6mna # target_branch: file:///export/home/jl208045/mysql/mysql-trunk-\ # 11765831/ # testament_sha1: 03520217340ab3582d2d0d75937e838eea24fd58 # timestamp: 2011-05-06 15:57:39 +0200 # source_branch: file:///export/home/jl208045/mysql/mysql-5.5/ # base_revision_id: jorgen.loland@stripped\ # 5wickj6dvrh1dpj6 # # Begin bundle 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 ckU4UJDYaZLf --===============5394318218463894653==--