diff -urN orig/include/mysql_com.h src/include/mysql_com.h --- orig/include/mysql_com.h 2010-12-18 18:48:16.000000000 -0800 +++ src/include/mysql_com.h 2010-12-21 09:50:42.000000000 -0800 @@ -106,6 +106,8 @@ thread */ #define REFRESH_MASTER 128 /* Remove all bin logs in the index and truncate the index */ +#define REFRESH_TABLE_STATS 256 /* Refresh table stats hash table */ +#define REFRESH_INDEX_STATS 512 /* Refresh index stats hash table */ /* The following can't be set with mysql_refresh() */ #define REFRESH_READ_LOCK 16384 /* Lock tables for read */ diff -urN orig/sql/ha_innodb.cc src/sql/ha_innodb.cc --- orig/sql/ha_innodb.cc 2010-12-18 18:48:16.000000000 -0800 +++ src/sql/ha_innodb.cc 2010-12-21 09:50:48.000000000 -0800 @@ -3288,6 +3309,8 @@ error = row_insert_for_mysql((byte*) record, prebuilt); + if (error == DB_SUCCESS) rows_changed++; + if (error == DB_SUCCESS && auto_inc_used) { /* Fetch the value that was set in the autoincrement field */ @@ -3538,6 +3561,8 @@ innodb_srv_conc_enter_innodb(prebuilt->trx); error = row_update_for_mysql((byte*) old_row, prebuilt); + + if (error == DB_SUCCESS) rows_changed++; /* We need to do some special AUTOINC handling for the following case: @@ -3608,6 +3633,8 @@ error = row_update_for_mysql((byte*) record, prebuilt); + if (error == DB_SUCCESS) rows_changed++; + innodb_srv_conc_exit_innodb(prebuilt->trx); error = convert_error_code_to_mysql(error, user_thd); @@ -3887,6 +3914,9 @@ if (ret == DB_SUCCESS) { error = 0; table->status = 0; + rows_read++; + if (active_index >= 0 && active_index < MAX_KEY) + index_rows_read[active_index]++; } else if (ret == DB_RECORD_NOT_FOUND) { error = HA_ERR_KEY_NOT_FOUND; @@ -4040,6 +4070,9 @@ if (ret == DB_SUCCESS) { error = 0; table->status = 0; + rows_read++; + if (active_index >= 0 && active_index < MAX_KEY) + index_rows_read[active_index]++; } else if (ret == DB_RECORD_NOT_FOUND) { error = HA_ERR_END_OF_FILE; diff -urN orig/sql/ha_myisam.cc src/sql/ha_myisam.cc --- orig/sql/ha_myisam.cc 2010-12-18 18:48:16.000000000 -0800 +++ src/sql/ha_myisam.cc 2010-12-21 09:50:48.000000000 -0800 @@ -670,6 +670,9 @@ if ((error= update_auto_increment())) return error; } + + rows_changed++; + return mi_write(file,buf); } @@ -1518,13 +1521,17 @@ statistic_increment(table->in_use->status_var.ha_update_count,&LOCK_status); if (table->timestamp_field_type & TIMESTAMP_AUTO_SET_ON_UPDATE) table->timestamp_field->set_time(); - return mi_update(file,old_data,new_data); + int error=mi_update(file,old_data,new_data); + if (!error) rows_changed++; + return error; } int ha_myisam::delete_row(const byte * buf) { statistic_increment(table->in_use->status_var.ha_delete_count,&LOCK_status); - return mi_delete(file,buf); + int error=mi_delete(file,buf); + if (!error) rows_changed++; + return error; } int ha_myisam::index_read(byte * buf, const byte * key, @@ -1535,6 +1542,13 @@ &LOCK_status); int error=mi_rkey(file,buf,active_index, key, key_len, find_flag); table->status=error ? STATUS_NOT_FOUND: 0; + if (!error) { + rows_read++; + + int inx = (active_index == -1) ? file->lastinx : active_index; + if (inx >= 0 && inx < MAX_KEY) + index_rows_read[inx]++; + } return error; } @@ -1545,6 +1559,13 @@ &LOCK_status); int error=mi_rkey(file,buf,index, key, key_len, find_flag); table->status=error ? STATUS_NOT_FOUND: 0; + if (!error) { + rows_read++; + + int inx = (active_index == -1) ? file->lastinx : active_index; + if (inx >= 0 && inx < MAX_KEY) + index_rows_read[inx]++; + } return error; } @@ -1555,6 +1576,13 @@ &LOCK_status); int error=mi_rkey(file,buf,active_index, key, key_len, HA_READ_PREFIX_LAST); table->status=error ? STATUS_NOT_FOUND: 0; + if (!error) { + rows_read++; + + int inx = (active_index == -1) ? file->lastinx : active_index; + if (inx >= 0 && inx < MAX_KEY) + index_rows_read[inx]++; + } return error; } @@ -1565,6 +1593,13 @@ &LOCK_status); int error=mi_rnext(file,buf,active_index); table->status=error ? STATUS_NOT_FOUND: 0; + if (!error) { + rows_read++; + + int inx = (active_index == -1) ? file->lastinx : active_index; + if (inx >= 0 && inx < MAX_KEY) + index_rows_read[inx]++; + } return error; } @@ -1575,6 +1610,13 @@ &LOCK_status); int error=mi_rprev(file,buf, active_index); table->status=error ? STATUS_NOT_FOUND: 0; + if (!error) { + rows_read++; + + int inx = (active_index == -1) ? file->lastinx : active_index; + if (inx >= 0 && inx < MAX_KEY) + index_rows_read[inx]++; + } return error; } @@ -1585,6 +1627,13 @@ &LOCK_status); int error=mi_rfirst(file, buf, active_index); table->status=error ? STATUS_NOT_FOUND: 0; + if (!error) { + rows_read++; + + int inx = (active_index == -1) ? file->lastinx : active_index; + if (inx >= 0 && inx < MAX_KEY) + index_rows_read[inx]++; + } return error; } @@ -1595,6 +1644,13 @@ &LOCK_status); int error=mi_rlast(file, buf, active_index); table->status=error ? STATUS_NOT_FOUND: 0; + if (!error) { + rows_read++; + + int inx = (active_index == -1) ? file->lastinx : active_index; + if (inx >= 0 && inx < MAX_KEY) + index_rows_read[inx]++; + } return error; } @@ -1611,6 +1667,13 @@ error= mi_rnext_same(file,buf); } while (error == HA_ERR_RECORD_DELETED); table->status=error ? STATUS_NOT_FOUND: 0; + if (!error) { + rows_read++; + + int inx = (active_index == -1) ? file->lastinx : active_index; + if (inx >= 0 && inx < MAX_KEY) + index_rows_read[inx]++; + } return error; } @@ -1628,6 +1691,7 @@ &LOCK_status); int error=mi_scan(file, buf); table->status=error ? STATUS_NOT_FOUND: 0; + if (!error) rows_read++; return error; } @@ -1642,6 +1706,7 @@ &LOCK_status); int error=mi_rrnd(file, buf, my_get_ptr(pos,ref_length)); table->status=error ? STATUS_NOT_FOUND: 0; + if (!error) rows_read++; return error; } diff -urN orig/sql/handler.cc src/sql/handler.cc --- orig/sql/handler.cc 2010-12-18 18:48:16.000000000 -0800 +++ src/sql/handler.cc 2010-12-21 09:50:48.000000000 -0800 @@ -1442,6 +1442,7 @@ else dupp_ref=ref+ALIGN_SIZE(ref_length); } + rows_read = rows_changed = 0; DBUG_RETURN(error); } @@ -2227,6 +2228,117 @@ return error; } +void handler::update_table_stats(THD *thd) +{ + if (!rows_read && !rows_changed) + return; // Nothing to update. + + pthread_mutex_lock(&LOCK_global_table_stats); + update_table_stats_hash(&global_table_stats); + pthread_mutex_unlock(&LOCK_global_table_stats); + + update_table_stats_hash(&(thd->table_stats)); + update_table_stats_hash(&(thd->single_query_table_stats)); + + rows_read = rows_changed = 0; +} + +// Updates the global table stats with the TABLE this handler represents. +void handler::update_table_stats_hash(HASH *table_stats) { + // table_cache_key is db_name + '\0' + table_name + '\0'. + if (!table->s || !table->s->table_cache_key || !table->s->table_name) return; + + TABLE_STATS* table_stats_res; + char key[NAME_LEN * 2 + 2]; + // [db] + '.' + [table] + sprintf(key, "%s.%s", table->s->table_cache_key, table->s->table_name); + + // Gets the global table stats, creating one if necessary. + if (!(table_stats_res = (TABLE_STATS*)hash_search(table_stats, + (byte*)key, + strlen(key)))) { + if (!(table_stats_res = ((TABLE_STATS*) + my_malloc(sizeof(TABLE_STATS), MYF(MY_WME))))) { + // Out of memory. + sql_print_error("Allocating table stats failed."); + return; + } + strncpy(table_stats_res->table, key, sizeof(table_stats_res->table)); + table_stats_res->rows_read = 0; + table_stats_res->rows_changed = 0; + table_stats_res->rows_changed_x_indexes = 0; + + if (my_hash_insert(table_stats, (byte*)table_stats_res)) { + // Out of memory. + sql_print_error("Inserting table stats failed."); + my_free((char*)table_stats_res, 0); + return; + } + } + // Updates the global table stats. + table_stats_res->rows_read += rows_read; + table_stats_res->rows_changed += rows_changed; + table_stats_res->rows_changed_x_indexes += + rows_changed * (table->s->keys ? table->s->keys : 1); +} + +void handler::update_index_stats(THD *thd) +{ + if (!table->s || !table->s->table_cache_key || !table->s->table_name) return; + + pthread_mutex_lock(&LOCK_global_index_stats); + update_index_stats_hash(&global_index_stats); + pthread_mutex_unlock(&LOCK_global_index_stats); + + update_index_stats_hash(&(thd->index_stats)); + update_index_stats_hash(&(thd->single_query_index_stats)); + + for (int x = 0; x < table->s->keys; x++) + index_rows_read[x] = 0; +} + +// Updates the global index stats with this handler's accumulated index reads. +void handler::update_index_stats_hash(HASH *index_stats) { + // table_cache_key is db_name + '\0' + table_name + '\0'. + + for (int x = 0; x < table->s->keys; x++) { + if (index_rows_read[x]) { + // Rows were read using this index. + KEY* key_info = &table->key_info[x]; + + if (!key_info->name) continue; + + INDEX_STATS* index_stats_res; + char key[NAME_LEN * 3 + 3]; + // [db] + '.' + [table] + '.' + [index] + sprintf(key, "%s.%s.%s", table->s->table_cache_key, + table->s->table_name, key_info->name); + + // Gets the global index stats, creating one if necessary. + if (!(index_stats_res = (INDEX_STATS*)hash_search(index_stats, + (byte*)key, + strlen(key)))) { + if (!(index_stats_res = ((INDEX_STATS*) + my_malloc(sizeof(INDEX_STATS), MYF(MY_WME))))) { + // Out of memory. + sql_print_error("Allocating index stats failed."); + return; + } + strncpy(index_stats_res->index, key, sizeof(index_stats_res->index)); + index_stats_res->rows_read = 0; + + if (my_hash_insert(index_stats, (byte*)index_stats_res)) { + // Out of memory. + sql_print_error("Inserting index stats failed."); + my_free((char*)index_stats_res, 0); + return; + } + } + // Updates the global index stats. + index_stats_res->rows_read += index_rows_read[x]; + } + } +} /**************************************************************************** ** Some general functions that isn't in the handler class diff -urN orig/sql/handler.h src/sql/handler.h --- orig/sql/handler.h 2010-12-18 18:48:16.000000000 -0800 +++ src/sql/handler.h 2010-12-21 09:50:48.000000000 -0800 @@ -604,6 +604,9 @@ bool auto_increment_column_changed; bool implicit_emptied; /* Can be !=0 only if HEAP */ const COND *pushed_cond; + ulonglong rows_read; + ulonglong rows_changed; + ulonglong index_rows_read[MAX_KEY]; handler(const handlerton *ht_arg, TABLE *table_arg) :table(table_arg), ht(ht_arg), @@ -615,8 +618,10 @@ ref_length(sizeof(my_off_t)), block_size(0), raid_type(0), ft_handler(0), inited(NONE), locked(FALSE), implicit_emptied(0), - pushed_cond(NULL) - {} + pushed_cond(NULL), rows_read(0), rows_changed(0) + { + memset(index_rows_read, 0, sizeof(index_rows_read)); + } virtual ~handler(void) { DBUG_ASSERT(locked == FALSE); /* TODO: DBUG_ASSERT(inited == NONE); */ } virtual handler *clone(MEM_ROOT *mem_root); int ha_open(const char *name, int mode, int test_if_locked); @@ -625,7 +630,11 @@ virtual void print_error(int error, myf errflag); virtual bool get_error_message(int error, String *buf); uint get_dup_key(int error); - void change_table_ptr(TABLE *table_arg) { table=table_arg; } + void change_table_ptr(TABLE *table_arg) { + table=table_arg; + rows_read = rows_changed = 0; + memset(index_rows_read, 0, sizeof(index_rows_read)); + } virtual double scan_time() { return ulonglong2double(data_file_length) / IO_SIZE + 2; } virtual double read_time(uint index, uint ranges, ha_rows rows) @@ -885,6 +894,10 @@ virtual bool is_crashed() const { return 0; } virtual bool auto_repair() const { return 0; } + void update_table_stats(THD *thd); + void update_table_stats_hash(HASH *table_stats); + void update_index_stats(THD *thd); + void update_index_stats_hash(HASH *index_stats); /* default rename_table() and delete_table() rename/delete files with a given name and extensions from bas_ext() diff -urN orig/sql/lex.h src/sql/lex.h --- orig/sql/lex.h 2010-12-18 18:48:16.000000000 -0800 +++ src/sql/lex.h 2010-12-21 09:50:49.000000000 -0800 @@ -234,6 +234,7 @@ { "IN", SYM(IN_SYM)}, { "INDEX", SYM(INDEX_SYM)}, { "INDEXES", SYM(INDEXES)}, + { "INDEX_STATISTICS", SYM(INDEX_STATS_SYM)}, { "INFILE", SYM(INFILE)}, { "INNER", SYM(INNER_SYM)}, { "INNOBASE", SYM(INNOBASE_SYM)}, @@ -474,6 +475,7 @@ { "TABLE", SYM(TABLE_SYM)}, { "TABLES", SYM(TABLES)}, { "TABLESPACE", SYM(TABLESPACE)}, + { "TABLE_STATISTICS", SYM(TABLE_STATS_SYM)}, { "TEMPORARY", SYM(TEMPORARY)}, { "TEMPTABLE", SYM(TEMPTABLE_SYM)}, { "TERMINATED", SYM(TERMINATED)}, diff -urN orig/sql/log.cc src/sql/log.cc --- orig/sql/log.cc 2010-12-18 18:48:16.000000000 -0800 +++ src/sql/log.cc 2010-12-21 09:50:49.000000000 -0800 @@ -2233,6 +2233,10 @@ { bool error=0; time_t current_time; + HASH *table_stats; + HASH *index_stats; + int i = 0; + if (!is_open()) return 0; DBUG_ENTER("MYSQL_LOG::write"); @@ -2252,24 +2256,23 @@ { Security_context *sctx= thd->security_ctx; current_time=time(NULL); - if (current_time != last_time) - { - last_time=current_time; - struct tm tm_tmp; - struct tm *start; - localtime_r(¤t_time,&tm_tmp); - start=&tm_tmp; - /* Note that my_b_write() assumes it knows the length for this */ - sprintf(buff,"# Time: %02d%02d%02d %2d:%02d:%02d\n", - start->tm_year % 100, - start->tm_mon+1, - start->tm_mday, - start->tm_hour, - start->tm_min, - start->tm_sec); - if (my_b_write(&log_file, (byte*) buff,24)) - tmp_errno=errno; - } + last_time=current_time; + struct tm tm_tmp; + struct tm *start; + localtime_r(¤t_time,&tm_tmp); + start=&tm_tmp; + /* Note that my_b_write() assumes it knows the length for this */ + sprintf(buff,"# Time: %02d%02d%02d %2d:%02d:%02d\n", + start->tm_year % 100, + start->tm_mon+1, + start->tm_mday, + start->tm_hour, + start->tm_min, + start->tm_sec); + + if (my_b_write(&log_file, (byte*) buff,24)) + tmp_errno=errno; + if (my_b_printf(&log_file, "# User@Host: %s[%s] @ %s [%s]\n", sctx->priv_user ? sctx->priv_user : "", @@ -2290,6 +2293,32 @@ (ulong) thd->examined_row_count) == (uint) -1) tmp_errno=errno; } + + table_stats = &(thd->single_query_table_stats); + index_stats = &(thd->single_query_index_stats); + + my_b_printf(&log_file, "# Row_Stats: "); + for (i = 0; i < table_stats->records; ++i) { + TABLE_STATS *table_stats_res = (TABLE_STATS*)hash_element(table_stats, i); + my_b_printf(&log_file, "%s:rows_read=%lu,rows_changed=%lu,rows_changed_x_indexes=%lu;", + table_stats_res->table, + table_stats_res->rows_read, + table_stats_res->rows_changed, + table_stats_res->rows_changed_x_indexes + ); + } + my_b_printf(&log_file, "\n"); + + my_b_printf(&log_file, "# Index_Stats: "); + for (i = 0; i < index_stats->records; ++i) { + INDEX_STATS *index_stats_res = (INDEX_STATS*)hash_element(index_stats, i); + my_b_printf(&log_file, "%s:rows_read=%lu;", + index_stats_res->index, + index_stats_res->rows_read + ); + } + my_b_printf(&log_file, "\n"); + if (thd->db && strcmp(thd->db,db)) { // Database changed if (my_b_printf(&log_file,"use %s;\n",thd->db) == (uint) -1) diff -urN orig/sql/mysqld.cc src/sql/mysqld.cc --- orig/sql/mysqld.cc 2010-12-18 18:48:16.000000000 -0800 +++ src/sql/mysqld.cc 2010-12-21 09:50:49.000000000 -0800 @@ -548,6 +548,10 @@ LOCK_crypt, LOCK_bytes_sent, LOCK_bytes_received, LOCK_global_system_variables, LOCK_user_conn, LOCK_slave_list, LOCK_active_mi; + + +pthread_mutex_t LOCK_global_table_stats; +pthread_mutex_t LOCK_global_index_stats; /* The below lock protects access to two global server variables: max_prepared_stmt_count and prepared_stmt_count. These variables @@ -1189,6 +1193,9 @@ x_free(opt_secure_file_priv); bitmap_free(&temp_pool); free_max_user_conn(); + + free_global_table_stats(); + free_global_index_stats(); #ifdef HAVE_REPLICATION end_slave_list(); free_list(&replicate_do_db); @@ -1303,6 +1310,11 @@ (void) pthread_cond_destroy(&COND_thread_cache); (void) pthread_cond_destroy(&COND_flush_thread_cache); (void) pthread_cond_destroy(&COND_manager); + + + + (void) pthread_mutex_destroy(&LOCK_global_table_stats); + (void) pthread_mutex_destroy(&LOCK_global_index_stats); } #endif /*EMBEDDED_LIBRARY*/ @@ -2212,7 +2224,7 @@ { // Flush everything bool not_used; - reload_acl_and_cache((THD*) 0,REFRESH_LOG, (TABLE_LIST*) 0, ¬_used); + reload_acl_and_cache((THD*) 0,REFRESH_LOG, 0, (TABLE_LIST*) 0, ¬_used); signal(signo, SIG_ACK); } @@ -2614,6 +2626,7 @@ (REFRESH_LOG | REFRESH_TABLES | REFRESH_FAST | REFRESH_GRANT | REFRESH_THREADS | REFRESH_HOSTS), + 0, (TABLE_LIST*) 0, ¬_used); // Flush logs } break; @@ -3148,6 +3161,11 @@ (void) pthread_mutex_init(&LOCK_rpl_status, MY_MUTEX_INIT_FAST); (void) pthread_cond_init(&COND_rpl_status, NULL); #endif + + + + (void) pthread_mutex_init(&LOCK_global_table_stats, MY_MUTEX_INIT_FAST); + (void) pthread_mutex_init(&LOCK_global_index_stats, MY_MUTEX_INIT_FAST); sp_cache_init(); /* Parameter for threads created for connections */ (void) pthread_attr_init(&connection_attrib); @@ -3419,6 +3437,10 @@ sql_print_error("Out of memory"); unireg_abort(1); } + + init_table_stats(&global_table_stats); + init_index_stats(&global_index_stats); + if (ha_init()) { sql_print_error("Can't init databases"); diff -urN orig/sql/mysql_priv.h src/sql/mysql_priv.h --- orig/sql/mysql_priv.h 2010-12-18 18:48:16.000000000 -0800 +++ src/sql/mysql_priv.h 2010-12-21 09:50:49.000000000 -0800 @@ -727,7 +727,13 @@ bool multi_delete_set_locks_and_link_aux_tables(LEX *lex); void init_max_user_conn(void); void init_update_queries(void); + +void init_table_stats(HASH *table_stats); +void init_index_stats(HASH *index_stats); void free_max_user_conn(void); + +void free_global_table_stats(void); +void free_global_index_stats(void); pthread_handler_t handle_one_connection(void *arg); pthread_handler_t handle_bootstrap(void *arg); void end_thread(THD *thd,bool put_in_cache); @@ -744,7 +750,7 @@ uint cached_tables(void); void kill_mysql(void); void close_connection(THD *thd, uint errcode, bool lock); -bool reload_acl_and_cache(THD *thd, ulong options, TABLE_LIST *tables, +bool reload_acl_and_cache(THD *thd, ulong options, ulong option_type, TABLE_LIST *tables, bool *write_to_binlog); bool check_access(THD *thd, ulong access, const char *db, ulong *save_priv, bool no_grant, bool no_errors, bool schema_db); @@ -948,6 +954,9 @@ void mysqld_list_processes(THD *thd,const char *user,bool verbose); int mysqld_show_status(THD *thd); int mysqld_show_variables(THD *thd,const char *wild); + +int mysqld_show_table_stats(THD *thd, const char *wild); +int mysqld_show_index_stats(THD *thd, const char *wild); bool mysqld_show_storage_engines(THD *thd); bool mysqld_show_privileges(THD *thd); bool mysqld_show_column_types(THD *thd); @@ -1133,7 +1142,7 @@ bool check_alias); TABLE **find_temporary_table(THD *thd, const char *db, const char *table_name); bool close_temporary_table(THD *thd, const char *db, const char *table_name); -void close_temporary(TABLE *table, bool delete_table); +void close_temporary(THD *thd, TABLE *table, bool delete_table); bool rename_temporary_table(THD* thd, TABLE *table, const char *new_db, const char *table_name); void remove_db_from_cache(const char *db); @@ -1378,6 +1387,14 @@ extern struct system_status_var global_status_var; extern struct rand_struct sql_rand; + + + +extern HASH global_table_stats; +extern pthread_mutex_t LOCK_global_table_stats; +extern HASH global_index_stats; +extern pthread_mutex_t LOCK_global_index_stats; + extern const char *opt_date_time_formats[]; extern KNOWN_DATE_TIME_FORMAT known_date_time_formats[]; diff -urN orig/sql/slave.cc src/sql/slave.cc --- orig/sql/slave.cc 2010-12-18 18:48:16.000000000 -0800 +++ src/sql/slave.cc 2010-12-21 09:50:49.000000000 -0800 @@ -534,7 +534,7 @@ Don't ask for disk deletion. For now, anyway they will be deleted when slave restarts, but it is a better intention to not delete them. */ - close_temporary(table, 0); + close_temporary(NULL, table, 0); } save_temporary_tables= 0; slave_open_temp_tables= 0; diff -urN orig/sql/sql_base.cc src/sql/sql_base.cc --- orig/sql/sql_base.cc 2010-12-18 18:48:16.000000000 -0800 +++ src/sql/sql_base.cc 2010-12-21 09:50:49.000000000 -0800 @@ -625,6 +625,13 @@ DBUG_ASSERT(table->key_read == 0); DBUG_ASSERT(!table->file || table->file->inited == handler::NONE); + if (table && table->file) + { + table->file->update_table_stats(thd); + table->file->update_index_stats(thd); + } + + *table_ptr=table->next; if (table->needs_reopen_or_name_lock() || thd->version != refresh_version || !table->db_stat) @@ -666,10 +673,13 @@ /* Close and delete temporary tables */ -void close_temporary(TABLE *table,bool delete_table) +void close_temporary(THD *thd, TABLE *table,bool delete_table) { DBUG_ENTER("close_temporary"); char path[FN_REFLEN]; + + table->file->update_table_stats(thd); + table->file->update_index_stats(thd); db_type table_type=table->s->db_type; strmov(path,table->s->path); free_io_cache(table); @@ -700,7 +710,7 @@ for (table= thd->temporary_tables; table; table= next) { next= table->next; - close_temporary(table, 1); + close_temporary(thd, table, 1); } thd->temporary_tables= 0; return; @@ -786,7 +796,7 @@ strlen(table->s->table_name)); s_query.q_append(','); next= table->next; - close_temporary(table, 1); + close_temporary(thd, table, 1); } thd->clear_error(); CHARSET_INFO *cs_save= thd->variables.character_set_client; @@ -810,7 +820,7 @@ else { next= table->next; - close_temporary(table, 1); + close_temporary(thd, table, 1); } } if (!was_quote_show) @@ -1066,7 +1076,7 @@ unlock the table and remove the table from this list. */ mysql_lock_remove(thd, thd->locked_tables, table, FALSE); - close_temporary(table, 1); + close_temporary(thd, table, 1); if (thd->slave_thread) --slave_open_temp_tables; return 0; @@ -3388,7 +3398,7 @@ if (file && file->delete_table(path)) { error=1; - sql_print_warning("Could not remove tmp table: '%s', error: %d", + sql_print_error("Could not remove tmp table: '%s', error: %d", path, my_errno); } delete file; diff -urN orig/sql/sql_class.cc src/sql/sql_class.cc --- orig/sql/sql_class.cc 2010-12-18 18:48:16.000000000 -0800 +++ src/sql/sql_class.cc 2010-12-21 09:50:49.000000000 -0800 @@ -182,6 +182,11 @@ { ulong tmp; + init_table_stats(&table_stats); + init_index_stats(&index_stats); + init_table_stats(&single_query_table_stats); + init_index_stats(&single_query_index_stats); + /* Pass nominal parameters to init_alloc_root only to ensure that the destructor works OK in case of an error. The main_mem_root @@ -283,6 +288,7 @@ thr_lock_owner_init(&main_lock_id, &lock_info); m_internal_handler= NULL; + /* init the hash tables for storing single query row stats */ } @@ -375,6 +381,7 @@ #endif transaction.xid_state.xid.null(); transaction.xid_state.in_thd=1; + } @@ -400,6 +407,12 @@ (hash_free_key) free_user_var, 0); sp_cache_clear(&sp_proc_cache); sp_cache_clear(&sp_func_cache); + + hash_free(&table_stats); + init_table_stats(&table_stats); + + hash_free(&index_stats); + init_index_stats(&index_stats); } @@ -459,6 +472,10 @@ pthread_mutex_lock(&LOCK_delete); pthread_mutex_unlock(&LOCK_delete); add_to_status(&global_status_var, &status_var); + hash_free(&table_stats); + hash_free(&index_stats); + hash_free(&single_query_table_stats); + hash_free(&single_query_index_stats); /* Close connection */ #ifndef EMBEDDED_LIBRARY diff -urN orig/sql/sql_class.h src/sql/sql_class.h --- orig/sql/sql_class.h 2010-12-18 18:48:16.000000000 -0800 +++ src/sql/sql_class.h 2010-12-21 09:50:49.000000000 -0800 @@ -1216,6 +1216,14 @@ struct rand_struct rand; // used for authentication struct system_variables variables; // Changeable local variables struct system_status_var status_var; // Per thread statistic vars + //the first two are kept for the duration of the connection + HASH table_stats; + HASH index_stats; + + //these two are kept only for single queries and are used for the slow query log + HASH single_query_table_stats; + HASH single_query_index_stats; + THR_LOCK_INFO lock_info; // Locking info of this thread THR_LOCK_OWNER main_lock_id; // To use for conventional queries THR_LOCK_OWNER *lock_id; // If not main_lock_id, points to diff -urN orig/sql/sql_delete.cc src/sql/sql_delete.cc --- orig/sql/sql_delete.cc 2010-12-18 18:48:16.000000000 -0800 +++ src/sql/sql_delete.cc 2010-12-21 09:50:49.000000000 -0800 @@ -909,7 +909,7 @@ goto trunc_by_del; strmov(path, table->s->path); *table_ptr= table->next; // Unlink table from list - close_temporary(table,0); + close_temporary(thd, table,0); if (thd->slave_thread) --slave_open_temp_tables; *fn_ext(path)=0; // Remove the .frm extension diff -urN orig/sql/sql_lex.h src/sql/sql_lex.h --- orig/sql/sql_lex.h 2010-12-18 18:48:16.000000000 -0800 +++ src/sql/sql_lex.h 2010-12-21 09:50:49.000000000 -0800 @@ -61,6 +61,7 @@ SQLCOM_SHOW_GRANTS, SQLCOM_SHOW_CREATE, SQLCOM_SHOW_CHARSETS, SQLCOM_SHOW_COLLATIONS, SQLCOM_SHOW_CREATE_DB, SQLCOM_SHOW_TABLE_STATUS, SQLCOM_SHOW_TRIGGERS, + SQLCOM_SHOW_TABLE_STATS, SQLCOM_SHOW_INDEX_STATS, SQLCOM_LOAD,SQLCOM_SET_OPTION,SQLCOM_LOCK_TABLES,SQLCOM_UNLOCK_TABLES, SQLCOM_GRANT, diff -urN orig/sql/sql_parse.cc src/sql/sql_parse.cc --- orig/sql/sql_parse.cc 2010-12-18 18:48:16.000000000 -0800 +++ src/sql/sql_parse.cc 2010-12-21 09:50:49.000000000 -0800 @@ -124,6 +124,11 @@ return (xid_state->xa_state == XA_ROLLBACK_ONLY); } +HASH global_table_stats; +extern pthread_mutex_t LOCK_global_table_stats; + +HASH global_index_stats; +extern pthread_mutex_t LOCK_global_index_stats; /** Rollback work done on behalf of at ransaction branch. */ @@ -543,6 +548,71 @@ } + + + + + + + + + + + + + + + + + + + + + +extern "C" byte *get_key_table_stats(TABLE_STATS *table_stats, uint *length, + my_bool not_used __attribute__((unused))) +{ + *length = strlen(table_stats->table); + return (byte*)table_stats->table; +} + +extern "C" void free_table_stats(TABLE_STATS* table_stats) +{ + my_free((char*)table_stats, MYF(0)); +} + +void init_table_stats(HASH *table_stats) +{ + if (hash_init(table_stats, system_charset_info, max_connections, + 0, 0, (hash_get_key)get_key_table_stats, + (hash_free_key)free_table_stats, 0)) { + sql_print_error("Initializing global_table_stats failed."); + exit(1); + } +} + +extern "C" byte *get_key_index_stats(INDEX_STATS *index_stats, uint *length, + my_bool not_used __attribute__((unused))) +{ + *length = strlen(index_stats->index); + return (byte*)index_stats->index; +} + +extern "C" void free_index_stats(INDEX_STATS* index_stats) +{ + my_free((char*)index_stats, MYF(0)); +} + +void init_index_stats(HASH *index_stats) +{ + if (hash_init(index_stats, system_charset_info, max_connections, + 0, 0, (hash_get_key)get_key_index_stats, + (hash_free_key)free_index_stats, 0)) { + sql_print_error("Initializing global_index_stats failed."); + exit(1); + } +} + /* check if user has already too many connections @@ -647,6 +717,20 @@ + + + +void free_global_table_stats(void) +{ + hash_free(&global_table_stats); +} + +void free_global_index_stats(void) +{ + hash_free(&global_index_stats); +} + + /* Mark all commands that somehow changes a table This is used to check number of updates / hour @@ -2133,7 +2217,7 @@ if (check_global_access(thd,RELOAD_ACL)) break; mysql_log.write(thd,command,NullS); - if (!reload_acl_and_cache(thd, options, (TABLE_LIST*) 0, ¬_used)) + if (!reload_acl_and_cache(thd, options, 0, (TABLE_LIST*) 0, ¬_used)) send_ok(thd); break; } @@ -2636,6 +2720,11 @@ lex->time_zone_tables_used) mysql_reset_errors(thd, 0); + if (lex->sql_command != SQLCOM_SHOW_TABLE_STATS && lex->sql_command != SQLCOM_SHOW_INDEX_STATS) { + my_hash_reset(&(thd->single_query_table_stats)); + my_hash_reset(&(thd->single_query_index_stats)); + } + #ifdef HAVE_REPLICATION if (unlikely(thd->slave_thread)) { @@ -2903,6 +2992,16 @@ } #endif + case SQLCOM_SHOW_TABLE_STATS: + { + res= mysqld_show_table_stats(thd, (lex->wild ? lex->wild->ptr() : NullS)); + break; + } + case SQLCOM_SHOW_INDEX_STATS: + { + res= mysqld_show_index_stats(thd, (lex->wild ? lex->wild->ptr() : NullS)); + break; + } case SQLCOM_BACKUP_TABLE: { DBUG_ASSERT(first_table == all_tables && first_table != 0); @@ -4336,7 +4435,7 @@ reload_acl_and_cache() will tell us if we are allowed to write to the binlog or not. */ - if (!reload_acl_and_cache(thd, lex->type, first_table, &write_to_binlog)) + if (!reload_acl_and_cache(thd, lex->type, lex->option_type, first_table, &write_to_binlog)) { /* We WANT to write and we CAN write. @@ -7127,7 +7226,7 @@ !=0 error. thd->killed or thd->net.report_error is set */ -bool reload_acl_and_cache(THD *thd, ulong options, TABLE_LIST *tables, +bool reload_acl_and_cache(THD *thd, ulong options, ulong option_type, TABLE_LIST *tables, bool *write_to_binlog) { bool result=0; @@ -7306,6 +7405,44 @@ #endif if (options & REFRESH_USER_RESOURCES) reset_mqh((LEX_USER *) NULL); + if (options & REFRESH_TABLE_STATS) + { + if (option_type == OPT_GLOBAL) + { + pthread_mutex_lock(&LOCK_global_table_stats); + my_hash_reset(&global_table_stats); + pthread_mutex_unlock(&LOCK_global_table_stats); + } + + if (option_type == OPT_SESSION) + { + my_hash_reset(&(thd->table_stats)); + } + + if (option_type == OPT_DEFAULT) + { + my_hash_reset(&(thd->single_query_table_stats)); + } + } + if (options & REFRESH_INDEX_STATS) + { + if (option_type == OPT_GLOBAL) + { + pthread_mutex_lock(&LOCK_global_index_stats); + my_hash_reset(&global_index_stats); + pthread_mutex_unlock(&LOCK_global_index_stats); + } + + if (option_type == OPT_SESSION) + { + my_hash_reset(&(thd->index_stats)); + } + + if (option_type == OPT_DEFAULT) + { + my_hash_reset(&(thd->single_query_index_stats)); + } + } *write_to_binlog= tmp_write_to_binlog; return result; } diff -urN orig/sql/sql_show.cc src/sql/sql_show.cc --- orig/sql/sql_show.cc 2010-12-18 18:48:16.000000000 -0800 +++ src/sql/sql_show.cc 2010-12-21 14:35:26.000000000 -0800 @@ -1805,6 +1805,165 @@ } + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +// Sends the global table stats back to the client. +int mysqld_show_table_stats(THD *thd, const char *wild) { + Protocol *protocol= thd->protocol; + List field_list; + LEX *lex= thd->lex; + HASH *table_stats; + + DBUG_ENTER("mysqld_show_table_stats"); + field_list.push_back(new Item_empty_string("Table", NAME_LEN * 2 + 2)); + field_list.push_back(new Item_int("Rows_read", 0)); + field_list.push_back(new Item_int("Rows_changed", 0)); + field_list.push_back(new Item_int("Rows_changed_x_#indexes", 0)); + if (protocol->send_fields(&field_list, + Protocol::SEND_NUM_ROWS | Protocol::SEND_EOF)) + DBUG_RETURN(TRUE); /* purecov: inspected */ + + if (lex->option_type == OPT_GLOBAL) + { + // Iterates through all the global table stats and sends them to the client. + // Pattern matching on the table name is supported. + pthread_mutex_lock(&LOCK_global_table_stats); + table_stats = &global_table_stats; + } else if (lex->option_type == OPT_SESSION) { + table_stats = &(thd->table_stats); + } else { + table_stats = &(thd->single_query_table_stats); + } + + for (uint i = 0; i < table_stats->records; ++i) { + TABLE_STATS *table_stats_res = + (TABLE_STATS*)hash_element(table_stats, i); + if (!(wild && wild[0] && + wild_case_compare(system_charset_info, table_stats_res->table, wild))) { + protocol->prepare_for_resend(); + protocol->store(table_stats_res->table, system_charset_info); + protocol->store((longlong)table_stats_res->rows_read); + protocol->store((longlong)table_stats_res->rows_changed); + protocol->store((longlong)table_stats_res->rows_changed_x_indexes); + if (protocol->write()) + goto err; /* purecov: inspected */ + } + } + if (lex->option_type == OPT_GLOBAL) + pthread_mutex_unlock(&LOCK_global_table_stats); + + send_eof(thd); + DBUG_RETURN(FALSE); + + err: + if (lex->option_type == OPT_GLOBAL) + pthread_mutex_unlock(&LOCK_global_table_stats); + + DBUG_RETURN(TRUE); +} + +// Sends the global index stats back to the client. +int mysqld_show_index_stats(THD *thd, const char *wild) { + Protocol *protocol= thd->protocol; + List field_list; + LEX *lex= thd->lex; + HASH *index_stats; + + DBUG_ENTER("mysqld_show_index_stats"); + field_list.push_back(new Item_empty_string("Index", NAME_LEN * 3 + 3)); + field_list.push_back(new Item_int("Rows_read", 0)); + if (protocol->send_fields(&field_list, + Protocol::SEND_NUM_ROWS | Protocol::SEND_EOF)) + DBUG_RETURN(TRUE); /* purecov: inspected */ + + if (lex->option_type == OPT_GLOBAL) + { + // Iterates through all the global index stats and sends them to the client. + // Pattern matching on the index name is supported. + pthread_mutex_lock(&LOCK_global_index_stats); + index_stats = &global_index_stats; + } else if (lex->option_type == OPT_SESSION) { + index_stats = &(thd->index_stats); + } else { + index_stats = &(thd->single_query_index_stats); + } + + for (uint i = 0; i < index_stats->records; ++i) { + INDEX_STATS *index_stats_res = + (INDEX_STATS*)hash_element(index_stats, i); + if (!(wild && wild[0] && + wild_case_compare(system_charset_info, index_stats_res->index, wild))) { + protocol->prepare_for_resend(); + protocol->store(index_stats_res->index, system_charset_info); + protocol->store((longlong)index_stats_res->rows_read); + if (protocol->write()) + goto err; /* purecov: inspected */ + } + } + + if (lex->option_type == OPT_GLOBAL) + pthread_mutex_unlock(&LOCK_global_index_stats); + + send_eof(thd); + DBUG_RETURN(FALSE); + + err: + pthread_mutex_unlock(&LOCK_global_index_stats); + DBUG_RETURN(TRUE); +} + /* collect status for all running threads */ void calc_sum_of_all_status(STATUS_VAR *to) diff -urN orig/sql/sql_yacc.yy src/sql/sql_yacc.yy --- orig/sql/sql_yacc.yy 2010-12-18 18:48:16.000000000 -0800 +++ src/sql/sql_yacc.yy 2010-12-21 09:50:49.000000000 -0800 @@ -676,6 +676,7 @@ %token IMPORT %token INDEXES %token INDEX_SYM +%token INDEX_STATS_SYM %token INFILE %token INNER_SYM %token INNOBASE_SYM @@ -935,6 +936,7 @@ %token TABLES %token TABLESPACE %token TABLE_SYM +%token TABLE_STATS_SYM %token TEMPORARY %token TEMPTABLE_SYM %token TERMINATED @@ -1088,7 +1090,7 @@ ident_list ident_list_arg opt_expr_list %type - option_type opt_var_type opt_var_ident_type + option_type opt_var_type opt_var_ident_type opt_var_type_default %type key_type opt_unique_or_fulltext constraint_key_type @@ -8158,6 +8160,16 @@ { Lex->sql_command = SQLCOM_SHOW_SLAVE_STAT; } + | opt_var_type_default TABLE_STATS_SYM wild_and_where + { + Lex->sql_command= SQLCOM_SHOW_TABLE_STATS; + Lex->option_type= $1; + } + | opt_var_type_default INDEX_STATS_SYM wild_and_where + { + Lex->sql_command = SQLCOM_SHOW_INDEX_STATS; + Lex->option_type= $1; + } | CREATE PROCEDURE sp_name { LEX *lex= Lex; @@ -8346,9 +8358,22 @@ lex->no_write_to_binlog= $2; } flush_options - {} - ; - + {}; + +/* +flush_stats: + FLUSH_SYM opt_var_type + TABLE_STATS_SYM + { + Lex->type|= REFRESH_TABLE_STATS; + Lex->option_type= $1; + } + | INDEX_STATS_SYM + { + Lex->type|= REFRESH_INDEX_STATS; + Lex->option_type= $1; + }; +*/ flush_options: flush_options ',' flush_option | flush_option; @@ -8364,7 +8389,31 @@ | SLAVE { Lex->type|= REFRESH_SLAVE; } | MASTER_SYM { Lex->type|= REFRESH_MASTER; } | DES_KEY_FILE { Lex->type|= REFRESH_DES_KEY_FILE; } - | RESOURCES { Lex->type|= REFRESH_USER_RESOURCES; }; + | RESOURCES { Lex->type|= REFRESH_USER_RESOURCES; } + | GLOBAL_SYM INDEX_STATS_SYM { + Lex->option_type= OPT_GLOBAL; + Lex->type|= REFRESH_INDEX_STATS; + } + | SESSION_SYM INDEX_STATS_SYM { + Lex->option_type= OPT_SESSION; + Lex->type|= REFRESH_INDEX_STATS; + } + | INDEX_STATS_SYM { + Lex->option_type= OPT_DEFAULT; + Lex->type|= REFRESH_INDEX_STATS; + } + | GLOBAL_SYM TABLE_STATS_SYM { + Lex->option_type= OPT_GLOBAL; + Lex->type|= REFRESH_TABLE_STATS; + } + | SESSION_SYM TABLE_STATS_SYM { + Lex->option_type= OPT_SESSION; + Lex->type|= REFRESH_TABLE_STATS; + } + | TABLE_STATS_SYM { + Lex->option_type= OPT_DEFAULT; + Lex->type|= REFRESH_TABLE_STATS; + } opt_table_list: /* empty */ {;} @@ -9668,6 +9717,13 @@ | SESSION_SYM { $$=OPT_SESSION; } ; +opt_var_type_default: + /* empty */ { $$=OPT_DEFAULT; } + | GLOBAL_SYM { $$=OPT_GLOBAL; } + | LOCAL_SYM { $$=OPT_SESSION; } + | SESSION_SYM { $$=OPT_SESSION; } + ; + opt_var_ident_type: /* empty */ { $$=OPT_DEFAULT; } | GLOBAL_SYM '.' { $$=OPT_GLOBAL; } diff -urN orig/sql/structs.h src/sql/structs.h --- orig/sql/structs.h 2010-12-18 18:48:16.000000000 -0800 +++ src/sql/structs.h 2010-12-21 09:50:49.000000000 -0800 @@ -272,6 +272,19 @@ time_t intime; } USER_CONN; +typedef struct st_table_stats { + char table[NAME_LEN * 2 + 2]; // [db] + '.' + [table] + '\0' + ulonglong rows_read, rows_changed; + ulonglong rows_changed_x_indexes; + /* Stores enum db_type, but forward declarations cannot be done */ + int engine_type; +} TABLE_STATS; + +typedef struct st_index_stats { + char index[NAME_LEN * 3 + 3]; // [db] + '.' + [table] + '.' + [index] + '\0' + ulonglong rows_read; +} INDEX_STATS; + /* Bits in form->update */ #define REG_MAKE_DUPP 1 /* Make a copy of record when read */ #define REG_NEW_RECORD 2 /* Write a new record if not found */