From: Ole John Aske Date: February 28 2011 10:42am Subject: bzr commit into mysql-5.1-telco-7.0 branch (ole.john.aske:4234) Bug#11804277 List-Archive: http://lists.mysql.com/commits/132096 X-Bug: 11804277 Message-Id: <20110228104214.10B95223@fimafeng09.norway.sun.com> MIME-Version: 1.0 Content-Type: multipart/mixed; boundary="===============7571275159401741799==" --===============7571275159401741799== MIME-Version: 1.0 Content-Type: text/plain; charset="us-ascii" Content-Transfer-Encoding: 7bit Content-Disposition: inline #At file:///net/fimafeng09/export/home/tmp/oleja/mysql/mysql-5.1-telco-7.0/ based on revid:msabaratnam@stripped 4234 Ole John Aske 2011-02-28 Fix for bug#11804277 - INCORRECT INDEX MAY BE SELECTED DUE TO INSUFFICIENT STATISTICS FROM CLUSTER Add heuristics to ha_ndbcluster::records_in_range() which identifies a range as: - An open bound range ( LT/GT BETWEEN AND ) - A (partial) EQ-range ( EQ ) ... Or a combination of these.... These are handled as follows: Open bound ranges ----------------- Without a histogram of how the values in the index are distributed, we can only assume an equal distrubution. A statistically correct estimate for a condition of the form ' LT/GT ' would then have been to assume it selects 50% of the rows in the table. However, I have experienced that this will cause the range-cost to directly compete with the cost of a full table scan. We should therefore be somewhat more conservative and estimate 10% of the rows to be returned. Closed bound range ------------------ We assume this to be somewhat better than an open bounded range returning 5% of the rows in the table. EQ-range -------- An EQ-range will excatly specify a fraction of the first part of an index. It is reasonable to assume: - Specifing a larger fraction of the index will improve the selectivity of the EQ-range. - Each part of the specified EQ-range will have the same selectivity. We can model this as a Binomial Distribution of the indexed values. http://en.wikipedia.org/wiki/Binomial_distrib modified: mysql-test/suite/ndb/r/ndb_condition_pushdown.result mysql-test/suite/ndb/r/ndb_index.result mysql-test/suite/ndb/r/ndb_index_unique.result mysql-test/suite/ndb/r/ndb_read_multi_range.result mysql-test/suite/ndb/r/ndb_statistics.result mysql-test/suite/ndb/t/ndb_statistics.test sql/ha_ndbcluster.cc === modified file 'mysql-test/suite/ndb/r/ndb_condition_pushdown.result' --- a/mysql-test/suite/ndb/r/ndb_condition_pushdown.result 2011-01-17 13:29:52 +0000 +++ b/mysql-test/suite/ndb/r/ndb_condition_pushdown.result 2011-02-28 10:42:04 +0000 @@ -1910,7 +1910,7 @@ insert into NodeAlias VALUES(null, 8 , ' 12:22:26'); explain select * from NodeAlias where (aliasKey LIKE '491803%'); id select_type table type possible_keys key key_len ref rows Extra -1 SIMPLE NodeAlias range NodeAlias_KeyIndex NodeAlias_KeyIndex 48 NULL 10 Using where with pushed condition +1 SIMPLE NodeAlias range NodeAlias_KeyIndex NodeAlias_KeyIndex 48 NULL 2 Using where with pushed condition select * from NodeAlias where (aliasKey LIKE '491803%') order by id; id nodeId displayName aliasKey objectVersion changed 7 8 491803% 491803% 0 2008-03-10 12:22:26 @@ -2225,7 +2225,7 @@ join tx as t2 on tx.a = t2.c and tx.b = where t2.a = 4 group by t2.c; id select_type table type possible_keys key key_len ref rows filtered Extra -1 SIMPLE t2 ref PRIMARY PRIMARY 4 const 10 100.00 Using where; Using filesort +1 SIMPLE t2 ref PRIMARY PRIMARY 4 const 2 100.00 Using where; Using filesort 1 SIMPLE tx eq_ref PRIMARY PRIMARY 8 test.t2.c,test.t2.d 1 100.00 Warnings: Note 1003 select `test`.`t2`.`c` AS `c`,count(distinct `test`.`t2`.`a`) AS `count(distinct t2.a)` from `test`.`tx` join `test`.`tx` `t2` where ((`test`.`tx`.`b` = `test`.`t2`.`d`) and (`test`.`tx`.`a` = `test`.`t2`.`c`) and (`test`.`t2`.`a` = 4)) group by `test`.`t2`.`c` @@ -2242,7 +2242,7 @@ join tx as t2 on tx.a = t2.c and tx.b = where t2.a = 4 group by t2.c; id select_type table type possible_keys key key_len ref rows filtered Extra -1 SIMPLE t2 ref PRIMARY PRIMARY 4 const 10 100.00 Using where; Using filesort +1 SIMPLE t2 ref PRIMARY PRIMARY 4 const 2 100.00 Using where; Using filesort 1 SIMPLE tx eq_ref PRIMARY PRIMARY 8 test.t2.c,test.t2.d 1 100.00 Warnings: Note 1003 select `test`.`t2`.`c` AS `c`,count(distinct `test`.`t2`.`a`) AS `count(distinct t2.a)` from `test`.`tx` join `test`.`tx` `t2` where ((`test`.`tx`.`b` = `test`.`t2`.`d`) and (`test`.`tx`.`a` = `test`.`t2`.`c`) and (`test`.`t2`.`a` = 4)) group by `test`.`t2`.`c` === modified file 'mysql-test/suite/ndb/r/ndb_index.result' --- a/mysql-test/suite/ndb/r/ndb_index.result 2010-12-22 11:13:45 +0000 +++ b/mysql-test/suite/ndb/r/ndb_index.result 2011-02-28 10:42:04 +0000 @@ -306,7 +306,7 @@ explain select i,vc from t1 where i>=1 or vc > '0'; id select_type table type possible_keys key key_len ref rows Extra -1 SIMPLE t1 index_merge PRIMARY,i1,i2 i1,i2 5,18 NULL 20 Using sort_union(i1,i2); Using where with pushed condition +1 SIMPLE t1 index_merge PRIMARY,i1,i2 i1,i2 5,18 NULL 6 Using sort_union(i1,i2); Using where with pushed condition select i,vc from t1 where i>=1 or vc > '0'; i vc @@ -350,7 +350,7 @@ explain select i,vc from t2 where i>=1 or vc > '0'; id select_type table type possible_keys key key_len ref rows Extra -1 SIMPLE t2 index_merge i1,i2 i1,i2 5,19 NULL 20 Using sort_union(i1,i2); Using where with pushed condition +1 SIMPLE t2 index_merge i1,i2 i1,i2 5,19 NULL 6 Using sort_union(i1,i2); Using where with pushed condition select i,vc from t2 where i>=1 or vc > '0'; i vc === modified file 'mysql-test/suite/ndb/r/ndb_index_unique.result' --- a/mysql-test/suite/ndb/r/ndb_index_unique.result 2011-01-18 07:49:14 +0000 +++ b/mysql-test/suite/ndb/r/ndb_index_unique.result 2011-02-28 10:42:04 +0000 @@ -185,7 +185,7 @@ set @old_ecpd = @@session.engine_conditi set engine_condition_pushdown = true; explain select * from t2 where (b = 3 OR b = 5) AND c IS NULL AND a < 9 order by a; id select_type table type possible_keys key key_len ref rows Extra -1 SIMPLE t2 range PRIMARY,b b 9 NULL 2 Using where with pushed condition; Using filesort +1 SIMPLE t2 range PRIMARY,b PRIMARY 4 NULL 2 Using where with pushed condition select * from t2 where (b = 3 OR b = 5) AND c IS NULL AND a < 9 order by a; a b c 3 3 NULL === modified file 'mysql-test/suite/ndb/r/ndb_read_multi_range.result' --- a/mysql-test/suite/ndb/r/ndb_read_multi_range.result 2011-01-18 07:49:14 +0000 +++ b/mysql-test/suite/ndb/r/ndb_read_multi_range.result 2011-02-28 10:42:04 +0000 @@ -605,7 +605,7 @@ SELECT DISTINCT STRAIGHT_JOIN t1.pk FROM t1 LEFT JOIN t2 ON t2.a = t1.a AND t2.pk != 6; id select_type table type possible_keys key key_len ref rows Extra 1 SIMPLE t1 ALL NULL NULL NULL NULL 3000 Using temporary -1 SIMPLE t2 range PRIMARY PRIMARY 4 NULL 20 Using where; Distinct +1 SIMPLE t2 range PRIMARY PRIMARY 4 NULL 6 Using where; Distinct SELECT DISTINCT STRAIGHT_JOIN t1.pk FROM t1 LEFT JOIN t2 ON t2.a = t1.a AND t2.pk != 6; drop table t1, t2; === modified file 'mysql-test/suite/ndb/r/ndb_statistics.result' --- a/mysql-test/suite/ndb/r/ndb_statistics.result 2011-01-18 11:49:03 +0000 +++ b/mysql-test/suite/ndb/r/ndb_statistics.result 2011-02-28 10:42:04 +0000 @@ -38,24 +38,124 @@ id select_type table type possible_keys EXPLAIN SELECT * FROM t10000 WHERE k >= 42 and k < 10000; id select_type table type possible_keys key key_len ref rows Extra -1 SIMPLE t10000 range PRIMARY PRIMARY 4 NULL 10 Using where with pushed condition +1 SIMPLE t10000 range PRIMARY PRIMARY 4 NULL 500 Using where with pushed condition EXPLAIN SELECT * FROM t10000 WHERE k BETWEEN 42 AND 10000; id select_type table type possible_keys key key_len ref rows Extra -1 SIMPLE t10000 range PRIMARY PRIMARY 4 NULL 10 Using where with pushed condition +1 SIMPLE t10000 range PRIMARY PRIMARY 4 NULL 500 Using where with pushed condition EXPLAIN SELECT * FROM t10000 WHERE k < 42; id select_type table type possible_keys key key_len ref rows Extra -1 SIMPLE t10000 range PRIMARY PRIMARY 4 NULL 10 Using where with pushed condition +1 SIMPLE t10000 range PRIMARY PRIMARY 4 NULL 1000 Using where with pushed condition EXPLAIN SELECT * FROM t10000 WHERE k > 42; id select_type table type possible_keys key key_len ref rows Extra -1 SIMPLE t10000 range PRIMARY PRIMARY 4 NULL 10 Using where with pushed condition +1 SIMPLE t10000 range PRIMARY PRIMARY 4 NULL 1000 Using where with pushed condition EXPLAIN SELECT * FROM t10000 AS X JOIN t10000 AS Y ON Y.I=X.I AND Y.J = X.I; id select_type table type possible_keys key key_len ref rows Extra 1 SIMPLE X ALL I NULL NULL NULL 10000 1 SIMPLE Y ref J,I I 10 test.X.I,test.X.I 11 Using where +EXPLAIN +SELECT * FROM t100 WHERE k < 42; +id select_type table type possible_keys key key_len ref rows Extra +1 SIMPLE t100 range PRIMARY PRIMARY 4 NULL 10 Using where with pushed condition +EXPLAIN +SELECT * FROM t100 WHERE k > 42; +id select_type table type possible_keys key key_len ref rows Extra +1 SIMPLE t100 range PRIMARY PRIMARY 4 NULL 10 Using where with pushed condition +EXPLAIN +SELECT * FROM t10000 WHERE k < 42; +id select_type table type possible_keys key key_len ref rows Extra +1 SIMPLE t10000 range PRIMARY PRIMARY 4 NULL 1000 Using where with pushed condition +EXPLAIN +SELECT * FROM t10000 WHERE k > 42; +id select_type table type possible_keys key key_len ref rows Extra +1 SIMPLE t10000 range PRIMARY PRIMARY 4 NULL 1000 Using where with pushed condition +EXPLAIN +SELECT * FROM t100 WHERE k BETWEEN 42 AND 10000; +id select_type table type possible_keys key key_len ref rows Extra +1 SIMPLE t100 range PRIMARY PRIMARY 4 NULL 5 Using where with pushed condition +EXPLAIN +SELECT * FROM t10000 WHERE k BETWEEN 42 AND 10000; +id select_type table type possible_keys key key_len ref rows Extra +1 SIMPLE t10000 range PRIMARY PRIMARY 4 NULL 500 Using where with pushed condition +EXPLAIN +SELECT * FROM t10000 WHERE I = 0; +id select_type table type possible_keys key key_len ref rows Extra +1 SIMPLE t10000 ref I I 5 const 200 Using where with pushed condition +EXPLAIN +SELECT * FROM t10000 WHERE J = 0; +id select_type table type possible_keys key key_len ref rows Extra +1 SIMPLE t10000 ref J J 5 const 100 Using where with pushed condition +EXPLAIN +SELECT * FROM t10000 WHERE I = 0 AND J = 0; +id select_type table type possible_keys key key_len ref rows Extra +1 SIMPLE t10000 ref J,I I 10 const,const 4 Using where with pushed condition +EXPLAIN +SELECT * FROM t10000 WHERE I = 0; +id select_type table type possible_keys key key_len ref rows Extra +1 SIMPLE t10000 ref I I 5 const 200 Using where with pushed condition +EXPLAIN +SELECT * FROM t10000 WHERE I = 0 AND J > 1; +id select_type table type possible_keys key key_len ref rows Extra +1 SIMPLE t10000 range J,I I 10 NULL 100 Using where with pushed condition +EXPLAIN +SELECT * FROM t10000 WHERE I = 0 AND J < 1; +id select_type table type possible_keys key key_len ref rows Extra +1 SIMPLE t10000 range J,I I 10 NULL 50 Using where with pushed condition +EXPLAIN +SELECT * FROM t10000 WHERE I = 0 AND J BETWEEN 1 AND 10; +id select_type table type possible_keys key key_len ref rows Extra +1 SIMPLE t10000 range J,I I 10 NULL 50 Using where with pushed condition +EXPLAIN +SELECT * FROM t10000 WHERE I = 0 AND J = 1; +id select_type table type possible_keys key key_len ref rows Extra +1 SIMPLE t10000 ref J,I I 10 const,const 4 Using where with pushed condition +EXPLAIN +SELECT * FROM t10000 WHERE J = 0; +id select_type table type possible_keys key key_len ref rows Extra +1 SIMPLE t10000 ref J J 5 const 100 Using where with pushed condition +EXPLAIN +SELECT * FROM t10000 WHERE J = 0 AND K > 1; +id select_type table type possible_keys key key_len ref rows Extra +1 SIMPLE t10000 range PRIMARY,J J 9 NULL 50 Using where with pushed condition +EXPLAIN +SELECT * FROM t10000 WHERE J = 0 AND K < 1; +id select_type table type possible_keys key key_len ref rows Extra +1 SIMPLE t10000 range PRIMARY,J J 9 NULL 50 Using where with pushed condition +EXPLAIN +SELECT * FROM t10000 WHERE J = 0 AND K BETWEEN 1 AND 10; +id select_type table type possible_keys key key_len ref rows Extra +1 SIMPLE t10000 range PRIMARY,J J 9 NULL 25 Using where with pushed condition +EXPLAIN +SELECT * FROM t10000 WHERE J = 0 AND K = 1; +id select_type table type possible_keys key key_len ref rows Extra +1 SIMPLE NULL NULL NULL NULL NULL NULL NULL Impossible WHERE noticed after reading const tables +EXPLAIN +SELECT * FROM t10000 WHERE I = 0 AND J <> 1; +id select_type table type possible_keys key key_len ref rows Extra +1 SIMPLE t10000 range J,I I 10 NULL 150 Using where with pushed condition +EXPLAIN +SELECT * FROM t10000 WHERE I <> 0 AND J = 1; +id select_type table type possible_keys key key_len ref rows Extra +1 SIMPLE t10000 ref J,I J 5 const 100 Using where with pushed condition +EXPLAIN +SELECT * FROM t10000 WHERE I <> 0 AND J <> 1; +id select_type table type possible_keys key key_len ref rows Extra +1 SIMPLE t10000 range J,I J 5 NULL 1500 Using where with pushed condition +EXPLAIN +SELECT * FROM t10000 WHERE J <> 1 AND I = 0; +id select_type table type possible_keys key key_len ref rows Extra +1 SIMPLE t10000 range J,I I 10 NULL 150 Using where with pushed condition +EXPLAIN +SELECT * FROM t10000 WHERE J = 1 AND I <> 0; +id select_type table type possible_keys key key_len ref rows Extra +1 SIMPLE t10000 ref J,I J 5 const 100 Using where with pushed condition +EXPLAIN +SELECT * FROM t10000 WHERE J <> 1 AND I <> 0; +id select_type table type possible_keys key key_len ref rows Extra +1 SIMPLE t10000 range J,I J 5 NULL 1500 Using where with pushed condition DROP TABLE t10,t100,t10000; End of 5.1 tests === modified file 'mysql-test/suite/ndb/t/ndb_statistics.test' --- a/mysql-test/suite/ndb/t/ndb_statistics.test 2011-01-18 11:49:03 +0000 +++ b/mysql-test/suite/ndb/t/ndb_statistics.test 2011-02-28 10:42:04 +0000 @@ -62,6 +62,82 @@ EXPLAIN SELECT * FROM t10000 AS X JOIN t10000 AS Y ON Y.I=X.I AND Y.J = X.I; +# +# Bug #11804277: INCORRECT INDEX MAY BE SELECTED DUE TO INSUFFICIENT +# STATISTICS FROM CLUSTER +# + +# Open bounded range should return 10% of #rows in table +EXPLAIN +SELECT * FROM t100 WHERE k < 42; +EXPLAIN +SELECT * FROM t100 WHERE k > 42; +EXPLAIN +SELECT * FROM t10000 WHERE k < 42; +EXPLAIN +SELECT * FROM t10000 WHERE k > 42; + +#Closed bounded range should return 5% of #rows in table +EXPLAIN +SELECT * FROM t100 WHERE k BETWEEN 42 AND 10000; +EXPLAIN +SELECT * FROM t10000 WHERE k BETWEEN 42 AND 10000; + +#EQ-range selectivity depends on +# - key length specified +# - #rows in table. +# - unique/non-unique index +# - min 2% selectivity +# +# Possibly combined with open/closed ranges as +# above which further improves selectivity +# +EXPLAIN +SELECT * FROM t10000 WHERE I = 0; +EXPLAIN +SELECT * FROM t10000 WHERE J = 0; + +EXPLAIN +SELECT * FROM t10000 WHERE I = 0 AND J = 0; + +EXPLAIN +SELECT * FROM t10000 WHERE I = 0; +EXPLAIN +SELECT * FROM t10000 WHERE I = 0 AND J > 1; +EXPLAIN +SELECT * FROM t10000 WHERE I = 0 AND J < 1; +EXPLAIN +SELECT * FROM t10000 WHERE I = 0 AND J BETWEEN 1 AND 10; +EXPLAIN +SELECT * FROM t10000 WHERE I = 0 AND J = 1; + +EXPLAIN +SELECT * FROM t10000 WHERE J = 0; +EXPLAIN +SELECT * FROM t10000 WHERE J = 0 AND K > 1; +EXPLAIN +SELECT * FROM t10000 WHERE J = 0 AND K < 1; +EXPLAIN +SELECT * FROM t10000 WHERE J = 0 AND K BETWEEN 1 AND 10; +EXPLAIN +SELECT * FROM t10000 WHERE J = 0 AND K = 1; + +## Verify selection of 'best' index +## (The one of index I/J being EQ) +EXPLAIN +SELECT * FROM t10000 WHERE I = 0 AND J <> 1; +EXPLAIN +SELECT * FROM t10000 WHERE I <> 0 AND J = 1; +EXPLAIN +SELECT * FROM t10000 WHERE I <> 0 AND J <> 1; + +EXPLAIN +SELECT * FROM t10000 WHERE J <> 1 AND I = 0; +EXPLAIN +SELECT * FROM t10000 WHERE J = 1 AND I <> 0; +EXPLAIN +SELECT * FROM t10000 WHERE J <> 1 AND I <> 0; + DROP TABLE t10,t100,t10000; === modified file 'sql/ha_ndbcluster.cc' --- a/sql/ha_ndbcluster.cc 2011-02-24 09:46:11 +0000 +++ b/sql/ha_ndbcluster.cc 2011-02-28 10:42:04 +0000 @@ -11182,7 +11182,100 @@ ha_ndbcluster::records_in_range(uint inx DBUG_RETURN(rows); } - DBUG_RETURN(10); /* Good guess when you don't know anything */ + /* Use simple heuristics to estimate fraction + of 'stats.record' returned from range. + */ + do + { + if (stats.records == ~(ha_rows)0 || stats.records == 0) + { + /* Refresh statistics, only read from datanodes if 'use_exact_count' */ + THD *thd= current_thd; + if (update_stats(thd, THDVAR(thd, use_exact_count))) + break; + } + + Uint64 rows; + Uint64 table_rows= stats.records; + size_t eq_bound_len= 0; + size_t min_key_length= (min_key) ? min_key->length : 0; + size_t max_key_length= (max_key) ? max_key->length : 0; + + // Might have an closed/open range bound: + // Low range open + if (!min_key_length) + { + rows= (!max_key_length) + ? table_rows // No range was specified + : table_rows/10; // -oo .. -> 10% selectivity + } + // High range open + else if (!max_key_length) + { + rows= table_rows/10; // ..oo -> 10% selectivity + } + else + { + size_t bounds_len= min(min_key_length,max_key_length); + uint eq_bound_len= 0; + uint eq_bound_offs= 0; + + KEY_PART_INFO* key_part= key_info->key_part; + KEY_PART_INFO* end= key_part+key_info->key_parts; + for (; key_part != end; key_part++) + { + uint part_length= key_part->store_length; + if (eq_bound_offs+part_length > bounds_len || + memcmp(&min_key->key[eq_bound_offs], + &max_key->key[eq_bound_offs], + part_length)) + { + break; + } + eq_bound_len+= key_part->length; + eq_bound_offs+= part_length; + } + + if (!eq_bound_len) + { + rows= table_rows/20; // .. -> 5% + } + else + { + // Has an equality range on a leading part of 'key_length': + // - Null indicator, and HA_KEY_BLOB_LENGTH bytes in + // 'extra_length' are removed from key_fraction calculations. + // - Assume reduced selectivity for non-unique indexes + // by decreasing 'eq_fraction' by 20% + // - Assume equal selectivity for all eq_parts in key. + + double eq_fraction = (double)(eq_bound_len) / + (key_length - key_info->extra_length); + if (idx_type == ORDERED_INDEX) // Non-unique index -> less selectivity + eq_fraction/= 1.20; + if (eq_fraction >= 1.0) // Exact match -> 1 row + DBUG_RETURN(1); + + rows = (Uint64)(table_rows / pow(table_rows, eq_fraction)); + if (rows > (table_rows/50)) // EQ-range: Max 2% of rows + rows= (table_rows/50); + + if (min_key_length > eq_bound_offs) + rows/= 2; + if (max_key_length > eq_bound_offs) + rows/= 2; + } + } + + // Make sure that EQ is preferred even if row-count is low + if (eq_bound_len && rows < 2) // At least 2 rows as not exact + rows= 2; + else if (rows < 3) + rows= 3; + DBUG_RETURN(min(rows,table_rows)); + } while (0); + + DBUG_RETURN(10); /* Poor guess when you don't know anything */ } ulonglong ha_ndbcluster::table_flags(void) const --===============7571275159401741799== MIME-Version: 1.0 Content-Type: text/bzr-bundle; charset="us-ascii"; name="bzr/ole.john.aske@stripped" Content-Transfer-Encoding: 7bit Content-Disposition: inline # Bazaar merge directive format 2 (Bazaar 0.90) # revision_id: ole.john.aske@stripped\ # x8cfyiihnfry3vd2 # target_branch: file:///net/fimafeng09/export/home/tmp/oleja/mysql\ # /mysql-5.1-telco-7.0/ # testament_sha1: 4921703d6cb16026a7b2e5a23ac6433f40e07860 # timestamp: 2011-02-28 11:42:13 +0100 # base_revision_id: msabaratnam@stripped\ # bf3nv6ld10o77875 # # Begin bundle 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 /0UFBQopQ0/8XckU4UJCa+wqqA== --===============7571275159401741799==--