diff options
Diffstat (limited to 'sql/sql_statistics.cc')
-rw-r--r-- | sql/sql_statistics.cc | 1587 |
1 files changed, 1587 insertions, 0 deletions
diff --git a/sql/sql_statistics.cc b/sql/sql_statistics.cc new file mode 100644 index 00000000000..b11d925a593 --- /dev/null +++ b/sql/sql_statistics.cc @@ -0,0 +1,1587 @@ +/* Copyright (C) 2009 MySQL AB + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; version 2 of the License. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA */ + +/** + @file + + @brief + functions to update persitent statistical tables and to read from them + + @defgroup Query_Optimizer Query Optimizer + @{ +*/ + +#include "mysql_priv.h" +#include "sql_statistics.h" + +/* + The system variable 'optimizer_use_stat_tables' can take one of the + following values: + "never", "complementary", "preferably", "exclusively". + If the values of the variable 'optimizer_use_stat_tables' is set to + "never then any statistical data from the persistent statistical tables + is ignored by the optimizer. + If the value of the variable 'optimizer_use_stat_tables' is set to + "complementary" then a particular statistical characteristic is used + by the optimizer only if the database engine does not provide similar + statistics. For example, 'nulls_ratio' for table columns currently + are not provided by any engine. So optimizer uses this statistical data + from the statistical tables. At the same time it does not use + 'avg_frequency' for any index prefix from the statistical tables since + the a similar statistical characteristic 'records_per_key' can be + requested from the database engine. + If the value the variable 'optimizer_use_stat_tables' is set to + "preferably" the optimizer uses a particular statistical data only if + it can't be found in the statistical data. + If the value of the variable 'optimizer_use_stat_tables' is set to + "exclusively" the optimizer never uses statistical data that can be + returned by the database engine Only statistical data from the + statistical tables is used. + If an ANALYZE command is executed then it results in collecting + statistical data for the tables specified by the command and storing + the collected statistics in the persistent statistical tables only + when the value of the variable 'optimizer_use_stat_tables' is not + equal to "never". +*/ + +const char *optimizer_use_stat_tables_modes[] = + {"never", "complentary", "preferably", "exclusively", NullS}; + +TYPELIB optimizer_use_stat_tables_typelib= { + array_elements(optimizer_use_stat_tables_modes) - 1, "", + optimizer_use_stat_tables_modes, NULL +}; + +/* Currently there are only 3 persistent statistical tables */ +static const uint STATISTICS_TABLES= 3; + +/* + The names of the statistical tables in this list must correspond the + definitions of the tables in the file ../scripts/mysql_system_tables.sql +*/ +static const char *STAT_TABLE_NAME[STATISTICS_TABLES]= +{ + "table_stat", + "column_stat", + "index_stat" +}; + +/* + @details + The function sets null bits stored in the bitmap table_field->write_stat + for all statistical values collected for a column. +*/ +inline void set_nulls_for_write_column_stat_values(Field *table_field) +{ + table_field->write_stat.column_stat_nulls= + ((1 << (COLUMN_STAT_AVG_FREQUENCY-COLUMN_STAT_COLUMN_NAME))-1) << + (COLUMN_STAT_COLUMN_NAME+1); +} + +/* + @details + The function sets null bits stored in the bitmap table_field->read_stat + for all statistical values collected for a column. +*/ +inline void set_nulls_for_read_column_stat_values(Field *table_field) +{ + table_field->read_stat.column_stat_nulls= + ((1 << (COLUMN_STAT_AVG_FREQUENCY-COLUMN_STAT_COLUMN_NAME))-1) << + (COLUMN_STAT_COLUMN_NAME+1); +} + +/* + @details + The function removes the null bit stored in the bitmap + table_field->write_stat for the statistical value collected + on the statistical column number stat_field_no. +*/ +inline void set_not_null_for_write_column_stat_value(Field *table_field, + uint stat_field_no) +{ + table_field->write_stat.column_stat_nulls&= ~(1 << stat_field_no); +} + +/* + @details + The function removes the null bit stored in the bitmap + table_field->read_stat for the statistical value collected + on the statistical column number stat_field_no. +*/ +inline void set_not_null_for_read_column_stat_value(Field *table_field, + uint stat_field_no) +{ + table_field->read_stat.column_stat_nulls&= ~(1 << stat_field_no); +} + +/* + @details + The function checks the null bit stored in the bitmap + table_field->read_stat for the statistical value collected + on the statistical column number stat_field_no. +*/ +inline bool check_null_for_write_column_stat_value(Field *table_field, + uint stat_field_no) +{ + return table_field->write_stat.column_stat_nulls & (1 << stat_field_no); +} + +/* + Stat_table is the base class for classes Table_stat, Column_stat and + Index_stat. The methods of these classes allow us table to read + statistical data from statistical tables and write collected statistical + data into statistical data. Objects of the classes Table_stat, Column_stat + and Index stat are used for reading/writing statistics from/into + persistent tables table_stat, column_stat and index_stat correspondingly. + These tables are stored in the system database 'mysql'. + + Statistics is read and written always for a given database table t. When + an object of any of these classes is created a pointer to the TABLE + structure for this database table is passed as a parameter to the constructor + of the object. The other parameter is a pointer to the TABLE structure for + the corresponding statistical table st. So construction of an object to + read/write statistical data on table t from/into statistical table st + requires both table t and st to be opened. + + Reading/writing statistical data from/into a statistical table is always + performed by key. At the moment there is only one key defined for each + statistical table and this key is primary. + The primary key for the table table_stat is built as (db_name, table_name). + The primary key for the table column_stat is built as (db_name, table_name, + column_name). + The primary key for the table index_stat is built as (db_name, table_name, + index_name, prefix_arity). + + Reading statistical data from a statistical table is performed by the + following pattern. First a table dependent method sets the values of the + the fields that comprise the lookup key. Then an implementation of the + method get_stat_values() declared in Stat_table as a pure virtual method + finds the row from the statistical table by the set key. If the row is + found the values of statistical fields are read from this row and are + distributed in the internal structures. + + Let's assume the statistical data is read for table t from database db. + + When statistical data is searched in the table table_stat first + Table_stat::set_key_fields() should set the fields of db_name and + table_name. Then get_stat_values looks for a row by the set key value, + and, if the row is found, reads the value from the column + table_stat.cardinality into the field read_stat.cardinality of the TABLE + structure for table t and sets the value of read_stat.cardinality_is_null + from this structure to FALSE. If the value of the 'cardinality' column + in the row is null or if no row is found read_stat.cardinality_is_null + is set to TRUE. + + When statistical data is searched in the table column_stat first + Column_stat::set_key_fields() should set the fields of db_name, table_name + and column_name with column_name taken out of the only parameter f of the + Field* type passed to this method. After this get_stat_values looks + for a row by the set key value. If the row is found the values of statistical + data columns min_value, max_value, nulls_ratio, avg_length, avg_frequency + are read into internal structures. Values of nulls_ratio, avg_length, + avg_frequency are read into the corresponding fields of the read_stat + structure from the Field object f, while values from min_value and max_value + are copied into the min_value and max_value record buffers attached to the + TABLE structure for table t. + If the value of a statistical column in the found row is null, then the + corresponding flag in the f->read_stat.column_stat_nulls bitmap is set off. + Otherwise the flag is set on. If no row is found for the column the all flags + in f->column_stat_nulls are set off. + + When statistical data is searched in the table index_stat first + Index_stat::set_key_fields() has to be called to set the fields of db_name, + table_name, index_name and prefix_arity. The value of index_name is extracted + from the first parameter key_info of the KEY* type passed to the method. + This parameter specifies the index of interest idx. The second parameter + passed to the method specifies the arity k of the index prefix for which + statistical data is to be read. E.g. if the index idx consists of 3 + components (p1,p2,p3) the table index_stat usually will contain 3 rows for + this index: the first - for the prefix (p1), the second - for the prefix + (p1,p2), and the third - for the the prefix (p1,p2,p3). After the key fields + has been set a call of get_stat_value looks for a row by the set key value. + If the row is found and the value of the avg_frequency column is not null + then this value is assigned to key_info->read_stat.avg_frequency[k]. + Otherwise 0 is assigned to this element. + + The method Stat_table::update_stat is used to write statistical data + collected in the internal structures into a statistical table st. + It is assumed that before any invocation of this method a call of the + function st.set_key_fields has set the values of the primary key fields + that serve to locate the row from the statistical table st where the + the colected statistical data from internal structures are to be written + to. The statistical data is written from the counterparts of the + statistical fields of internal structures into which it would be read + by the functions get_stat_values. The counterpart fields are used + only when statistics is collected + When updating/inserting a row from the statistical table st the method + Stat_table::update_stat calls the implementation of the pure virtual + method store_field_values to transfer statistical data from the fields + of internal structures to the fields of record buffer used for updates + of the statistical table st. +*/ + +class Stat_table +{ +private: + /* Handler used for the retrieval of the statistical table stat_table */ + handler *stat_file; + + KEY *stat_key_info; /* Structure for the index to access stat_table */ + uint stat_key_length; /* Length of the key to access stat_table */ + uchar *record[2]; /* Record buffers used to access/update stat_table */ + uint stat_key_idx; /* The number of the key to access stat_table */ + +protected: + /* Statistical table to read statistics from or to update */ + TABLE *stat_table; + + /* Table for which statistical data is read / updated */ + TABLE *table; + char *db_name; /* Name of the database containing 'table' */ + uint db_name_len; /* Length of db_name */ + char *table_name; /* Name of the table 'table' */ + uint table_name_len; /* Name of table_name */ + +public: + + /* + @details + This constructor has to be called by any constructor of the derived + classes. The constructor 'tunes' the private and protected members of + the constructed object to the statistical table 'stat_table' with the + statistical data of our interest and to the table 'tab' for which this + statistics has been collected. + */ + Stat_table(TABLE *stat, TABLE *tab) :stat_table(stat), table(tab) + { + stat_file= stat_table->file; + /* Currently any statistical table has only one key */ + stat_key_idx= 0; + stat_key_info= &stat_table->key_info[stat_key_idx]; + stat_key_length= stat_key_info->key_length; + record[0]= stat_table->record[0]; + record[1]= stat_table->record[1]; + db_name= table->s->db.str; + db_name_len= table->s->db.length; + table_name= table->s->table_name.str; + table_name_len= table->s->table_name.length; + } + + virtual ~Stat_table() {} + + /* + @brief + Store statistical data into fields of the statistical table + + @details + This is a purely virtual method. + The implementation for any derived class shall put the appropriate + statistical data into the corresponding fields of stat_table. + + @note + The method is called by the update_stat function. + */ + virtual void store_stat_fields()= 0; + + /* + @brief + Read statistical data from fields of the statistical table + + @details + This is a purely virtual method. + The implementation for any derived read shall read the appropriate + statistical data from the corresponding fields of stat_table. + */ + virtual void get_stat_values()= 0; + + /* + @breif + Find a record by key in the statistical table + + @details + The function looks for a record in stat_table by its primary key. + It assumes that the key fields have been already stored in the record + buffer of stat_table. + + @retval + FALSE the record is not found + @retval + TRUE the record is found + */ + bool find_stat() + { + uchar key[MAX_KEY_LENGTH]; + key_copy(key, record[0], stat_key_info, stat_key_length); + return !stat_file->ha_index_read_idx_map(record[0], stat_key_idx, key, + HA_WHOLE_KEY, HA_READ_KEY_EXACT); + } + + /* + @breif + Update/insert a record in the statistical table with new statistics + + @details + The function first looks for a record by its primary key in the statistical + table stat_table. If the record is found the function updates statistical + fields of the records. The data for these fields are taken from internal + structures containing info on the table 'table'. If the record is not + found the function inserts a new record with the primary key set to the + search key and the statistical data taken from the internal structures. + The function assumes that the key fields have been already stored in + the record buffer of stat_table. + + @retval + FALSE success with the update/insert of the record + @retval + TRUE failure with the update/insert of the record + + @note + The function calls the virtual method store_stat_fields to populate the + statistical fields of the updated/inserted row with new statistics. + */ + bool update_stat() + { + int err; + if (find_stat()) + { + store_record(stat_table, record[1]); + store_stat_fields(); + if ((err= stat_file->ha_update_row(record[1], record[0])) && + err != HA_ERR_RECORD_IS_THE_SAME) + return TRUE; + } + else + { + store_stat_fields(); + if ((err= stat_file->ha_write_row(record[0]))) + return TRUE; + } + return FALSE; + } +}; + + +/* + An object of the class Table_stat is created to read statistical + data on tables from the statistical table table_stat or to update + table_stat with such statistical data. + Rows from the statistical table are read and updated always by + primary key. +*/ + +class Table_stat: public Stat_table +{ +private: + Field *db_name_field; /* Field for the column table_stat.db_name */ + Field *table_name_field; /* Field for the column table_stat.table_name */ + +public: + + /* + @details + The constructor 'tunes' the private and protected members of the + constructed object for the statistical table table_stat to read/update + statistics on table 'tab'. The TABLE structure for the table table_stat + must be passed as a value for the parameter 'stat'. + */ + Table_stat(TABLE *stat, TABLE *tab) :Stat_table(stat, tab) + { + db_name_field= stat_table->field[TABLE_STAT_DB_NAME]; + table_name_field= stat_table->field[TABLE_STAT_TABLE_NAME]; + } + + /* + @brief + Set the key fields for the statistical table table_stat + + @details + The function sets the values of the fields db_name and table_name + in the record buffer for the statistical table table_stat. + These fields comprise the primary key for the table. + + @note + The function is supposed to be called before any use of the + method find_stat for an object of the Table_stat class. + */ + void set_key_fields() + { + db_name_field->store(db_name, db_name_len, &my_charset_bin); + table_name_field->store(table_name, table_name_len, &my_charset_bin); + } + + /* + @brief + Store statistical data into statistical fields of table_stat + + @details + This implementation of a purely virtual method sets the value of the + column 'cardinality' of the statistical table table_stat according to + the value of the flag write_stat.cardinality_is_null and the value of + the field write_stat.cardinality' from the TABLE structure for 'table'. + */ + void store_stat_fields() + { + Field *stat_field= stat_table->field[TABLE_STAT_CARDINALITY]; + if (table->write_stat.cardinality_is_null) + stat_field->set_null(); + else + { + stat_field->set_notnull(); + stat_field->store(table->write_stat.cardinality); + } + } + + /* + @brief + Read statistical data from statistical fields of table_stat + + @details + This implementation of a purely virtual method first looks for a record + the statistical table table_stat by its primary key set the record + buffer with the help of Table_stat::set_key_fields. Then, if the row is + found the function reads the value of the column 'cardinality' of the table + table_stat and sets the value of the flag read_stat.cardinality_is_null + and the value of the field read_stat.cardinality' from the TABLE structure + for 'table' accordingly. + */ + void get_stat_values() + { + table->read_stat.cardinality_is_null= TRUE; + table->read_stat.cardinality= 0; + if (find_stat()) + { + Field *stat_field= stat_table->field[TABLE_STAT_CARDINALITY]; + if (!stat_field->is_null()) + { + table->read_stat.cardinality_is_null= FALSE; + table->read_stat.cardinality= stat_field->val_int(); + } + } + } + +}; + + +/* + An object of the class Column_stat is created to read statistical data + on table columns from the statistical table column_stat or to update + column_stat with such statistical data. + Rows from the statistical table are read and updated always by + primary key. +*/ + +class Column_stat: public Stat_table +{ +private: + Field *db_name_field; /* Field for the column column_stat.db_name */ + Field *table_name_field; /* Field for the column column_stat.table_name */ + Field *column_name_field; /* Field for the column column_stat.column_name */ + + Field *table_field; /* Field from 'table' to read /update statistics on */ + +public: + + /* + @details + The constructor 'tunes' the private and protected members of the + constructed object for the statistical table column_stat to read/update + statistics on fields of the table 'tab'. The TABLE structure for the table + column_stat must be passed as a value for the parameter 'stat'. + */ + Column_stat(TABLE *stat, TABLE *tab) :Stat_table(stat, tab) + { + db_name_field= stat_table->field[COLUMN_STAT_DB_NAME]; + table_name_field= stat_table->field[COLUMN_STAT_TABLE_NAME]; + column_name_field= stat_table->field[COLUMN_STAT_COLUMN_NAME]; + } + + /* + @brief + Set the key fields for the statistical table column_stat + + @param + column Field for the 'table' column to read/update statistics on + + @details + The function sets the values of the fields db_name, table_name and + column_name in the record buffer for the statistical table column_stat. + These fields comprise the primary key for the table. + + @note + The function is supposed to be called before any use of the + method find_stat for an object of the Column_stat class. + */ + void set_key_fields(Field *column) + { + db_name_field->store(db_name, db_name_len, &my_charset_bin); + table_name_field->store(table_name, table_name_len, &my_charset_bin); + table_field= column; + const char *column_name= column->field_name; + column_name_field->store(column_name, strlen(column_name), &my_charset_bin); + } + + /* + @brief + Store statistical data into statistical fields of column_stat + + @details + This implementation of a purely virtual method sets the value of the + columns 'min_value', 'max_value', 'nulls_ratio', 'avg_length' and + 'avg_frequency' of the stistical table columns_stat according to the + contents of the bitmap write_stat.column_stat_nulls and the values + of the fields min_value, max_value, nulls_ratio, avg_length and + avg_frequency of the structure write_stat from the Field structure + for the field 'table_field'. + The value of the k-th column in the table columns_stat is set to NULL + if the k-th bit in the bitmap 'column_stat_nulls' is set to 1. + + @note + A value from the field min_value/max_value is always converted + into a utf8 string. If the length of the column 'min_value'/'max_value' + is less than the length of the string the string is trimmed to fit the + length of the column. + */ + void store_stat_fields() + { + char buff[MAX_FIELD_WIDTH]; + String val(buff, sizeof(buff), &my_charset_utf8_bin); + + for (uint i= COLUMN_STAT_MIN_VALUE; i <= COLUMN_STAT_AVG_FREQUENCY; i++) + { + Field *stat_field= stat_table->field[i]; + if (check_null_for_write_column_stat_value(table_field, i)) + stat_field->set_null(); + else + { + stat_field->set_notnull(); + switch (i) { + case COLUMN_STAT_MIN_VALUE: + if (table_field->type() == MYSQL_TYPE_BIT) + stat_field->store(table_field->write_stat.min_value->val_int()); + else + { + table_field->write_stat.min_value->val_str(&val); + stat_field->store(val.ptr(), val.length(), &my_charset_utf8_bin); + } + break; + case COLUMN_STAT_MAX_VALUE: + if (table_field->type() == MYSQL_TYPE_BIT) + stat_field->store(table_field->write_stat.max_value->val_int()); + else + { + table_field->write_stat.max_value->val_str(&val); + stat_field->store(val.ptr(), val.length(), &my_charset_utf8_bin); + } + break; + case COLUMN_STAT_NULLS_RATIO: + stat_field->store(table_field->write_stat.nulls_ratio); + break; + case COLUMN_STAT_AVG_LENGTH: + stat_field->store(table_field->write_stat.avg_length); + break; + case COLUMN_STAT_AVG_FREQUENCY: + stat_field->store(table_field->write_stat.avg_frequency); + break; + } + } + } + } + + /* + @brief + Read statistical data from statistical fields of column_stat + + @details + This implementation of a purely virtual method first looks for a record + the statistical table column_stat by its primary key set the record + buffer with the help of Column_stat::set_key_fields. Then, if the row is + found, the function reads the values of the columns 'min_value', + 'max_value', 'nulls_ratio', 'avg_length' and 'avg_frequency' of the + table column_stat and sets accordingly the value of the bitmap + read_stat.column_stat_nulls' and the values of the fields min_value, + max_value, nulls_ratio, avg_length and avg_frequency of the structure + read_stat from the Field structure for the field 'table_field'. + */ + void get_stat_values() + { + set_nulls_for_read_column_stat_values(table_field); + + if (table_field->read_stat.min_value) + table_field->read_stat.min_value->set_null(); + if (table_field->read_stat.max_value) + table_field->read_stat.max_value->set_null(); + + if (find_stat()) + { + char buff[MAX_FIELD_WIDTH]; + String val(buff, sizeof(buff), &my_charset_utf8_bin); + + for (uint i= COLUMN_STAT_MIN_VALUE; i <= COLUMN_STAT_AVG_FREQUENCY; i++) + { + Field *stat_field= stat_table->field[i]; + + if (!stat_field->is_null() && + (i > COLUMN_STAT_MAX_VALUE || + (i == COLUMN_STAT_MIN_VALUE && table_field->read_stat.min_value) || + (i == COLUMN_STAT_MAX_VALUE && table_field->read_stat.max_value))) + { + set_not_null_for_read_column_stat_value(table_field, i); + + switch (i) { + case COLUMN_STAT_MIN_VALUE: + stat_field->val_str(&val); + table_field->read_stat.min_value->store(val.ptr(), val.length(), + &my_charset_utf8_bin); + break; + case COLUMN_STAT_MAX_VALUE: + stat_field->val_str(&val); + table_field->read_stat.max_value->store(val.ptr(), val.length(), + &my_charset_utf8_bin); + break; + case COLUMN_STAT_NULLS_RATIO: + table_field->read_stat.nulls_ratio= stat_field->val_real(); + break; + case COLUMN_STAT_AVG_LENGTH: + table_field->read_stat.avg_length= stat_field->val_real(); + break; + case COLUMN_STAT_AVG_FREQUENCY: + table_field->read_stat.avg_frequency= stat_field->val_real(); + break; + } + } + } + } + } + +}; + + +/* + An object of the class Index_stat is created to read statistical + data on index prefixes from the statistical table index_stat or + to update index_stat with such statistical data. + Rows from the statistical table are read and updated always by + primary key. +*/ + +class Index_stat: public Stat_table +{ +private: + Field *db_name_field; /* Field for the column index_stat.db_name */ + Field *table_name_field; /* Field for the column index_stat.table_name */ + Field *index_name_field; /* Field for the column index_stat.table_name */ + Field *prefix_arity_field; /* Field for the column index_stat.prefix_arity */ + + KEY *table_key_info; /* Info on the index to read/update statistics on */ + uint prefix_arity; /* Number of components of the index prefix of interest */ + +public: + + /* + @details + The constructor 'tunes' the private and protected members of the + constructed object for the statistical table index_stat to read/update + statistics on prefixes of different indexes of the table 'tab'. + The TABLE structure for the table index_stat must be passed as a value + for the parameter 'stat'. + */ + Index_stat(TABLE *stat, TABLE *tab) :Stat_table(stat, tab) + { + db_name_field= stat_table->field[INDEX_STAT_DB_NAME]; + table_name_field= stat_table->field[INDEX_STAT_TABLE_NAME]; + index_name_field= stat_table->field[INDEX_STAT_INDEX_NAME]; + prefix_arity_field= stat_table->field[INDEX_STAT_PREFIX_ARITY]; + + } + + /* + @brief + Set the key fields for the statistical table index_stat + + @param + index_info Info for the index of 'table' to read/update statistics on + @param + index_prefix_arity Number of components in the index prefix of interest + + + @details + The function sets the values of the fields db_name, table_name and + index_name, prefix_arity in the record buffer for the statistical + table index_stat. These fields comprise the primary key for the table. + + @note + The function is supposed to be called before any use of the + method find_stat for an object of the Index_stat class. + */ + void set_key_fields(KEY *index_info, uint index_prefix_arity) + { + db_name_field->store(db_name, db_name_len, &my_charset_bin); + table_name_field->store(table_name, table_name_len, &my_charset_bin); + table_key_info= index_info; + char *index_name= index_info->name; + index_name_field->store(index_name, strlen(index_name), &my_charset_bin); + prefix_arity= index_prefix_arity; + prefix_arity_field->store(index_prefix_arity, TRUE); + } + + /* + @brief + Store statistical data into statistical fields of tableindex_stat + + @details + This implementation of a purely virtual method sets the value of the + column 'avg_frequency' of the statistical table index_stat according to + the value of write_stat.avg_frequency[Index_stat::prefix_arity] + from the KEY_INFO structure 'table_key_info'. + If the value of write_stat. avg_frequency[Index_stat::prefix_arity] is + equal to 0, the value of the column is set to NULL. + */ + void store_stat_fields() + { + Field *stat_field= stat_table->field[INDEX_STAT_AVG_FREQUENCY]; + double avg_frequency= + table_key_info->write_stat.avg_frequency[prefix_arity-1]; + if (avg_frequency == 0) + stat_field->set_null(); + else + { + stat_field->set_notnull(); + stat_field->store(avg_frequency); + } + } + + /* + @brief + Read statistical data from statistical fields of index_stat + + @details + This implementation of a purely virtual method first looks for a record the + statistical table index_stat by its primary key set the record buffer with + the help of Index_stat::set_key_fields. If the row is found the function + reads the value of the column 'avg_freguency' of the table index_stat and + sets the value of read_stat.avg_frequency[Index_stat::prefix_arity] + from the KEY_INFO structure 'table_key_info' accordingly. If the value of + the column is NULL, read_stat.avg_frequency[Index_stat::prefix_arity] is + set to 0. Otherwise, read_stat.avg_frequency[Index_stat::prefix_arity] is + set to the value of the column. + */ + void get_stat_values() + { + double avg_frequency= 0; + if(find_stat()) + { + Field *stat_field= stat_table->field[INDEX_STAT_AVG_FREQUENCY]; + if (!stat_field->is_null()) + avg_frequency= stat_field->val_real(); + } + table_key_info->read_stat.avg_frequency[prefix_arity-1]= avg_frequency; + } + +}; + + +/* + The class Count_distinct_field is a helper class used to calculate + the number of distinct values for a column. The class employs the + Unique class for this purpose. + The class Count_distinct_field is used only by the function + collect_statistics_from_table to calculate the values for + column avg_frequency of the statistical table column_stat. +*/ + +class Count_distinct_field: public Sql_alloc +{ +protected: + + /* Field for which the number of distinct values is to be find out */ + Field *table_field; + Unique *tree; /* The helper object to contain distinct values */ + uint tree_key_length; /* The length of the keys for the elements of 'tree */ + +public: + + /* + @param + field Field for which the number of distinct values is + to be find out + @param + max_heap_table_size The linit for the memory used by the RB tree container + of the constructed Unique object 'tree' + + @details + The constructor sets the values of 'table_field' and 'tree_key_length', + and then calls the 'new' operation to create a Unique object for 'tree'. + The type of 'field' and the value max_heap_table_size of determine the set + of the parameters to be passed to the constructor of the Unique object. + */ + Count_distinct_field(Field *field, uint max_heap_table_size) + { + qsort_cmp2 compare_key; + void* cmp_arg; + enum enum_field_types f_type= field->type(); + + table_field= field; + tree_key_length= field->pack_length(); + + if ((f_type == MYSQL_TYPE_VARCHAR) || + (!field->binary() && (f_type == MYSQL_TYPE_STRING || + f_type == MYSQL_TYPE_VAR_STRING))) + { + compare_key= (qsort_cmp2) simple_str_key_cmp; + cmp_arg= (void*) field; + } + else + { + cmp_arg= (void*) &tree_key_length; + compare_key= (qsort_cmp2) simple_raw_key_cmp; + } + + tree= new Unique(compare_key, cmp_arg, + tree_key_length, max_heap_table_size); + } + + virtual ~Count_distinct_field() + { + delete tree; + tree= NULL; + } + + /* + @brief + Check whether the Unique object tree has been succesfully created + */ + bool exists() + { + return (tree != NULL); + } + + /* + @brief + Add the value of 'field' to the container of the Unique object 'tree' + */ + virtual bool add() + { + return tree->unique_add(table_field->ptr); + } + + /* + @brief + Calculate the number of elements accumulated in the container of 'tree' + */ + ulonglong get_value() + { + ulonglong count; + if (tree->elements == 0) + return (ulonglong) tree->elements_in_tree(); + count= 0; + tree->walk(count_distinct_walk, (void*) &count); + return count; + } +}; + + +/* + The class Count_distinct_field_bit is derived from the class + Count_distinct_field to be used only for fields of the MYSQL_TYPE_BIT type. + The class provides a different implementation for the method add +*/ + +class Count_distinct_field_bit: public Count_distinct_field +{ +public: + Count_distinct_field_bit(Field *field, uint max_heap_table_size) + :Count_distinct_field(field, max_heap_table_size) {} + bool add() + { + longlong val= table_field->val_int(); + return tree->unique_add(&val); + } +}; + + +/* + The class Index_prefix_calc is a helper class used to calculate the values + for the column 'avg_frequency' of the statistical table index_stat. + For any table t from the database db and any k-component prefix of the + index i for this table the row from index_stat with the primary key + (db,t,i,k) must contain in the column 'avg_frequency' either NULL or + the number that is the ratio of N and V, where N is the number of index + entries without NULL values in the first k components of the index i, + and V is the number of distinct tuples composed of the first k components + encountered among these index entries. + Currently the objects of this class are used only by the function + collect_statistics_for_index. +*/ + +class Index_prefix_calc: public Sql_alloc +{ +private: + /* Table containing index specified by index_info */ + TABLE *index_table; + /* Info for the index i for whose prefix 'avg_frequency' is calculated */ + KEY *index_info; + /* The maximum number of the components in the prefixes of interest */ + uint prefixes; + bool empty; + + /* This structure is created for every k components of the index i */ + class Prefix_calc_state + { + public: + /* + The number of the scanned index entries without nulls + in the first k components + */ + ulonglong entry_count; + /* + The number if the scanned index entries without nulls with + the last encountered k-component prefix + */ + ulonglong prefix_count; + /* The values of the last encoutered k-component prefix */ + Cached_item *last_prefix; + }; + + /* + Array of structures used to calculate 'avg_frequency' for different + prefixes of the index i + */ + Prefix_calc_state *calc_state; + +public: + Index_prefix_calc(TABLE *table, KEY *key_info) + : index_table(table), index_info(key_info) + { + uint i; + Prefix_calc_state *state; + uint key_parts= key_info->key_parts; + empty= TRUE; + prefixes= 0; + if ((calc_state= + (Prefix_calc_state *) sql_alloc(sizeof(Prefix_calc_state)*key_parts))) + { + uint keyno= key_info-table->key_info; + for (i= 0, state= calc_state; i < key_parts; i++, state++) + { + /* + Do not consider prefixes containing a component that is only part + of the field. This limitation is set to avoid fetching data when + calculating the values of 'avg_frequency' for prefixes. + */ + if (!key_info->key_part[i].field->part_of_key.is_set(keyno)) + break; + + if (!(state->last_prefix= + new Cached_item_field(key_info->key_part[i].field))) + break; + state->entry_count= state->prefix_count= 0; + prefixes++; + } + } + } + + /* + @breif + Change the elements of calc_state after reading the next index entry + + @details + This function is to be called at the index scan each time the next + index entry has been read into the record buffer. + For each of the index prefixes the function checks whether nulls + are encountered in any of the k components of the prefix. + If this is not the case the value of calc_state[k-1].entry_count + is incremented by 1. Then the function checks whether the value of + any of these k components has changed. If so, the value of + calc_state[k-1].prefix_count is incremented by 1. + */ + void add() + { + uint i; + Prefix_calc_state *state; + uint first_changed= prefixes; + for (i= prefixes, state= calc_state+prefixes-1; i; i--, state--) + { + if (state->last_prefix->cmp()) + first_changed= i-1; + } + if (empty) + { + first_changed= 0; + empty= FALSE; + } + for (i= 0, state= calc_state; i < prefixes; i++, state++) + { + if (state->last_prefix->null_value) + break; + if (i >= first_changed) + state->prefix_count++; + state->entry_count++; + } + } + + /* + @brief + Calculate the values of avg_frequency for all prefixes of an index + + @details + This function is to be called after the index scan to count the number + of distinct index prefixes has been done. The function calculates + the value of avg_frequency for the index prefix with k components + as calc_state[k-1].entry_count/calc_state[k-1].prefix_count. + If calc_state[k-1].prefix_count happens to be 0, the value of + avg_frequency[k-1] is set to 0, i.e. is considered as unknown. + */ + void get_avg_frequency() + { + uint i; + Prefix_calc_state *state; + for (i= 0, state= calc_state; i < prefixes; i++, state++) + { + if (i < prefixes) + { + index_info->write_stat.avg_frequency[i]= + state->prefix_count == 0 ? 0 : + (double) state->entry_count / state->prefix_count; + } + } + } +}; + + +/* + @brief + Create fields for min/max values to collect/read column statistics + + @param + table Table the fields are created for + @param + for_write Those fields are created that are used to collect statistics + + @note + The function first allocates record buffers to store min/max values + for 'table's fields. Then for each table field f it creates Field structures + that points to these buffers rather that to the record buffer as the + Field object for f does. The pointers of the created fields are placed + either in the write_stat or in the read_stat structure of the Field + object for f, depending on the value of the 'for_write' parameter. + + @note + The buffers allocated when min/max values are used to read statistics + from the persistent statistical tables differ from those buffers that + are used when statistics on min/max values for column is collected. + The same is true for the fields created for min/max values. +*/ + +static +void create_min_max_stistical_fields(TABLE *table, bool for_write) +{ + Field *table_field; + Field **field_ptr; + uchar *record; + uint rec_buff_length= table->s->rec_buff_length; + + for (field_ptr= table->field; *field_ptr; field_ptr++) + { + table_field= *field_ptr; + if (for_write) + table_field->write_stat.max_value= + table_field->write_stat.min_value= NULL; + else + table_field->read_stat.max_value= + table_field->read_stat.min_value= NULL; + } + + if ((record= (uchar *) alloc_root(&table->mem_root, 2*rec_buff_length))) + { + for (uint i=0; i < 2; i++, record+= rec_buff_length) + { + for (field_ptr= table->field; *field_ptr; field_ptr++) + { + Field *fld; + table_field= *field_ptr; + my_ptrdiff_t diff= record-table->record[0]; + if (!(fld= table_field->clone(&table->mem_root, table, diff, TRUE))) + continue; + if (i == 0) + { + if (for_write) + table_field->write_stat.min_value= fld; + else + table_field->read_stat.min_value= fld; + } + else + { + if (for_write) + table_field->write_stat.max_value= fld; + else + table_field->read_stat.max_value= fld; + } + } + } + } +} + +/* + @brief + Collect statistical data on an index + + @param + table The table the index belongs to + index The number of this index in the table + + @details + The function collects the value of 'avg_frequency' for the prefixes + on an index from 'table'. The index is specified by its number. + If the scan is successful the calculated statistics is saved in the + elements of the array write_stat.avg_frequency of the KEY_INFO structure + for the index. The statistics for the prefix with k components is saved + in the element number k-1. + + @retval + 0 If the statistics has been successfully collected + @retval + 1 Otherwise + + @note + The function collects statistics for the index prefixes for one index + scan during which no data is fetched from the table records. That's why + statistical data for prefixes that contain part of a field is not + collected. + The function employs an object of the helper class Index_prefix_calc to + count for each index prefix the number of index entries without nulls and + the number of distinct entries among them. + +*/ + +static +int collect_statistics_for_index(TABLE *table, uint index) +{ + int rc= 0; + KEY *key_info= &table->key_info[index]; + ha_rows rows= 0; + Index_prefix_calc index_prefix_calc(table, key_info); + DBUG_ENTER("collect_statistics_for_index"); + + table->key_read= 1; + table->file->extra(HA_EXTRA_KEYREAD); + + table->file->ha_index_init(index, TRUE); + rc= table->file->ha_index_first(table->record[0]); + while (rc != HA_ERR_END_OF_FILE) + { + if (rc) + break; + rows++; + index_prefix_calc.add(); + rc= table->file->ha_index_next(table->record[0]); + } + table->key_read= 0; + table->file->ha_index_end(); + + rc= (rc == HA_ERR_END_OF_FILE) ? 0 : 1; + + if (!rc) + index_prefix_calc.get_avg_frequency(); + + DBUG_RETURN(rc); +} + +/* + @brief + Collect statistical data for a table + + @param + thd The thread handle + @param + table The table to collect statistics on + + @details + The function collects data for various statistical characteristics on + the table 'table'. These data is saved in the internal fields that could + be reached from 'table'. The data is prepared to be saved in the persistent + statistical table by the function update_statistics_for_table. + The collected statistical values are not placed in the same fields that + keep the statistical data used by the optimizer. Therefore, at any time, + there is no collision between the statistics being collected and the one + used by the optimizer to look for optimal query execution plans for other + clients. + + @retval + 0 If the statistics has been successfully collected + @retval + 1 Otherwise + + @note + The function first collects statistical data for statistical characteristics + to be saved in the statistical tables table_stat and column_stat. To do this + it performs a full table scan of 'table'. At this scan the function collects + statistics on each column of the table and count the total number of the + scanned rows. To calculate the value of 'avg_frequency' for a column the + function constructs an object of the helper class Count_distinct_field + (or its derivation). Currently this class cannot count the number of + distinct values for blob columns. So the value of 'avg_frequency' for + blob columns is always null. + After the full table scan the function calls collect_statistics_for_index + for each table index. The latter performs full index scan for each index. + + @note + Currently the statistical data is collected indiscriminately for all + columns/indexes of 'table', for all statistical characteristics. + TODO. Collect only specified statistical characteristics for specified + columns/indexes. + + @note + Currently the process of collecting statistical data is not optimized. + For example, 'avg_frequency' for a column could be copied from the + 'avg_frequency' collected for an index if this column is used as the + first component of the index. Min and min values for this column could + be extracted from the index as well. +*/ + +int collect_statistics_for_table(THD *thd, TABLE *table) +{ + int rc; + Field **field_ptr; + Field *table_field; + ha_rows rows= 0; + handler *file=table->file; + + DBUG_ENTER("collect_statistics_for_table"); + + table->write_stat.cardinality_is_null= TRUE; + table->write_stat.cardinality= 0; + + create_min_max_stistical_fields(table, TRUE); + + for (field_ptr= table->field; *field_ptr; field_ptr++) + { + table_field= *field_ptr; + uint max_heap_table_size= thd->variables.max_heap_table_size; + set_nulls_for_write_column_stat_values(table_field); + table_field->nulls= 0; + table_field->column_total_length= 0; + if (table_field->flags & BLOB_FLAG) + table_field->count_distinct= NULL; + else + { + table_field->count_distinct= + table_field->type() == MYSQL_TYPE_BIT ? + new Count_distinct_field_bit(table_field, max_heap_table_size) : + new Count_distinct_field(table_field, max_heap_table_size); + } + if (table_field->count_distinct && + !table_field->count_distinct->exists()) + table_field->count_distinct= NULL; + } + + bitmap_set_all(table->read_set); + + /* Perform a full table scan to collect statistics on 'table's columns */ + if (!(rc= file->ha_rnd_init(FALSE))) + { + while ((rc= file->ha_rnd_next(table->record[0])) != HA_ERR_END_OF_FILE) + { + if (rc) + break; + + for (field_ptr= table->field; *field_ptr; field_ptr++) + { + table_field= *field_ptr; + if (table_field->is_null()) + table_field->nulls++; + else + { + table_field->column_total_length+= table_field->value_length(); + if (table_field->write_stat.min_value && + table_field->update_min(table_field->write_stat.min_value, + rows == table_field->nulls)) + set_not_null_for_write_column_stat_value(table_field, + COLUMN_STAT_MIN_VALUE); + if (table_field->write_stat.max_value && + table_field->update_max(table_field->write_stat.max_value, + rows == table_field->nulls)) + set_not_null_for_write_column_stat_value(table_field, + COLUMN_STAT_MAX_VALUE); + if (table_field->count_distinct) + table_field->count_distinct->add(); + } + } + rows++; + } + file->ha_rnd_end(); + } + rc= rc == HA_ERR_END_OF_FILE ? 0 : 1; + + /* + Calculate values for all statistical characteristics on columns and + and for each field f of 'table' save them in the write_stat structure + from the Field object for f. + */ + if (!rc) + { + table->write_stat.cardinality_is_null= FALSE; + table->write_stat.cardinality= rows; + + for (field_ptr= table->field; *field_ptr; field_ptr++) + { + table_field= *field_ptr; + table_field->write_stat.nulls_ratio= (double) table_field->nulls/rows; + table_field->write_stat.avg_length= + (double) table_field->column_total_length / (rows-table_field->nulls); + if (table_field->count_distinct) + { + table_field->write_stat.avg_frequency= + (double) (rows-table_field->nulls) / + table_field->count_distinct->get_value(); + set_not_null_for_write_column_stat_value(table_field, + COLUMN_STAT_AVG_FREQUENCY); + delete table_field->count_distinct; + table_field->count_distinct= NULL; + } + + set_not_null_for_write_column_stat_value(table_field, + COLUMN_STAT_NULLS_RATIO); + set_not_null_for_write_column_stat_value(table_field, + COLUMN_STAT_AVG_LENGTH); + } + } + + if (!rc) + { + uint keys= table->s->keys ; + + /* Collect statistics for indexes */ + for (uint i= 0; i < keys; i++) + { + if ((rc= collect_statistics_for_index(table, i))) + break; + } + } + + DBUG_RETURN(rc); +} + + +/* + @brief + Update statistics for a table in the persistent statistical tables + + @param + thd The thread handle + @param + table The table to collect statistics on + + @details + For each statistical table st the function looks for the rows from this + table that contain statistical data on 'table'. If rows with given + statistical characteritics exist they are updated with the new statistical + values taken from internal structures for 'table'. Otherwise new rows + with these statistical characteristics are added into st. + It is assumed that values stored in the statistical tables are found and + saved by the function collect_statistics_for_table. + + @retval + 0 If all statistical tables has been successfully updated + @retval + 1 Otherwise + + @note + The function is called when executing the ANALYZE actions for 'table'. + The function first unlocks the opened table the statistics on which has + been collected, but does not closes it, so all collected statistical data + remains in internal structures for 'table'. Then the function opens the + statistical tables and writes the statistical data for 'table'into them. + It is not allowed just to open statistical tables for writing when some + other tables are locked for reading. + After the statistical tables have been opened they are updated one by one + with the new statistics on 'table'. Objects of the helper classes + Table_stat, Column_stat and Index_stat are employed for this. + After having been updated the statistical system tables are closed. +*/ + +int update_statistics_for_table(THD *thd, TABLE *table) +{ + TABLE_LIST tables[STATISTICS_TABLES]; + Open_tables_state open_tables_state_backup; + uint i; + int err; + int rc= 0; + TABLE *stat_table; + uint keys= table->s->keys; + + DBUG_ENTER("update_statistics_for_table"); + + bzero((char *) &tables[0], sizeof(tables)); + + for (i= 0; i < STATISTICS_TABLES; i++) + { + tables[i].db= (char*) "mysql"; + tables[i].table_name= (char *) STAT_TABLE_NAME[i]; + tables[i].alias= tables[i].table_name; + tables[i].lock_type= TL_WRITE; + if (i < STATISTICS_TABLES - 1) + tables[i].next_global= tables[i].next_local= + tables[i].next_name_resolution_table= &tables[i+1]; + } + + if (unlock_tables_n_open_system_tables_for_write(thd, + tables, + &open_tables_state_backup)) + DBUG_RETURN(1); + + /* Update the statistical table table_stat */ + stat_table= tables[TABLE_STAT].table; + Table_stat table_stat(stat_table, table); + restore_record(stat_table, s->default_values); + table_stat.set_key_fields(); + err= table_stat.update_stat(); + if (err) + rc= 1; + + /* Update the statistical table colum_stat */ + stat_table= tables[COLUMN_STAT].table; + Column_stat column_stat(stat_table, table); + for (Field **field_ptr= table->field; *field_ptr; field_ptr++) + { + Field *table_field= *field_ptr; + restore_record(stat_table, s->default_values); + column_stat.set_key_fields(table_field); + err= column_stat.update_stat(); + if (err & !rc) + rc= 1; + } + + /* Update the statistical table index_stat */ + stat_table= tables[INDEX_STAT].table; + Index_stat index_stat(stat_table, table); + KEY *key_info, *key_info_end; + + for (key_info= table->key_info, key_info_end= table->key_info+keys; + key_info < key_info_end; key_info++) + { + uint key_parts= key_info->key_parts; + for (i= 0; i < key_parts; i++) + { + restore_record(stat_table, s->default_values); + index_stat.set_key_fields(key_info, i+1); + err= index_stat.update_stat(); + if (err & !rc) + rc= 1; + } + } + + close_system_tables(thd, &open_tables_state_backup); + + DBUG_RETURN(rc); +} + + +/* + @brief + Read statistics for a table from the persistent statistical tables + + @param + thd The thread handle + @param + table The table to read statistics on + + @details + For each statistical table the function looks for the rows from this + table that contain statistical data on 'table'. If such rows is found + the data from statistical columns of it is read into the appropriate + fields of internal structures for 'table'. Later at the query processing + this data are supposed to be used by the optimizer. + The function is called in function open_tables. + + @retval + 0 If data has been succesfully read from all statistical tables + @retval + 1 Otherwise + + @note + The function first calls the function open_system_tables_for_read to + be able to read info from the statistical tables. On success the data is + read from one table after another after which the statistical tables are + closed. Objects of the helper classes Table_stat, Column_stat and Index_stat + are employed to read statistical data from the statistical tables. + TODO. Consider a variant when statistical tables are opened and closed + only once for all tables, not for every table of the query as it's done + now. +*/ + +int read_statistics_for_table(THD *thd, struct st_table *table) +{ + uint i; + TABLE *stat_table; + Field *table_field; + Field **field_ptr; + KEY *key_info, *key_info_end; + TABLE_LIST tables[STATISTICS_TABLES]; + Open_tables_state open_tables_state_backup; + + DBUG_ENTER("read_statistics_for_table"); + + bzero((char *) &tables[0], sizeof(tables)); + + for (i= 0; i < STATISTICS_TABLES; i++) + { + tables[i].db= (char*) "mysql"; + tables[i].table_name= (char *) STAT_TABLE_NAME[i]; + tables[i].alias= tables[i].table_name; + tables[i].lock_type= TL_READ; + if (i < STATISTICS_TABLES - 1) + tables[i].next_global= tables[i].next_local= + tables[i].next_name_resolution_table= &tables[i+1]; + } + + if (open_system_tables_for_read(thd, tables, &open_tables_state_backup)) + DBUG_RETURN(1); + + create_min_max_stistical_fields(table, FALSE); + + /* Read statistics from the statistical table index_stat */ + stat_table= tables[TABLE_STAT].table; + Table_stat table_stat(stat_table, table); + table_stat.set_key_fields(); + table_stat.get_stat_values(); + + /* Read statistics from the statistical table column_stat */ + stat_table= tables[COLUMN_STAT].table; + Column_stat column_stat(stat_table, table); + for (field_ptr= table->field; *field_ptr; field_ptr++) + { + table_field= *field_ptr; + column_stat.set_key_fields(table_field); + column_stat.get_stat_values(); + } + + /* Read statistics from the statistical table index_stat */ + stat_table= tables[INDEX_STAT].table; + Index_stat index_stat(stat_table, table); + for (key_info= table->key_info, key_info_end= key_info+table->s->keys; + key_info < key_info_end; key_info++) + { + + for (i= 0; i < key_info->key_parts; i++) + { + index_stat.set_key_fields(key_info, i+1); + index_stat.get_stat_values(); + } + } + + close_system_tables(thd, &open_tables_state_backup); + + DBUG_RETURN(0); +} |