List:Commits« Previous MessageNext Message »
From:vasil.dimov Date:November 10 2010 8:30am
Subject:bzr commit into mysql-next-mr-innodb branch (vasil.dimov:3306)
View as plain text  
#At file:///usr/local/devel/bzrroot/server/mysql-next-mr-innodb/ based on revid:marko.makela@stripped

 3306 Vasil Dimov	2010-11-10 [merge]
      Merge mysql-trunk-innodb -> mysql-next-mr-innodb

    added:
      mysql-test/suite/innodb/r/innodb_bug53046.result
      mysql-test/suite/innodb/t/innodb_bug53046.test
    modified:
      storage/innobase/btr/btr0cur.c
      storage/innobase/dict/dict0dict.c
      storage/innobase/dict/dict0stats.c
      storage/innobase/handler/ha_innodb.cc
      storage/innobase/include/dict0dict.h
      storage/innobase/include/dict0stats.h
=== added file 'mysql-test/suite/innodb/r/innodb_bug53046.result'
--- a/mysql-test/suite/innodb/r/innodb_bug53046.result	1970-01-01 00:00:00 +0000
+++ b/mysql-test/suite/innodb/r/innodb_bug53046.result	revid:vasil.dimov@stripped
@@ -0,0 +1,27 @@
+CREATE TABLE bug53046_1 (c1 INT PRIMARY KEY) ENGINE=INNODB;
+CREATE TABLE bug53046_2 (c2 INT PRIMARY KEY,
+FOREIGN KEY (c2) REFERENCES bug53046_1(c1)
+ON UPDATE CASCADE ON DELETE CASCADE) ENGINE=INNODB;
+INSERT INTO bug53046_1 VALUES (1);
+INSERT INTO bug53046_1 SELECT c1+(SELECT MAX(c1) FROM bug53046_1)
+FROM bug53046_1;
+INSERT INTO bug53046_1 SELECT c1+(SELECT MAX(c1) FROM bug53046_1)
+FROM bug53046_1;
+INSERT INTO bug53046_1 SELECT c1+(SELECT MAX(c1) FROM bug53046_1)
+FROM bug53046_1;
+INSERT INTO bug53046_1 SELECT c1+(SELECT MAX(c1) FROM bug53046_1)
+FROM bug53046_1;
+INSERT INTO bug53046_1 SELECT c1+(SELECT MAX(c1) FROM bug53046_1)
+FROM bug53046_1;
+INSERT INTO bug53046_2 VALUES (1), (2);
+ANALYZE TABLE bug53046_1;
+Table	Op	Msg_type	Msg_text
+test.bug53046_1	analyze	status	OK
+SHOW TABLE STATUS LIKE 'bug53046_1';
+UPDATE bug53046_1 SET c1 = c1 - 1;
+DELETE FROM bug53046_1;
+INSERT INTO bug53046_1 VALUES (1);
+INSERT INTO bug53046_2 VALUES (1);
+TRUNCATE TABLE bug53046_2;
+DROP TABLE bug53046_2;
+DROP TABLE bug53046_1;

=== added file 'mysql-test/suite/innodb/t/innodb_bug53046.test'
--- a/mysql-test/suite/innodb/t/innodb_bug53046.test	1970-01-01 00:00:00 +0000
+++ b/mysql-test/suite/innodb/t/innodb_bug53046.test	revid:vasil.dimov@stripped
@@ -0,0 +1,48 @@
+#
+# http://bugs.mysql.com/53046
+# dict_update_statistics_low can still be run concurrently on same table
+#
+# This is a symbolic test, it would not fail if the bug is present.
+# Rather those SQL commands have been used during manual testing under
+# UNIV_DEBUG & UNIV_SYNC_DEBUG to test all changed codepaths for locking
+# correctness.
+#
+
+-- source include/have_innodb.inc
+
+CREATE TABLE bug53046_1 (c1 INT PRIMARY KEY) ENGINE=INNODB;
+CREATE TABLE bug53046_2 (c2 INT PRIMARY KEY,
+		 FOREIGN KEY (c2) REFERENCES bug53046_1(c1)
+		 ON UPDATE CASCADE ON DELETE CASCADE) ENGINE=INNODB;
+
+INSERT INTO bug53046_1 VALUES (1);
+let $i = 5;
+while ($i) {
+	eval INSERT INTO bug53046_1 SELECT c1+(SELECT MAX(c1) FROM bug53046_1)
+		FROM bug53046_1;
+	dec $i;
+}
+
+INSERT INTO bug53046_2 VALUES (1), (2);
+
+# CREATE TABLE innodb_table_monitor (a int) ENGINE=INNODB;
+# wait more than 1 minute and observe the mysqld output
+# DROP TABLE innodb_table_monitor;
+
+ANALYZE TABLE bug53046_1;
+
+# this prints create time and other nondeterministic data
+-- disable_result_log
+SHOW TABLE STATUS LIKE 'bug53046_1';
+-- enable_result_log
+
+UPDATE bug53046_1 SET c1 = c1 - 1;
+
+DELETE FROM bug53046_1;
+
+INSERT INTO bug53046_1 VALUES (1);
+INSERT INTO bug53046_2 VALUES (1);
+TRUNCATE TABLE bug53046_2;
+
+DROP TABLE bug53046_2;
+DROP TABLE bug53046_1;

=== modified file 'storage/innobase/btr/btr0cur.c'
--- a/storage/innobase/btr/btr0cur.c	revid:marko.makela@stripped
+++ b/storage/innobase/btr/btr0cur.c	revid:vasil.dimov@stripped
@@ -3635,8 +3635,6 @@ btr_estimate_number_of_different_key_val
 	also the pages used for external storage of fields (those pages are
 	included in index->stat_n_leaf_pages) */
 
-	dict_index_stat_mutex_enter(index);
-
 	for (j = 0; j <= n_cols; j++) {
 		index->stat_n_diff_key_vals[j]
 			= ((n_diff[j]
@@ -3668,8 +3666,6 @@ btr_estimate_number_of_different_key_val
 		index->stat_n_sample_sizes[j] = n_sample_pages;
 	}
 
-	dict_index_stat_mutex_exit(index);
-
 	mem_free(n_diff);
 	if (UNIV_LIKELY_NULL(heap)) {
 		mem_heap_free(heap);

=== modified file 'storage/innobase/dict/dict0dict.c'
--- a/storage/innobase/dict/dict0dict.c	revid:marko.makela@stripped
+++ b/storage/innobase/dict/dict0dict.c	revid:vasil.dimov@stripped
@@ -97,9 +97,18 @@ UNIV_INTERN mysql_pfs_key_t	dict_foreign
 /** Identifies generated InnoDB foreign key names */
 static char	dict_ibfk[] = "_ibfk_";
 
-/** array of mutexes protecting dict_index_t::stat_n_diff_key_vals[] */
-#define DICT_INDEX_STAT_MUTEX_SIZE	32
-mutex_t	dict_index_stat_mutex[DICT_INDEX_STAT_MUTEX_SIZE];
+/** array of rw locks protecting
+dict_table_t::stat_initialized
+dict_table_t::stat_n_rows (*)
+dict_table_t::stat_clustered_index_size
+dict_table_t::stat_sum_of_other_index_sizes
+dict_table_t::stat_modified_counter (*)
+dict_table_t::indexes*::stat_n_diff_key_vals[]
+dict_table_t::indexes*::stat_index_size
+dict_table_t::indexes*::stat_n_leaf_pages
+(*) those are not always protected for performance reasons */
+#define DICT_TABLE_STATS_LATCHES_SIZE	64
+static rw_lock_t	dict_table_stats_latches[DICT_TABLE_STATS_LATCHES_SIZE];
 
 /*******************************************************************//**
 Tries to find column names for the index and sets the col field of the
@@ -304,43 +313,65 @@ dict_mutex_exit_for_mysql(void)
 	mutex_exit(&(dict_sys->mutex));
 }
 
-/** Get the mutex that protects index->stat_n_diff_key_vals[] */
-#define GET_INDEX_STAT_MUTEX(index) \
-	(&dict_index_stat_mutex[ut_fold_ull(index->id) \
-				% DICT_INDEX_STAT_MUTEX_SIZE])
+/** Get the latch that protects the stats of a given table */
+#define GET_TABLE_STATS_LATCH(table) \
+	(&dict_table_stats_latches[ut_fold_ull(table->id) \
+				   % DICT_TABLE_STATS_LATCHES_SIZE])
 
 /**********************************************************************//**
-Lock the appropriate mutex to protect index->stat_n_diff_key_vals[].
-index->id is used to pick the right mutex and it should not change
-before dict_index_stat_mutex_exit() is called on this index. */
+Lock the appropriate latch to protect a given table's statistics.
+table->id is used to pick the corresponding latch from a global array of
+latches. */
 UNIV_INTERN
 void
-dict_index_stat_mutex_enter(
-/*========================*/
-	const dict_index_t*	index)	/*!< in: index */
+dict_table_stats_lock(
+/*==================*/
+	const dict_table_t*	table,		/*!< in: table */
+	ulint			latch_mode)	/*!< in: RW_S_LATCH or
+						RW_X_LATCH */
 {
-	ut_ad(index != NULL);
-	ut_ad(index->magic_n == DICT_INDEX_MAGIC_N);
-	ut_ad(index->cached);
-	ut_ad(!index->to_be_dropped);
+	ut_ad(table != NULL);
+	ut_ad(table->magic_n == DICT_TABLE_MAGIC_N);
 
-	mutex_enter(GET_INDEX_STAT_MUTEX(index));
+	switch (latch_mode) {
+	case RW_S_LATCH:
+		rw_lock_s_lock(GET_TABLE_STATS_LATCH(table));
+		break;
+	case RW_X_LATCH:
+		rw_lock_x_lock(GET_TABLE_STATS_LATCH(table));
+		break;
+	case RW_NO_LATCH:
+		/* fall through */
+	default:
+		ut_error;
+	}
 }
 
 /**********************************************************************//**
-Unlock the appropriate mutex that protects index->stat_n_diff_key_vals[]. */
+Unlock the latch that has been locked by dict_table_stats_lock() */
 UNIV_INTERN
 void
-dict_index_stat_mutex_exit(
-/*=======================*/
-	const dict_index_t*	index)	/*!< in: index */
+dict_table_stats_unlock(
+/*====================*/
+	const dict_table_t*	table,		/*!< in: table */
+	ulint			latch_mode)	/*!< in: RW_S_LATCH or
+						RW_X_LATCH */
 {
-	ut_ad(index != NULL);
-	ut_ad(index->magic_n == DICT_INDEX_MAGIC_N);
-	ut_ad(index->cached);
-	ut_ad(!index->to_be_dropped);
+	ut_ad(table != NULL);
+	ut_ad(table->magic_n == DICT_TABLE_MAGIC_N);
 
-	mutex_exit(GET_INDEX_STAT_MUTEX(index));
+	switch (latch_mode) {
+	case RW_S_LATCH:
+		rw_lock_s_unlock(GET_TABLE_STATS_LATCH(table));
+		break;
+	case RW_X_LATCH:
+		rw_lock_x_unlock(GET_TABLE_STATS_LATCH(table));
+		break;
+	case RW_NO_LATCH:
+		/* fall through */
+	default:
+		ut_error;
+	}
 }
 
 /********************************************************************//**
@@ -744,9 +775,9 @@ dict_init(void)
 	mutex_create(dict_foreign_err_mutex_key,
 		     &dict_foreign_err_mutex, SYNC_ANY_LATCH);
 
-	for (i = 0; i < DICT_INDEX_STAT_MUTEX_SIZE; i++) {
-		mutex_create(PFS_NOT_INSTRUMENTED,
-			     &dict_index_stat_mutex[i], SYNC_INDEX_TREE);
+	for (i = 0; i < DICT_TABLE_STATS_LATCHES_SIZE; i++) {
+		rw_lock_create(PFS_NOT_INSTRUMENTED,
+			       &dict_table_stats_latches[i], SYNC_INDEX_TREE);
 	}
 }
 
@@ -826,11 +857,13 @@ dict_table_open_on_name(
 
 	table = dict_table_open_on_name_low(table_name, dict_locked);
 
-	if (table != NULL && !table->stat_initialized) {
+	if (table != NULL) {
 		/* If table->ibd_file_missing == TRUE, this will
 		print an error message and return without doing
 		anything. */
-		dict_stats_update(table, DICT_STATS_FETCH, dict_locked);
+		dict_stats_update(table,
+				  DICT_STATS_FETCH_ONLY_IF_NOT_IN_MEMORY,
+				  dict_locked);
 	}
 
 	return(table);
@@ -4663,6 +4696,8 @@ dict_table_print_low(
 
 	dict_stats_update(table, DICT_STATS_FETCH, TRUE);
 
+	dict_table_stats_lock(table, RW_S_LATCH);
+
 	fprintf(stderr,
 		"--------------------------------------\n"
 		"TABLE: name %s, id %llu, flags %lx, columns %lu,"
@@ -4689,6 +4724,8 @@ dict_table_print_low(
 		index = UT_LIST_GET_NEXT(indexes, index);
 	}
 
+	dict_table_stats_unlock(table, RW_S_LATCH);
+
 	foreign = UT_LIST_GET_FIRST(table->foreign_list);
 
 	while (foreign != NULL) {
@@ -4737,8 +4774,6 @@ dict_index_print_low(
 
 	ut_ad(mutex_own(&(dict_sys->mutex)));
 
-	dict_index_stat_mutex_enter(index);
-
 	if (index->n_user_defined_cols > 0) {
 		n_vals = index->stat_n_diff_key_vals[
 			index->n_user_defined_cols];
@@ -4746,8 +4781,6 @@ dict_index_print_low(
 		n_vals = index->stat_n_diff_key_vals[1];
 	}
 
-	dict_index_stat_mutex_exit(index);
-
 	fprintf(stderr,
 		"  INDEX: name %s, id %llu, fields %lu/%lu,"
 		" uniq %lu, type %lu\n"
@@ -5386,8 +5419,8 @@ dict_close(void)
 	mem_free(dict_sys);
 	dict_sys = NULL;
 
-	for (i = 0; i < DICT_INDEX_STAT_MUTEX_SIZE; i++) {
-		mutex_free(&dict_index_stat_mutex[i]);
+	for (i = 0; i < DICT_TABLE_STATS_LATCHES_SIZE; i++) {
+		rw_lock_free(&dict_table_stats_latches[i]);
 	}
 }
 #endif /* !UNIV_HOTBACKUP */

=== modified file 'storage/innobase/dict/dict0stats.c'
--- a/storage/innobase/dict/dict0stats.c	revid:marko.makela@stripped
+++ b/storage/innobase/dict/dict0stats.c	revid:vasil.dimov@stripped
@@ -206,21 +206,17 @@ dict_stats_update_transient(
 
 	index = dict_table_get_first_index(table);
 
-	dict_index_stat_mutex_enter(index);
-
 	table->stat_n_rows = index->stat_n_diff_key_vals[
 		dict_index_get_n_unique(index)];
 
-	dict_index_stat_mutex_exit(index);
-
 	table->stat_clustered_index_size = index->stat_index_size;
 
 	table->stat_sum_of_other_index_sizes = sum_of_index_sizes
 		- index->stat_index_size;
 
-	table->stat_initialized = TRUE;
-
 	table->stat_modified_counter = 0;
+
+	table->stat_initialized = TRUE;
 }
 /* @} */
 
@@ -1055,16 +1051,12 @@ dict_stats_analyze_index_for_n_prefix(
 				&pcur, n_prefix, &mtr);
 	}
 
-	dict_index_stat_mutex_enter(index);
-
 	index->stat_n_diff_key_vals[n_prefix]
 		= total_recs_on_level * n_diff_sum_of_all_analyzed_pages
 		/ n_recs_to_dive_below;
 
 	index->stat_n_sample_sizes[n_prefix] = n_recs_to_dive_below;
 
-	dict_index_stat_mutex_exit(index);
-
 	DEBUG_PRINTF("    %s(): n_diff=%llu for n_prefix=%lu\n",
 		     __func__, index->stat_n_diff_key_vals[n_prefix],
 		     n_prefix);
@@ -1144,8 +1136,6 @@ dict_stats_analyze_index(
 		/* do full scan of level 0; save results directly
 		into the index */
 
-		dict_index_stat_mutex_enter(index);
-
 		dict_stats_analyze_index_level(index,
 					       0 /* leaf level */,
 					       index->stat_n_diff_key_vals,
@@ -1157,8 +1147,6 @@ dict_stats_analyze_index(
 			index->stat_n_sample_sizes[i] = total_pages;
 		}
 
-		dict_index_stat_mutex_exit(index);
-
 		return(DB_SUCCESS);
 	}
 	/* else */
@@ -1362,10 +1350,10 @@ dict_stats_update_persistent(
 			+= index->stat_index_size;
 	}
 
-	table->stat_initialized = TRUE;
-
 	table->stat_modified_counter = 0;
 
+	table->stat_initialized = TRUE;
+
 	return(DB_SUCCESS);
 }
 /* @} */
@@ -1582,9 +1570,6 @@ dict_stats_save(
 	     index != NULL;
 	     index = dict_table_get_next_index(index)) {
 
-		/* on stack copies of index members to minimize the
-		scope of dict_index_stat_mutex; the maximum number
-		of elements is the max value of index->n_uniq */
 		ib_uint64_t	stat_n_diff_key_vals[REC_MAX_N_FIELDS];
 		ib_uint64_t	stat_n_sample_sizes[REC_MAX_N_FIELDS];
 		ulint		n_uniq;
@@ -1614,8 +1599,6 @@ dict_stats_save(
 
 		n_uniq = dict_index_get_n_unique(index);
 
-		dict_index_stat_mutex_enter(index);
-
 		ut_ad(n_uniq + 1 <= UT_ARR_SIZE(stat_n_diff_key_vals));
 
 		memcpy(stat_n_diff_key_vals, index->stat_n_diff_key_vals,
@@ -1626,8 +1609,6 @@ dict_stats_save(
 		memcpy(stat_n_sample_sizes, index->stat_n_sample_sizes,
 		       (n_uniq + 1) * sizeof(index->stat_n_sample_sizes[0]));
 
-		dict_index_stat_mutex_exit(index);
-
 		for (i = 1; i <= n_uniq; i++) {
 
 			char	stat_name[16];
@@ -1971,8 +1952,6 @@ dict_stats_fetch_index_stats_step(
 		}
 		/* else */
 
-		dict_index_stat_mutex_enter(index);
-
 		index->stat_n_diff_key_vals[n_pfx] = stat_value;
 
 		if (sample_size != UINT64_UNDEFINED) {
@@ -1982,8 +1961,6 @@ dict_stats_fetch_index_stats_step(
 			table manually and SET sample_size = NULL */
 			index->stat_n_sample_sizes[n_pfx] = 0;
 		}
-
-		dict_index_stat_mutex_exit(index);
 	} else {
 		/* silently ignore rows with unknown stat_name, the
 		user may have developed her own stats */
@@ -2177,50 +2154,75 @@ dict_stats_update(
 		if (dict_stats_persistent_storage_check(
 				caller_has_dict_sys_mutex)) {
 
+			dict_table_stats_lock(table, RW_X_LATCH);
+
 			ret = dict_stats_update_persistent(table);
 
+			/* XXX Currently dict_stats_save() would read the
+			stats from the table without dict_table_stats_lock()
+			which means it could save inconsistent data on the
+			disk. This is because we must call
+			dict_table_stats_lock() after locking dict_sys->mutex.
+			A solution is to copy here the stats to a temporary
+			buffer while holding the _stats_lock(), release it,
+			and pass that buffer to dict_stats_save(). */
+
+			dict_table_stats_unlock(table, RW_X_LATCH);
+
 			if (ret == DB_SUCCESS) {
 				ret = dict_stats_save(
 					table,
 					caller_has_dict_sys_mutex);
 			}
 
-		} else {
-			/* Fall back to transient stats since the persistent
-			storage is not present or is corrupted */
-
-			if (stats_upd_option == DICT_STATS_RECALC_PERSISTENT) {
+			return(ret);
+		}
+		/* else */
 
-				ut_print_timestamp(stderr);
-				/* XXX add link to the doc about storage
-				creation */
-				fprintf(stderr,
-					" InnoDB: Recalculation of persistent "
-					"statistics requested but the required "
-					"persistent statistics storage is not "
-					"present or is corrupted. "
-					"Using quick transient stats "
-					"instead.\n");
-			}
+		/* Fall back to transient stats since the persistent
+		storage is not present or is corrupted */
 
-			dict_stats_update_transient(table);
+		if (stats_upd_option == DICT_STATS_RECALC_PERSISTENT) {
 
-			ret = DB_SUCCESS;
+			ut_print_timestamp(stderr);
+			/* XXX add link to the doc about storage
+			creation */
+			fprintf(stderr,
+				" InnoDB: Recalculation of persistent "
+				"statistics requested but the required "
+				"persistent statistics storage is not "
+				"present or is corrupted. "
+				"Using quick transient stats "
+				"instead.\n");
 		}
 
-		break;
+		goto transient;
 
 	case DICT_STATS_RECALC_TRANSIENT:
 
-		dict_stats_update_transient(table);
-		ret = DB_SUCCESS;
-
-		break;
+		goto transient;
 
 	case DICT_STATS_FETCH:
+	case DICT_STATS_FETCH_ONLY_IF_NOT_IN_MEMORY:
 		/* fetch requested, either fetch from persistent statistics
 		storage or use the old method */
 
+		dict_table_stats_lock(table, RW_X_LATCH);
+
+		if (stats_upd_option == DICT_STATS_FETCH_ONLY_IF_NOT_IN_MEMORY
+		    && table->stat_initialized) {
+
+			dict_table_stats_unlock(table, RW_X_LATCH);
+			return(DB_SUCCESS);
+		}
+		/* else */
+
+		/* Must unlock because otherwise there is a lock order
+		violation with dict_sys->mutex below. Declare stats to be
+		initialized before unlocking. */
+		table->stat_initialized = TRUE;
+		dict_table_stats_unlock(table, RW_X_LATCH);
+
 		if (strchr(table->name, '/') == NULL
 		    || strcmp(table->name, INDEX_STATS_NAME) == 0
 		    || strcmp(table->name, TABLE_STATS_NAME) == 0) {
@@ -2228,32 +2230,34 @@ dict_stats_update(
 			InnoDB internal tables, because we know the
 			persistent stats storage does not contain data
 			for them */
-			dict_stats_update_transient(table);
-			ret = DB_SUCCESS;
-		} else if (dict_stats_persistent_storage_check(
-				caller_has_dict_sys_mutex)) {
 
-			ret = dict_stats_fetch_from_ps(
-				table,
+			goto transient;
+		}
+		/* else */
+
+		if (dict_stats_persistent_storage_check(
+			caller_has_dict_sys_mutex)) {
+
+			ret = dict_stats_fetch_from_ps(table,
 				caller_has_dict_sys_mutex);
-		} else {
 
-			/* if persistent statistics storage does not exist,
-			then force the following code to calculate the
-			transient stats */
-			ret = DB_STATS_DO_NOT_EXIST;
-		}
-
-		if (ret == DB_STATS_DO_NOT_EXIST) {
-
-			/* The persistent statistics storage does not exist
-			or stats for this particular table do not exist, then
-			calculate the quick transient statistics */
-			dict_stats_update_transient(table);
-			ret = DB_SUCCESS;
+			if (ret == DB_STATS_DO_NOT_EXIST
+			    || (ret != DB_SUCCESS && stats_upd_option
+				== DICT_STATS_FETCH_ONLY_IF_NOT_IN_MEMORY)) {
+				/* Stats for this particular table do not
+				exist or we have been called from open table
+				which needs to initialize the stats,
+				calculate the quick transient statistics */
+				goto transient;
+			}
+			/* else */
+
+			return(ret);
+		} else {
+			/* persistent statistics storage does not exist,
+			calculate the transient stats */
+			goto transient;
 		}
-		/* else either success or some other failure, return
-		ret whatever it is */
 
 		break;
 
@@ -2261,13 +2265,22 @@ dict_stats_update(
 	about unhandled enumeration value */
 	}
 
-	return(ret);
+transient:
+
+	dict_table_stats_lock(table, RW_X_LATCH);
+
+	dict_stats_update_transient(table);
+
+	dict_table_stats_unlock(table, RW_X_LATCH);
+
+	return(DB_SUCCESS);
 }
 /* @} */
 
 /*********************************************************************//**
 Close the stats tables. Should always be called after successful
-dict_stats_open(). It will free the dict_stats handle. */
+dict_stats_open(). It will free the dict_stats handle.
+dict_stats_close() @{ */
 UNIV_INLINE
 void
 dict_stats_close(
@@ -2287,10 +2300,13 @@ dict_stats_close(
 
 	mem_free(dict_stats);
 }
+/* @} */
+
 /*********************************************************************//**
 Open stats tables to prevent these tables from being DROPped.
 Also check whether they have the correct structure. The caller
 must call dict_stats_close() when he has finished DMLing the tables.
+dict_stats_open() @{
 @return pointer to open tables or NULL on failure */
 UNIV_INLINE
 dict_stats_t*
@@ -2323,6 +2339,7 @@ dict_stats_open(void)
 
 	return(dict_stats);
 }
+/* @} */
 
 /*********************************************************************//**
 Removes the information for a particular index's stats from the persistent

=== modified file 'storage/innobase/handler/ha_innodb.cc'
--- a/storage/innobase/handler/ha_innodb.cc	revid:marko.makela@stripped
+++ b/storage/innobase/handler/ha_innodb.cc	revid:vasil.dimov@stripped
@@ -7935,6 +7935,7 @@ ha_innobase::estimate_rows_upper_bound()
 	dict_index_t*	index;
 	ulonglong	estimate;
 	ulonglong	local_data_file_length;
+	ulint		stat_n_leaf_pages;
 
 	DBUG_ENTER("estimate_rows_upper_bound");
 
@@ -7954,10 +7955,12 @@ ha_innobase::estimate_rows_upper_bound()
 
 	index = dict_table_get_first_index(prebuilt->table);
 
-	ut_a(index->stat_n_leaf_pages > 0);
+	stat_n_leaf_pages = index->stat_n_leaf_pages;
+
+	ut_a(stat_n_leaf_pages > 0);
 
 	local_data_file_length =
-		((ulonglong) index->stat_n_leaf_pages) * UNIV_PAGE_SIZE;
+		((ulonglong) stat_n_leaf_pages) * UNIV_PAGE_SIZE;
 
 
 	/* Calculate a minimum length for a clustered index record and from
@@ -8185,6 +8188,9 @@ ha_innobase::info_low(
 	}
 
 	if (flag & HA_STATUS_VARIABLE) {
+
+		dict_table_stats_lock(ib_table, RW_S_LATCH);
+
 		n_rows = ib_table->stat_n_rows;
 
 		/* Because we do not protect stat_n_rows by any mutex in a
@@ -8234,6 +8240,8 @@ ha_innobase::info_low(
 				ib_table->stat_sum_of_other_index_sizes)
 					* UNIV_PAGE_SIZE;
 
+		dict_table_stats_unlock(ib_table, RW_S_LATCH);
+
 		/* Since fsp_get_available_space_in_free_extents() is
 		acquiring latches inside InnoDB, we do not call it if we
 		are asked by MySQL to avoid locking. Another reason to
@@ -8305,6 +8313,8 @@ ha_innobase::info_low(
 					table->s->keys);
 		}
 
+		dict_table_stats_lock(ib_table, RW_S_LATCH);
+
 		for (i = 0; i < table->s->keys; i++) {
 			ulong	j;
 			/* We could get index quickly through internal
@@ -8342,8 +8352,6 @@ ha_innobase::info_low(
 					break;
 				}
 
-				dict_index_stat_mutex_enter(index);
-
 				if (index->stat_n_diff_key_vals[j + 1] == 0) {
 
 					rec_per_key = stats.records;
@@ -8352,8 +8360,6 @@ ha_innobase::info_low(
 					 index->stat_n_diff_key_vals[j + 1]);
 				}
 
-				dict_index_stat_mutex_exit(index);
-
 				/* Since MySQL seems to favor table scans
 				too much over index searches, we pretend
 				index selectivity is 2 times better than
@@ -8370,6 +8376,8 @@ ha_innobase::info_low(
 				  (ulong) rec_per_key;
 			}
 		}
+
+		dict_table_stats_unlock(ib_table, RW_S_LATCH);
 	}
 
 	if (srv_force_recovery >= SRV_FORCE_NO_IBUF_MERGE) {

=== modified file 'storage/innobase/include/dict0dict.h'
--- a/storage/innobase/include/dict0dict.h	revid:marko.makela@stripped
+++ b/storage/innobase/include/dict0dict.h	revid:vasil.dimov@stripped
@@ -1087,21 +1087,25 @@ void
 dict_mutex_exit_for_mysql(void);
 /*===========================*/
 /**********************************************************************//**
-Lock the appropriate mutex to protect index->stat_n_diff_key_vals[].
-index->id is used to pick the right mutex and it should not change
-before dict_index_stat_mutex_exit() is called on this index. */
+Lock the appropriate latch to protect a given table's statistics.
+table->id is used to pick the corresponding latch from a global array of
+latches. */
 UNIV_INTERN
 void
-dict_index_stat_mutex_enter(
-/*========================*/
-	const dict_index_t*	index);	/*!< in: index */
+dict_table_stats_lock(
+/*==================*/
+	const dict_table_t*	table,		/*!< in: table */
+	ulint			latch_mode);	/*!< in: RW_S_LATCH or
+						RW_X_LATCH */
 /**********************************************************************//**
-Unlock the appropriate mutex that protects index->stat_n_diff_key_vals[]. */
+Unlock the latch that has been locked by dict_table_stats_lock() */
 UNIV_INTERN
 void
-dict_index_stat_mutex_exit(
-/*=======================*/
-	const dict_index_t*	index);	/*!< in: index */
+dict_table_stats_unlock(
+/*====================*/
+	const dict_table_t*	table,		/*!< in: table */
+	ulint			latch_mode);	/*!< in: RW_S_LATCH or
+						RW_X_LATCH */
 /********************************************************************//**
 Checks if the database name in two table names is the same.
 @return	TRUE if same db name */

=== modified file 'storage/innobase/include/dict0stats.h'
--- a/storage/innobase/include/dict0stats.h	revid:marko.makela@stripped
+++ b/storage/innobase/include/dict0stats.h	revid:vasil.dimov@stripped
@@ -46,8 +46,12 @@ enum dict_stats_upd_option {
 				using an imprecise quick algo
 				without saving the results
 				persistently */
-	DICT_STATS_FETCH	/* fetch the statistics from the
+	DICT_STATS_FETCH,	/* fetch the statistics from the
 				persistent storage */
+	DICT_STATS_FETCH_ONLY_IF_NOT_IN_MEMORY /* only fetch the stats
+				from the persistent storage if the in-memory
+				structures have not been initialized yet,
+				otherwise do nothing */
 };
 
 typedef enum dict_stats_upd_option	dict_stats_upd_option_t;

No bundle (reason: revision is a merge).
Thread
bzr commit into mysql-next-mr-innodb branch (vasil.dimov:3306) vasil.dimov10 Nov