From: Jorgen Loland Date: April 29 2011 9:13am Subject: bzr commit into mysql-trunk branch (jorgen.loland:3341) List-Archive: http://lists.mysql.com/commits/136376 Message-Id: <20110429091318.0C4419FE@atum21.norway.sun.com> MIME-Version: 1.0 Content-Type: multipart/mixed; boundary="===============2195103392674046760==" --===============2195103392674046760== 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 3341 Jorgen Loland 2011-04-29 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-04-29 09:13:14 +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-04-29 09:13:14 +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-04-29 09:13:14 +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-04-29 09:13:14 +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-04-29 09:13:14 +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-04-29 09:13:14 +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-04-29 09:13:14 +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-04-29 08:02:04 +0000 +++ b/sql/opt_range.cc 2011-04-29 09:13:14 +0000 @@ -6970,11 +6970,42 @@ key_or(RANGE_OPT_PARAM *param, SEL_ARG * tmp: [------**********] ^ ^ tmp.max is somewhere between these points here + + If tmp->next_key_part is empty: + => tmp->next_key_part OR key2->next_key_part will be empty + If tmp.max >= key2.max: the key2 range is covered by tmp. + Move on to next range in key2 + If key2.max > tmp.max: Make range of key2 [tmp.max, key2.max] + and start from the top + If tmp->next_key_part is NOT empty: + 1) Make new_arg with range [tmp.min, key2.min] and + insert it into key1 + 2) Make tmp the range [key2.min, tmp.max] */ + if (!tmp->next_key_part) + { + if (tmp->cmp_max_to_max(key2) >= 0) + { + /* + tmp.max >= key2.max. tmp covers the entire range in key2. + Move to next key2 range + */ + key2->increment_use_count(-1); // Free not used tree + key2=key2->next; + continue; + } + else + { + // Make key2 the range [tmp.max, key2.max] + key2->copy_max_to_min(tmp); + continue; + } + } + 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); @@ -7074,6 +7105,10 @@ key_or(RANGE_OPT_PARAM *param, SEL_ARG * 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 @@ -7081,6 +7116,11 @@ key_or(RANGE_OPT_PARAM *param, SEL_ARG * 3) Insert new_arg into key1 (after changing tmp range to insert into correct location in key1 tree) */ + 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 --===============2195103392674046760== 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\ # 7c5oqhir1ikg3e42 # target_branch: file:///export/home/jl208045/mysql/mysql-trunk-\ # 11765831/ # testament_sha1: fd60507ee846531e5c56fe20a056cd0e4efb60ae # timestamp: 2011-04-29 11:13:18 +0200 # source_branch: file:///export/home/jl208045/mysql/mysql-5.5/ # base_revision_id: jorgen.loland@stripped\ # ql1kyfu0nt9ygdp4 # # Begin bundle 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 --===============2195103392674046760==--