commit 90cddae04e4e8e568be9c8eb83ea6e40b18ef8ba
Author: Marko Mäkelä <marko.makela@mariadb.com>
Date:   Fri Nov 20 12:48:07 2020 +0200

    MDEV-24258 WIP (hangs on rollback): Try removing dict_sys.mutex

diff --git a/storage/innobase/btr/btr0sea.cc b/storage/innobase/btr/btr0sea.cc
index 1e10b6751fa..22a6773ec5a 100644
--- a/storage/innobase/btr/btr0sea.cc
+++ b/storage/innobase/btr/btr0sea.cc
@@ -223,12 +223,12 @@ void btr_search_disable()
 {
 	dict_table_t*	table;
 
-	mutex_enter(&dict_sys.mutex);
+	dict_sys.freeze();
 
 	btr_search_x_lock_all();
 
 	if (!btr_search_enabled) {
-		mutex_exit(&dict_sys.mutex);
+		dict_sys.unfreeze();
 		btr_search_x_unlock_all();
 		return;
 	}
@@ -249,7 +249,7 @@ void btr_search_disable()
 		btr_search_disable_ref_count(table);
 	}
 
-	mutex_exit(&dict_sys.mutex);
+	dict_sys.unfreeze();
 
 	/* Set all block->index = NULL. */
 	buf_pool.clear_hash_index();
@@ -1257,7 +1257,7 @@ void btr_search_drop_page_hash_index(buf_block_t* block)
 	ut_ad(page_is_leaf(block->frame));
 
 	/* We must not dereference block->index here, because it could be freed
-	if (index->table->n_ref_count == 0 && !mutex_own(&dict_sys.mutex)).
+	if (index->table->n_ref_count == 0 && !dict_sys.frozen()).
 	Determine the ahi_slot based on the block contents. */
 
 	const index_id_t	index_id
@@ -1424,7 +1424,7 @@ void btr_search_drop_page_hash_when_freed(const page_id_t page_id)
 			be open, or we should be in the process of
 			dropping the table (preventing eviction). */
 			ut_ad(index->table->get_ref_count() > 0
-			      || mutex_own(&dict_sys.mutex));
+			      || dict_sys.locked());
 			btr_search_drop_page_hash_index(block);
 		}
 	}
diff --git a/storage/innobase/dict/dict0boot.cc b/storage/innobase/dict/dict0boot.cc
index f4f94e2ace9..96ef86d73f7 100644
--- a/storage/innobase/dict/dict0boot.cc
+++ b/storage/innobase/dict/dict0boot.cc
@@ -244,7 +244,7 @@ dict_boot(void)
 
 	heap = mem_heap_create(450);
 
-	mutex_enter(&dict_sys.mutex);
+	dict_sys.lock();
 
 	/* Get the dictionary header */
 	const byte* dict_hdr = &dict_hdr_get(&mtr)->frame[DICT_HDR];
@@ -438,7 +438,7 @@ dict_boot(void)
 		dict_load_sys_table(dict_sys.sys_fields);
 	}
 
-	mutex_exit(&dict_sys.mutex);
+	dict_sys.unlock();
 
 	return(err);
 }
diff --git a/storage/innobase/dict/dict0crea.cc b/storage/innobase/dict/dict0crea.cc
index 9f0fe2b3585..a01eb9a0286 100644
--- a/storage/innobase/dict/dict0crea.cc
+++ b/storage/innobase/dict/dict0crea.cc
@@ -343,7 +343,7 @@ dict_build_table_def_step(
 	que_thr_t*	thr,	/*!< in: query thread */
 	tab_node_t*	node)	/*!< in: table create node */
 {
-	ut_ad(mutex_own(&dict_sys.mutex));
+	ut_d(dict_sys.assert_locked());
 	dict_table_t*	table = node->table;
 	trx_t* trx = thr_get_trx(thr);
 	ut_ad(!table->is_temporary());
@@ -470,7 +470,7 @@ dict_build_v_col_def_step(
 Based on an index object, this function builds the entry to be inserted
 in the SYS_INDEXES system table.
 @return the tuple which should be inserted */
-static
+static MY_ATTRIBUTE((nonnull, warn_unused_result))
 dtuple_t*
 dict_create_sys_indexes_tuple(
 /*==========================*/
@@ -483,12 +483,10 @@ dict_create_sys_indexes_tuple(
 	dfield_t*	dfield;
 	byte*		ptr;
 
-	ut_ad(mutex_own(&dict_sys.mutex));
-	ut_ad(index);
+	ut_d(dict_sys.assert_locked());
 	ut_ad(index->table->space || index->table->file_unreadable);
 	ut_ad(!index->table->space
 	      || index->table->space->id == index->table->space_id);
-	ut_ad(heap);
 
 	entry = dtuple_create(
 		heap, DICT_NUM_COLS__SYS_INDEXES + DATA_N_SYS_COLS);
@@ -712,7 +710,7 @@ dict_build_index_def_step(
 	dtuple_t*	row;
 	trx_t*		trx;
 
-	ut_ad(mutex_own(&dict_sys.mutex));
+	ut_d(dict_sys.assert_locked());
 
 	trx = thr_get_trx(thr);
 
@@ -763,7 +761,7 @@ dict_build_index_def(
 	dict_index_t*		index,	/*!< in/out: index */
 	trx_t*			trx)	/*!< in/out: InnoDB transaction handle */
 {
-	ut_ad(mutex_own(&dict_sys.mutex));
+	ut_d(dict_sys.assert_locked());
 
 	if (trx->table_id == 0) {
 		/* Record only the first table id. */
@@ -811,7 +809,7 @@ dict_create_index_tree_step(
 	dict_index_t*	index;
 	dtuple_t*	search_tuple;
 
-	ut_ad(mutex_own(&dict_sys.mutex));
+	ut_d(dict_sys.assert_locked());
 
 	index = node->index;
 
@@ -880,7 +878,7 @@ dict_create_index_tree_in_mem(
 {
 	mtr_t		mtr;
 
-	ut_ad(mutex_own(&dict_sys.mutex));
+	ut_d(dict_sys.assert_locked());
 	ut_ad(!(index->type & DICT_FTS));
 
 	mtr_start(&mtr);
@@ -910,7 +908,7 @@ void dict_drop_index_tree(btr_pcur_t* pcur, trx_t* trx, mtr_t* mtr)
 	byte*	ptr;
 	ulint	len;
 
-	ut_ad(mutex_own(&dict_sys.mutex));
+	ut_d(dict_sys.assert_locked());
 	ut_a(!dict_table_is_comp(dict_sys.sys_indexes));
 
 	ptr = rec_get_nth_field_old(rec, DICT_FLD__SYS_INDEXES__PAGE_NO, &len);
@@ -1056,7 +1054,7 @@ dict_create_table_step(
 	trx_t*		trx;
 
 	ut_ad(thr);
-	ut_ad(mutex_own(&dict_sys.mutex));
+	ut_d(dict_sys.assert_locked());
 
 	trx = thr_get_trx(thr);
 
@@ -1199,7 +1197,7 @@ dict_create_index_step(
 	trx_t*		trx;
 
 	ut_ad(thr);
-	ut_ad(mutex_own(&dict_sys.mutex));
+	ut_d(dict_sys.assert_locked());
 
 	trx = thr_get_trx(thr);
 
@@ -1360,7 +1358,7 @@ dict_check_if_system_table_exists(
 
 	ut_ad(!srv_any_background_activity());
 
-	mutex_enter(&dict_sys.mutex);
+	dict_sys.lock();
 
 	sys_table = dict_table_get_low(tablename);
 
@@ -1378,7 +1376,7 @@ dict_check_if_system_table_exists(
 		dict_table_prevent_eviction(sys_table);
 	}
 
-	mutex_exit(&dict_sys.mutex);
+	dict_sys.unlock();
 
 	return(error);
 }
@@ -1546,9 +1544,9 @@ dict_create_or_check_sys_virtual()
 		"SYS_VIRTUAL", DICT_NUM_FIELDS__SYS_VIRTUAL + 1, 1);
 
 	if (err == DB_SUCCESS) {
-		mutex_enter(&dict_sys.mutex);
+		dict_sys.lock();
 		dict_sys.sys_virtual = dict_table_get_low("SYS_VIRTUAL");
-		mutex_exit(&dict_sys.mutex);
+		dict_sys.unlock();
 		return(DB_SUCCESS);
 	}
 
@@ -1621,9 +1619,9 @@ dict_create_or_check_sys_virtual()
 	dberr_t sys_virtual_err = dict_check_if_system_table_exists(
 		"SYS_VIRTUAL", DICT_NUM_FIELDS__SYS_VIRTUAL + 1, 1);
 	ut_a(sys_virtual_err == DB_SUCCESS);
-	mutex_enter(&dict_sys.mutex);
+	dict_sys.lock();
 	dict_sys.sys_virtual = dict_table_get_low("SYS_VIRTUAL");
-	mutex_exit(&dict_sys.mutex);
+	dict_sys.unlock();
 
 	return(err);
 }
@@ -2020,7 +2018,7 @@ dict_create_add_foreigns_to_dictionary(
 	dict_foreign_t*	foreign;
 	dberr_t		error;
 
-	ut_ad(mutex_own(&dict_sys.mutex));
+	ut_d(dict_sys.assert_locked());
 
 	if (NULL == dict_table_get_low("SYS_FOREIGN")) {
 
diff --git a/storage/innobase/dict/dict0defrag_bg.cc b/storage/innobase/dict/dict0defrag_bg.cc
index 0d9cb185b81..66a9c3eff60 100644
--- a/storage/innobase/dict/dict0defrag_bg.cc
+++ b/storage/innobase/dict/dict0defrag_bg.cc
@@ -151,7 +151,7 @@ dict_stats_defrag_pool_del(
 {
 	ut_a((table && !index) || (!table && index));
 	ut_ad(!srv_read_only_mode);
-	ut_ad(mutex_own(&dict_sys.mutex));
+	ut_ad(dict_sys.frozen());
 
 	mutex_enter(&defrag_pool_mutex);
 
@@ -193,7 +193,7 @@ dict_stats_process_entry_from_defrag_pool()
 
 	dict_table_t*	table;
 
-	mutex_enter(&dict_sys.mutex);
+	dict_sys.lock();
 
 	/* If the table is no longer cached, we've already lost the in
 	memory stats so there's nothing really to write to disk. */
@@ -208,11 +208,11 @@ dict_stats_process_entry_from_defrag_pool()
 		if (table) {
 			dict_table_close(table, TRUE, FALSE);
 		}
-		mutex_exit(&dict_sys.mutex);
+		dict_sys.unlock();
 		return;
 	}
 
-	mutex_exit(&dict_sys.mutex);
+	dict_sys.unlock();
 	dict_stats_save_defrag_stats(index);
 	dict_table_close(table, FALSE, FALSE);
 }
@@ -243,7 +243,7 @@ dict_stats_save_defrag_summary(
 		return DB_SUCCESS;
 	}
 
-	dict_sys_lock();
+	dict_sys.lock();
 
 	ret = dict_stats_save_index_stat(index, time(NULL), "n_pages_freed",
 					 index->stat_defrag_n_pages_freed,
@@ -252,7 +252,7 @@ dict_stats_save_defrag_summary(
 					 " last defragmentation run.",
 					 NULL);
 
-	dict_sys_unlock();
+	dict_sys.unlock();
 
 	return (ret);
 }
@@ -292,7 +292,7 @@ dict_stats_save_defrag_stats(
 		return DB_SUCCESS;
 	}
 
-	dict_sys_lock();
+	dict_sys.lock();
 	ret = dict_stats_save_index_stat(index, now, "n_page_split",
 					 index->stat_defrag_n_page_split,
 					 NULL,
@@ -322,6 +322,6 @@ dict_stats_save_defrag_stats(
 		NULL);
 
 end:
-	dict_sys_unlock();
+	dict_sys.unlock();
 	return ret;
 }
diff --git a/storage/innobase/dict/dict0dict.cc b/storage/innobase/dict/dict0dict.cc
index 881f4c890c9..1693ec8e4ea 100644
--- a/storage/innobase/dict/dict0dict.cc
+++ b/storage/innobase/dict/dict0dict.cc
@@ -268,11 +268,10 @@ dict_table_try_drop_aborted(
 /**********************************************************************//**
 When opening a table,
 try to drop any indexes after an aborted index creation.
-Release the dict_sys.mutex. */
+Invoke dict_sys.unlock(). */
 static
 void
-dict_table_try_drop_aborted_and_mutex_exit(
-/*=======================================*/
+dict_table_try_drop_aborted_and_unlock(
 	dict_table_t*	table,		/*!< in: table (may be NULL) */
 	ibool		try_drop)	/*!< in: FALSE if should try to
 					drop indexes whose online creation
@@ -288,11 +287,11 @@ dict_table_try_drop_aborted_and_mutex_exit(
 		was aborted. */
 		table_id_t	table_id = table->id;
 
-		mutex_exit(&dict_sys.mutex);
+		dict_sys.unlock();
 
 		dict_table_try_drop_aborted(table, table_id, 1);
 	} else {
-		mutex_exit(&dict_sys.mutex);
+		dict_sys.unlock();
 	}
 }
 
@@ -313,10 +312,10 @@ dict_table_close(
 	MDL_ticket*	mdl)
 {
 	if (!dict_locked) {
-		mutex_enter(&dict_sys.mutex);
+		dict_sys.lock();
 	}
 
-	ut_ad(mutex_own(&dict_sys.mutex));
+	ut_d(dict_sys.assert_locked());
 	ut_a(table->get_ref_count() > 0);
 
 	const bool last_handle = table->release();
@@ -343,7 +342,7 @@ dict_table_close(
 			&& table->drop_aborted
 			&& dict_table_get_first_index(table);
 
-		mutex_exit(&dict_sys.mutex);
+		dict_sys.unlock();
 
 		/* dict_table_try_drop_aborted() can generate undo logs.
 		So it should be avoided after shutdown of background
@@ -362,10 +361,8 @@ dict_table_close(
 
 /********************************************************************//**
 Closes the only open handle to a table and drops a table while assuring
-that dict_sys.mutex is held the whole time.  This assures that the table
-is not evicted after the close when the count of open handles goes to zero.
-Because dict_sys.mutex is held, we do not need to call
-dict_table_prevent_eviction().  */
+that dict_sys.frozen() holds the whole time.  This assures that the table
+is not evicted after the close when table->n_ref_count reaches 0. */
 void
 dict_table_close_and_drop(
 /*======================*/
@@ -723,7 +720,7 @@ dict_index_get_nth_field_pos(
 }
 
 /** Parse the table file name into table name and database name.
-@tparam		dict_locked	whether dict_sys.mutex is being held
+@tparam		dict_locked	whether dict_sys.lock() was called
 @param[in,out]	db_name		database name buffer
 @param[in,out]	tbl_name	table name buffer
 @param[out]	db_name_len	database name length
@@ -738,9 +735,9 @@ bool dict_table_t::parse_name(char (&db_name)[NAME_LEN + 1],
   char tbl_buf[MAX_TABLE_NAME_LEN + 1];
 
   if (!dict_locked)
-    mutex_enter(&dict_sys.mutex); /* protect against renaming */
+    dict_sys.freeze(); /* protect against renaming */
   else
-    ut_ad(mutex_own(&dict_sys.mutex));
+    ut_ad(dict_sys.frozen());
   const size_t db_len= name.dblen();
   ut_ad(db_len <= MAX_DATABASE_NAME_LEN);
 
@@ -761,7 +758,7 @@ bool dict_table_t::parse_name(char (&db_name)[NAME_LEN + 1],
   tbl_buf[tbl_len]= 0;
 
   if (!dict_locked)
-    mutex_exit(&dict_sys.mutex);
+    dict_sys.unfreeze();
 
   *db_name_len= filename_to_tablename(db_buf, db_name,
                                       MAX_DATABASE_NAME_LEN + 1, true);
@@ -801,13 +798,13 @@ dict_acquire_mdl_shared(dict_table_t *table,
 
   if (trylock)
   {
-    mutex_enter(&dict_sys.mutex);
+    dict_sys.freeze();
     db_len= dict_get_db_name_len(table->name.m_name);
-    mutex_exit(&dict_sys.mutex);
+    dict_sys.unfreeze();
   }
   else
   {
-    ut_ad(mutex_own(&dict_sys.mutex));
+    ut_d(dict_sys.assert_locked());
     db_len= dict_get_db_name_len(table->name.m_name);
   }
 
@@ -846,7 +843,7 @@ dict_acquire_mdl_shared(dict_table_t *table,
     return nullptr;
 
   if (!trylock)
-    mutex_exit(&dict_sys.mutex);
+    dict_sys.unlock();
   {
     MDL_request request;
     MDL_REQUEST_INIT(&request,MDL_key::TABLE, db_buf, tbl_buf, MDL_SHARED, MDL_EXPLICIT);
@@ -867,7 +864,7 @@ dict_acquire_mdl_shared(dict_table_t *table,
   }
 
   if (!trylock)
-    mutex_enter(&dict_sys.mutex);
+    dict_sys.lock();
   else if (!*mdl)
     return nullptr;
 
@@ -940,10 +937,10 @@ dict_table_open_on_id(table_id_t table_id, bool dict_locked,
 	ut_ad(!dict_locked || !thd);
 
 	if (!dict_locked) {
-		mutex_enter(&dict_sys.mutex);
+		dict_sys.lock();
 	}
 
-	ut_ad(mutex_own(&dict_sys.mutex));
+	ut_d(dict_sys.assert_locked());
 
 	dict_table_t* table = dict_table_open_on_id_low(
 		table_id,
@@ -963,7 +960,7 @@ dict_table_open_on_id(table_id_t table_id, bool dict_locked,
 				table, thd, mdl, table_op);
 		}
 
-		dict_table_try_drop_aborted_and_mutex_exit(
+		dict_table_try_drop_aborted_and_unlock(
 			table, table_op == DICT_TABLE_OP_DROP_ORPHAN);
 	}
 
@@ -1029,8 +1026,6 @@ void dict_sys_t::create()
   UT_LIST_INIT(table_LRU, &dict_table_t::table_LRU);
   UT_LIST_INIT(table_non_LRU, &dict_table_t::table_LRU);
 
-  mutex_create(LATCH_ID_DICT_SYS, &mutex);
-
   const ulint hash_size = buf_pool_get_curr_size()
     / (DICT_POOL_PER_TABLE_HASH * UNIV_WORD_SIZE);
 
@@ -1085,11 +1080,11 @@ dict_table_open_on_name(
 	DBUG_PRINT("dict_table_open_on_name", ("table: '%s'", table_name));
 
 	if (!dict_locked) {
-		mutex_enter(&dict_sys.mutex);
+		dict_sys.lock();
 	}
 
 	ut_ad(table_name);
-	ut_ad(mutex_own(&dict_sys.mutex));
+	ut_d(dict_sys.assert_locked());
 
 	table = dict_table_check_if_in_cache_low(table_name);
 
@@ -1113,7 +1108,7 @@ dict_table_open_on_name(
 					<< " is corrupted. Please "
 					"drop the table and recreate.";
 				if (!dict_locked) {
-					mutex_exit(&dict_sys.mutex);
+					dict_sys.unlock();
 				}
 
 				DBUG_RETURN(NULL);
@@ -1122,7 +1117,7 @@ dict_table_open_on_name(
 			dict_sys.acquire(table);
 
 			if (!dict_locked) {
-				mutex_exit(&dict_sys.mutex);
+				dict_sys.unlock();
 			}
 
 			DBUG_RETURN(table);
@@ -1135,7 +1130,7 @@ dict_table_open_on_name(
 	ut_ad(dict_lru_validate());
 
 	if (!dict_locked) {
-		dict_table_try_drop_aborted_and_mutex_exit(table, try_drop);
+		dict_table_try_drop_aborted_and_unlock(table, try_drop);
 	}
 
 	DBUG_RETURN(table);
@@ -1509,7 +1504,7 @@ dict_table_rename_in_cache(
 	char		old_name[MAX_FULL_NAME_LEN + 1];
 	os_file_type_t	ftype;
 
-	ut_ad(mutex_own(&dict_sys.mutex));
+	ut_d(dict_sys.assert_locked());
 
 	/* store the old/current name to an automatic variable */
 	ut_a(strlen(table->name.m_name) < sizeof old_name);
@@ -1890,7 +1885,7 @@ dict_table_change_id_in_cache(
 	dict_table_t*	table,	/*!< in/out: table object already in cache */
 	table_id_t	new_id)	/*!< in: new id to set */
 {
-	ut_ad(mutex_own(&dict_sys.mutex));
+	ut_d(dict_sys.assert_locked());
 	ut_ad(table->magic_n == DICT_TABLE_MAGIC_N);
 	ut_ad(!table->is_temporary());
 
@@ -2049,7 +2044,7 @@ dict_index_add_to_cache(
 	ulint		n_ord;
 	ulint		i;
 
-	ut_ad(mutex_own(&dict_sys.mutex));
+	ut_d(dict_sys.assert_locked());
 	ut_ad(index->n_def == index->n_fields);
 	ut_ad(index->magic_n == DICT_INDEX_MAGIC_N);
 	ut_ad(!dict_index_is_online_ddl(index));
@@ -2180,7 +2175,7 @@ dict_index_remove_from_cache_low(
 	ut_ad(table && index);
 	ut_ad(table->magic_n == DICT_TABLE_MAGIC_N);
 	ut_ad(index->magic_n == DICT_INDEX_MAGIC_N);
-	ut_ad(mutex_own(&dict_sys.mutex));
+	ut_d(dict_sys.assert_locked());
 	ut_ad(table->id);
 #ifdef BTR_CUR_HASH_ADAPT
 	ut_ad(!index->freed());
@@ -2259,7 +2254,7 @@ dict_index_find_cols(
 
 	const dict_table_t* table = index->table;
 	ut_ad(table->magic_n == DICT_TABLE_MAGIC_N);
-	ut_ad(mutex_own(&dict_sys.mutex));
+	ut_d(dict_sys.assert_locked());
 
 	for (ulint i = 0; i < index->n_fields; i++) {
 		ulint		j;
@@ -2528,7 +2523,7 @@ dict_index_build_internal_clust(
 	ut_ad(dict_index_is_clust(index));
 	ut_ad(!dict_index_is_ibuf(index));
 
-	ut_ad(mutex_own(&dict_sys.mutex));
+	ut_d(dict_sys.assert_locked());
 
 	/* Create a new index object with certainly enough fields */
 	new_index = dict_mem_index_create(index->table, index->name,
@@ -2683,7 +2678,7 @@ dict_index_build_internal_non_clust(
 	ut_ad(table && index);
 	ut_ad(!dict_index_is_clust(index));
 	ut_ad(!dict_index_is_ibuf(index));
-	ut_ad(mutex_own(&dict_sys.mutex));
+	ut_d(dict_sys.assert_locked());
 
 	/* The clustered index should be the first in the list of indexes */
 	clust_index = UT_LIST_GET_FIRST(table->indexes);
@@ -2777,7 +2772,7 @@ dict_index_build_internal_fts(
 	dict_index_t*	new_index;
 
 	ut_ad(index->type == DICT_FTS);
-	ut_ad(mutex_own(&dict_sys.mutex));
+	ut_d(dict_sys.assert_locked());
 
 	/* Create a new index */
 	new_index = dict_mem_index_create(index->table, index->name,
@@ -2829,7 +2824,7 @@ dict_foreign_remove_from_cache(
 /*===========================*/
 	dict_foreign_t*	foreign)	/*!< in, own: foreign constraint */
 {
-	ut_ad(mutex_own(&dict_sys.mutex));
+	ut_d(dict_sys.assert_locked());
 	ut_a(foreign);
 
 	if (foreign->referenced_table != NULL) {
@@ -2854,7 +2849,7 @@ dict_foreign_find(
 	dict_table_t*	table,		/*!< in: table object */
 	dict_foreign_t*	foreign)	/*!< in: foreign constraint */
 {
-	ut_ad(mutex_own(&dict_sys.mutex));
+	ut_d(dict_sys.assert_locked());
 
 	ut_ad(dict_foreign_set_validate(table->foreign_set));
 	ut_ad(dict_foreign_set_validate(table->referenced_set));
@@ -2908,7 +2903,7 @@ dict_foreign_find_index(
 					/*!< out: index where error
 					happened */
 {
-	ut_ad(mutex_own(&dict_sys.mutex));
+	ut_d(dict_sys.assert_locked());
 
 	if (error) {
 		*error = FK_INDEX_NOT_FOUND;
@@ -3006,7 +3001,7 @@ dict_foreign_add_to_cache(
 	DBUG_ENTER("dict_foreign_add_to_cache");
 	DBUG_PRINT("dict_foreign_add_to_cache", ("id: %s", foreign->id));
 
-	ut_ad(mutex_own(&dict_sys.mutex));
+	ut_d(dict_sys.assert_locked());
 
 	for_table = dict_table_check_if_in_cache_low(
 		foreign->foreign_table_name_lookup);
@@ -3651,7 +3646,7 @@ dict_foreign_parse_drop_constraints(
 
 	ptr = str;
 
-	ut_ad(mutex_own(&dict_sys.mutex));
+	ut_d(dict_sys.assert_locked());
 loop:
 	ptr = dict_scan_to(ptr, "DROP");
 
@@ -3753,14 +3748,13 @@ dict_foreign_parse_drop_constraints(
 
 /**********************************************************************//**
 Returns an index object if it is found in the dictionary cache.
-Assumes that dict_sys.mutex is already being held.
 @return index, NULL if not found */
 dict_index_t*
 dict_index_get_if_in_cache_low(
 /*===========================*/
 	index_id_t	index_id)	/*!< in: index id */
 {
-	ut_ad(mutex_own(&dict_sys.mutex));
+	ut_ad(dict_sys.frozen());
 
 	return(dict_index_find_on_id_low(index_id));
 }
@@ -3780,11 +3774,11 @@ dict_index_get_if_in_cache(
 		return(NULL);
 	}
 
-	mutex_enter(&dict_sys.mutex);
+	dict_sys.freeze();
 
 	index = dict_index_get_if_in_cache_low(index_id);
 
-	mutex_exit(&dict_sys.mutex);
+	dict_sys.unfreeze();
 
 	return(index);
 }
@@ -4065,7 +4059,7 @@ dict_print_info_on_foreign_keys(
 	dict_foreign_t*	foreign;
 	std::string 	str;
 
-	mutex_enter(&dict_sys.mutex);
+	dict_sys.freeze();
 
 	for (dict_foreign_set::iterator it = table->foreign_set.begin();
 	     it != table->foreign_set.end();
@@ -4132,7 +4126,7 @@ dict_print_info_on_foreign_keys(
 		}
 	}
 
-	mutex_exit(&dict_sys.mutex);
+	dict_sys.unfreeze();
 	return str;
 }
 
@@ -4230,7 +4224,7 @@ dict_set_corrupted(
 		row_mysql_lock_data_dictionary(trx);
 	}
 
-	ut_ad(mutex_own(&dict_sys.mutex));
+	ut_d(dict_sys.assert_locked());
 	ut_ad(!dict_table_is_comp(dict_sys.sys_tables));
 	ut_ad(!dict_table_is_comp(dict_sys.sys_indexes));
 	ut_ad(!sync_check_iterate(dict_sync_check()));
@@ -4320,7 +4314,7 @@ dict_set_corrupted_index_cache_only(
 {
 	ut_ad(index != NULL);
 	ut_ad(index->table != NULL);
-	ut_ad(mutex_own(&dict_sys.mutex));
+	ut_d(dict_sys.assert_locked());
 	ut_ad(!dict_table_is_comp(dict_sys.sys_tables));
 	ut_ad(!dict_table_is_comp(dict_sys.sys_indexes));
 
@@ -4354,7 +4348,7 @@ dict_index_set_merge_threshold(
 	ut_ad(!dict_table_is_comp(dict_sys.sys_tables));
 	ut_ad(!dict_table_is_comp(dict_sys.sys_indexes));
 
-	dict_sys_lock();
+	dict_sys.lock();
 
 	heap = mem_heap_create(sizeof(dtuple_t) + 2 * (sizeof(dfield_t)
 			       + sizeof(que_fork_t) + sizeof(upd_node_t)
@@ -4399,7 +4393,7 @@ dict_index_set_merge_threshold(
 	mtr_commit(&mtr);
 	mem_heap_free(heap);
 
-	dict_sys_unlock();
+	dict_sys.unlock();
 }
 
 #ifdef UNIV_DEBUG
@@ -4431,14 +4425,14 @@ void
 dict_set_merge_threshold_all_debug(
 	uint	merge_threshold_all)
 {
-	mutex_enter(&dict_sys.mutex);
+	dict_sys.lock();
 
 	dict_set_merge_threshold_list_debug(
 		&dict_sys.table_LRU, merge_threshold_all);
 	dict_set_merge_threshold_list_debug(
 		&dict_sys.table_non_LRU, merge_threshold_all);
 
-	mutex_exit(&dict_sys.mutex);
+	dict_sys.unlock();
 }
 
 #endif /* UNIV_DEBUG */
@@ -4556,7 +4550,7 @@ dict_table_check_for_dup_indexes(
 	const dict_index_t*	index1;
 	const dict_index_t*	index2;
 
-	ut_ad(mutex_own(&dict_sys.mutex));
+	ut_ad(dict_sys.frozen());
 
 	/* The primary index _must_ exist */
 	ut_a(UT_LIST_GET_LEN(table->indexes) > 0);
@@ -4614,7 +4608,6 @@ dict_table_check_for_dup_indexes(
 Checks whether a table exists and whether it has the given structure.
 The table must have the same number of columns with the same names and
 types. The order of the columns does not matter.
-The caller must own the dictionary mutex.
 dict_table_schema_check() @{
 @return DB_SUCCESS if the table exists and contains the necessary columns */
 dberr_t
@@ -4633,7 +4626,7 @@ dict_table_schema_check(
 	dict_table_t*	table;
 	ulint		i;
 
-	ut_ad(mutex_own(&dict_sys.mutex));
+	ut_d(dict_sys.assert_locked());
 
 	table = dict_table_get_low(req_schema->table_name);
 
@@ -4882,7 +4875,7 @@ void dict_sys_t::resize()
 {
   ut_ad(this == &dict_sys);
   ut_ad(is_initialised());
-  mutex_enter(&mutex);
+  lock();
 
   /* all table entries are in table_LRU and table_non_LRU lists */
   table_hash.free();
@@ -4920,7 +4913,7 @@ void dict_sys_t::resize()
     HASH_INSERT(dict_table_t, id_hash, id_hash, id_fold, table);
   }
 
-  mutex_exit(&mutex);
+  unlock();
 }
 
 /** Close the data dictionary cache on shutdown. */
@@ -4929,7 +4922,7 @@ void dict_sys_t::close()
   ut_ad(this == &dict_sys);
   if (!is_initialised()) return;
 
-  mutex_enter(&mutex);
+  lock();
 
   /* Free the hash elements. We don't remove them from the table
   because we are going to destroy the table anyway. */
@@ -4947,8 +4940,7 @@ void dict_sys_t::close()
   /* No temporary tables should exist at this point. */
   temp_id_hash.free();
 
-  mutex_exit(&mutex);
-  mutex_free(&mutex);
+  unlock();
   latch.destroy();
 
   mutex_free(&dict_foreign_err_mutex);
@@ -4973,7 +4965,7 @@ dict_lru_validate(void)
 {
 	dict_table_t*	table;
 
-	ut_ad(mutex_own(&dict_sys.mutex));
+	ut_ad(dict_sys.frozen());
 
 	for (table = UT_LIST_GET_FIRST(dict_sys.table_LRU);
 	     table != NULL;
diff --git a/storage/innobase/dict/dict0load.cc b/storage/innobase/dict/dict0load.cc
index 753bcf74967..59791249990 100644
--- a/storage/innobase/dict/dict0load.cc
+++ b/storage/innobase/dict/dict0load.cc
@@ -220,7 +220,7 @@ dict_get_first_table_name_in_db(
 	ulint		len;
 	mtr_t		mtr;
 
-	ut_ad(mutex_own(&dict_sys.mutex));
+	ut_d(dict_sys.assert_locked());
 
 	heap = mem_heap_create(1000);
 
@@ -797,7 +797,7 @@ dict_get_first_path(
 	char*		filepath = NULL;
 	mem_heap_t*	heap = mem_heap_create(1024);
 
-	ut_ad(mutex_own(&dict_sys.mutex));
+	ut_d(dict_sys.assert_locked());
 
 	mtr_start(&mtr);
 
@@ -976,7 +976,7 @@ dict_sys_tables_rec_check(
 	const byte*	field;
 	ulint		len;
 
-	ut_ad(mutex_own(&dict_sys.mutex));
+	ut_d(dict_sys.assert_locked());
 
 	if (rec_get_deleted_flag(rec, 0)) {
 		return("delete-marked record in SYS_TABLES");
@@ -1476,7 +1476,7 @@ void dict_check_tablespaces_and_store_max_id()
 
 	DBUG_ENTER("dict_check_tablespaces_and_store_max_id");
 
-	dict_sys_lock();
+	dict_sys.lock();
 
 	/* Initialize the max space_id from sys header */
 	mtr.start();
@@ -1493,7 +1493,7 @@ void dict_check_tablespaces_and_store_max_id()
 	max_space_id = dict_check_sys_tables();
 	fil_set_max_space_id_if_bigger(max_space_id);
 
-	dict_sys_unlock();
+	dict_sys.unlock();
 
 	DBUG_VOID_RETURN;
 }
@@ -1776,7 +1776,7 @@ dict_load_columns(
 	mtr_t		mtr;
 	ulint		n_skipped = 0;
 
-	ut_ad(mutex_own(&dict_sys.mutex));
+	ut_d(dict_sys.assert_locked());
 
 	mtr_start(&mtr);
 
@@ -1892,7 +1892,7 @@ dict_load_virtual_one_col(
 	mtr_t		mtr;
 	ulint		skipped = 0;
 
-	ut_ad(mutex_own(&dict_sys.mutex));
+	ut_d(dict_sys.assert_locked());
 
 	if (v_col->num_base == 0) {
 		return;
@@ -2125,7 +2125,7 @@ dict_load_fields(
 	mtr_t		mtr;
 	dberr_t		error;
 
-	ut_ad(mutex_own(&dict_sys.mutex));
+	ut_d(dict_sys.assert_locked());
 
 	mtr_start(&mtr);
 
@@ -2356,7 +2356,7 @@ dict_load_indexes(
 	mtr_t		mtr;
 	dberr_t		error = DB_SUCCESS;
 
-	ut_ad(mutex_own(&dict_sys.mutex));
+	ut_d(dict_sys.assert_locked());
 
 	mtr_start(&mtr);
 
@@ -2650,7 +2650,7 @@ dict_save_data_dir_path(
 	dict_table_t*	table,		/*!< in/out: table */
 	const char*	filepath)	/*!< in: filepath of tablespace */
 {
-	ut_ad(mutex_own(&dict_sys.mutex));
+	ut_d(dict_sys.assert_locked());
 	ut_a(DICT_TF_HAS_DATA_DIR(table->flags));
 
 	ut_a(!table->data_dir_path);
@@ -2674,23 +2674,22 @@ dict_save_data_dir_path(
 	}
 }
 
-/** Make sure the data_dir_path is saved in dict_table_t if DATA DIRECTORY
-was used. Try to read it from the fil_system first, then from SYS_DATAFILES.
-@param[in]	table		Table object
-@param[in]	dict_mutex_own	true if dict_sys.mutex is owned already */
-void
-dict_get_and_save_data_dir_path(
-	dict_table_t*	table,
-	bool		dict_mutex_own)
+/** Make sure the data_file_name is saved in dict_table_t if needed.
+Try to read it from the fil_system first, then from SYS_DATAFILES.
+@param[in,out]	table		Table object
+@param[in]	dict_locked	whether dict_sys.lock() was called already */
+void dict_get_and_save_data_dir_path(dict_table_t* table, bool dict_locked)
 {
 	ut_ad(!table->is_temporary());
 	ut_ad(!table->space || table->space->id == table->space_id);
 
 	if (!table->data_dir_path && table->space_id && table->space) {
-		if (!dict_mutex_own) {
-			dict_mutex_enter_for_mysql();
+		if (!dict_locked) {
+			dict_sys.lock();
 		}
 
+		ut_d(dict_sys.assert_locked());
+
 		table->flags |= 1 << DICT_TF_POS_DATA_DIR
 			& ((1U << DICT_TF_BITS) - 1);
 		dict_save_data_dir_path(table,
@@ -2706,8 +2705,8 @@ dict_get_and_save_data_dir_path(
 				& ((1U << DICT_TF_BITS) - 1);
 		}
 
-		if (!dict_mutex_own) {
-			dict_mutex_exit_for_mysql();
+		if (!dict_locked) {
+			dict_sys.unlock();
 		}
 	}
 }
@@ -2731,7 +2730,7 @@ dict_table_t* dict_load_table(const char* name, dict_err_ignore_t ignore_err)
 	DBUG_ENTER("dict_load_table");
 	DBUG_PRINT("dict_load_table", ("loading table: '%s'", name));
 
-	ut_ad(mutex_own(&dict_sys.mutex));
+	ut_d(dict_sys.assert_locked());
 
 	result = dict_table_check_if_in_cache_low(name);
 
@@ -2865,7 +2864,7 @@ dict_load_table_one(
 	DBUG_ENTER("dict_load_table_one");
 	DBUG_PRINT("dict_load_table_one", ("table: %s", name.m_name));
 
-	ut_ad(mutex_own(&dict_sys.mutex));
+	ut_d(dict_sys.assert_locked());
 
 	heap = mem_heap_create(32000);
 
@@ -3111,7 +3110,7 @@ dict_load_table_on_id(
 	dict_table_t*	table;
 	mtr_t		mtr;
 
-	ut_ad(mutex_own(&dict_sys.mutex));
+	ut_d(dict_sys.assert_locked());
 
 	table = NULL;
 
@@ -3196,7 +3195,7 @@ dict_load_sys_table(
 {
 	mem_heap_t*	heap;
 
-	ut_ad(mutex_own(&dict_sys.mutex));
+	ut_d(dict_sys.assert_locked());
 
 	heap = mem_heap_create(1000);
 
@@ -3233,7 +3232,7 @@ dict_load_foreign_cols(
 	mtr_t		mtr;
 	size_t		id_len;
 
-	ut_ad(mutex_own(&dict_sys.mutex));
+	ut_d(dict_sys.assert_locked());
 
 	id_len = strlen(foreign->id);
 
@@ -3379,7 +3378,7 @@ dict_load_foreign(
 	DBUG_PRINT("dict_load_foreign",
 		   ("id: '%s', check_recursive: %d", id, check_recursive));
 
-	ut_ad(mutex_own(&dict_sys.mutex));
+	ut_d(dict_sys.assert_locked());
 
 	id_len = strlen(id);
 
@@ -3554,7 +3553,7 @@ dict_load_foreigns(
 
 	DBUG_ENTER("dict_load_foreigns");
 
-	ut_ad(mutex_own(&dict_sys.mutex));
+	ut_d(dict_sys.assert_locked());
 
 	sys_foreign = dict_table_get_low("SYS_FOREIGN");
 
@@ -3713,7 +3712,7 @@ dict_load_table_id_on_index_id(
 	bool		found = false;
 	mtr_t		mtr;
 
-	ut_ad(mutex_own(&dict_sys.mutex));
+	ut_d(dict_sys.assert_locked());
 
 	/* NOTE that the operation of this function is protected by
 	the dictionary mutex, and therefore no deadlocks can occur
diff --git a/storage/innobase/dict/dict0stats.cc b/storage/innobase/dict/dict0stats.cc
index 07d5230b3f7..2797c105cc0 100644
--- a/storage/innobase/dict/dict0stats.cc
+++ b/storage/innobase/dict/dict0stats.cc
@@ -163,12 +163,7 @@ dict_stats_should_ignore_index(
 Checks whether the persistent statistics storage exists and that all
 tables have the proper structure.
 @return true if exists and all tables are ok */
-static
-bool
-dict_stats_persistent_storage_check(
-/*================================*/
-	bool	caller_has_dict_sys_mutex)	/*!< in: true if the caller
-						owns dict_sys.mutex */
+static bool dict_stats_persistent_storage_check(bool dict_already_locked)
 {
 	/* definition for the table TABLE_STATS_NAME */
 	dict_col_meta_t	table_stats_columns[] = {
@@ -235,11 +230,11 @@ dict_stats_persistent_storage_check(
 	char		errstr[512];
 	dberr_t		ret;
 
-	if (!caller_has_dict_sys_mutex) {
-		mutex_enter(&dict_sys.mutex);
+	if (!dict_already_locked) {
+		dict_sys.lock();
 	}
 
-	ut_ad(mutex_own(&dict_sys.mutex));
+	ut_d(dict_sys.assert_locked());
 
 	/* first check table_stats */
 	ret = dict_table_schema_check(&table_stats_schema, errstr,
@@ -250,8 +245,8 @@ dict_stats_persistent_storage_check(
 					      sizeof(errstr));
 	}
 
-	if (!caller_has_dict_sys_mutex) {
-		mutex_exit(&dict_sys.mutex);
+	if (!dict_already_locked) {
+		dict_sys.unlock();
 	}
 
 	if (ret != DB_SUCCESS && ret != DB_STATS_DO_NOT_EXIST) {
@@ -511,7 +506,7 @@ dict_stats_empty_index(
 {
 	ut_ad(!(index->type & DICT_FTS));
 	ut_ad(!dict_index_is_ibuf(index));
-	ut_ad(mutex_own(&dict_sys.mutex));
+	ut_d(dict_sys.assert_locked());
 
 	ulint	n_uniq = index->n_uniq;
 
@@ -541,7 +536,7 @@ dict_stats_empty_table(
 	bool		empty_defrag_stats)
 				/*!< in: whether to empty defrag stats */
 {
-	mutex_enter(&dict_sys.mutex);
+	dict_sys.lock();
 
 	/* Zero the stats members */
 	table->stat_n_rows = 0;
@@ -567,7 +562,7 @@ dict_stats_empty_table(
 	}
 
 	table->stat_initialized = TRUE;
-	mutex_exit(&dict_sys.mutex);
+	dict_sys.unlock();
 }
 
 /*********************************************************************//**
@@ -666,7 +661,7 @@ dict_stats_copy(
                                              to have the same statistics as if
                                              the table was empty */
 {
-	ut_ad(mutex_own(&dict_sys.mutex));
+	ut_d(dict_sys.assert_locked());
 
 	dst->stats_last_recalc = src->stats_last_recalc;
 	dst->stat_n_rows = src->stat_n_rows;
@@ -783,7 +778,7 @@ dict_table_t*
 dict_stats_snapshot_create(
 	dict_table_t*	table)
 {
-	mutex_enter(&dict_sys.mutex);
+	dict_sys.lock();
 
 	dict_stats_assert_initialized(table);
 
@@ -798,8 +793,7 @@ dict_stats_snapshot_create(
 	t->stats_sample_pages = table->stats_sample_pages;
 	t->stats_bg_flag = table->stats_bg_flag;
 
-	mutex_exit(&dict_sys.mutex);
-
+	dict_sys.unlock();
 	return(t);
 }
 
@@ -837,14 +831,14 @@ dict_stats_update_transient_for_index(
 		Initialize some bogus index cardinality
 		statistics, so that the data can be queried in
 		various means, also via secondary indexes. */
-		mutex_enter(&dict_sys.mutex);
+dummy_empty:
+		dict_sys.lock();
 		dict_stats_empty_index(index, false);
-		mutex_exit(&dict_sys.mutex);
+		dict_sys.unlock();
+		return;
 #if defined UNIV_DEBUG || defined UNIV_IBUF_DEBUG
 	} else if (ibuf_debug && !dict_index_is_clust(index)) {
-		mutex_enter(&dict_sys.mutex);
-		dict_stats_empty_index(index, false);
-		mutex_exit(&dict_sys.mutex);
+		goto dummy_empty;
 #endif /* UNIV_DEBUG || UNIV_IBUF_DEBUG */
 	} else {
 		mtr_t	mtr;
@@ -865,10 +859,7 @@ dict_stats_update_transient_for_index(
 
 		switch (size) {
 		case ULINT_UNDEFINED:
-			mutex_enter(&dict_sys.mutex);
-			dict_stats_empty_index(index, false);
-			mutex_exit(&dict_sys.mutex);
-			return;
+			goto dummy_empty;
 		case 0:
 			/* The root node of the tree is a leaf */
 			size = 1;
@@ -884,8 +875,7 @@ dict_stats_update_transient_for_index(
 					index);
 
 			if (!stats.empty()) {
-				ut_ad(!mutex_own(&dict_sys.mutex));
-				mutex_enter(&dict_sys.mutex);
+				dict_sys.lock();
 				for (size_t i = 0; i < stats.size(); ++i) {
 					index->stat_n_diff_key_vals[i]
 						= stats[i].n_diff_key_vals;
@@ -894,7 +884,7 @@ dict_stats_update_transient_for_index(
 					index->stat_n_non_null_key_vals[i]
 						= stats[i].n_non_null_key_vals;
 				}
-				mutex_exit(&dict_sys.mutex);
+				dict_sys.unlock();
 			}
 		}
 	}
@@ -912,8 +902,6 @@ dict_stats_update_transient(
 /*========================*/
 	dict_table_t*	table)	/*!< in/out: table */
 {
-	ut_ad(!mutex_own(&dict_sys.mutex));
-
 	dict_index_t*	index;
 	ulint		sum_of_index_sizes	= 0;
 
@@ -945,9 +933,9 @@ dict_stats_update_transient(
 
 		if (dict_stats_should_ignore_index(index)
 		    || !index->is_readable()) {
-			mutex_enter(&dict_sys.mutex);
+			dict_sys.lock();
 			dict_stats_empty_index(index, false);
-			mutex_exit(&dict_sys.mutex);
+			dict_sys.unlock();
 			continue;
 		}
 
@@ -956,7 +944,7 @@ dict_stats_update_transient(
 		sum_of_index_sizes += index->stat_index_size;
 	}
 
-	mutex_enter(&dict_sys.mutex);
+	dict_sys.lock();
 
 	index = dict_table_get_first_index(table);
 
@@ -974,7 +962,7 @@ dict_stats_update_transient(
 
 	table->stat_initialized = TRUE;
 
-	mutex_exit(&dict_sys.mutex);
+	dict_sys.unlock();
 }
 
 /* @{ Pseudo code about the relation between the following functions
@@ -1932,7 +1920,6 @@ static index_stats_t dict_stats_analyze_index(dict_index_t* index)
 	DBUG_PRINT("info", ("index: %s, online status: %d", index->name(),
 			    dict_index_get_online_status(index)));
 
-	ut_ad(!mutex_own(&dict_sys.mutex)); // because this function is slow
 	ut_ad(index->table->get_ref_count());
 
 	/* Disable update statistic for Rtree */
@@ -2004,14 +1991,14 @@ static index_stats_t dict_stats_analyze_index(dict_index_t* index)
 
 		mtr.commit();
 
-		mutex_enter(&dict_sys.mutex);
+		dict_sys.lock();
 		for (ulint i = 0; i < n_uniq; i++) {
 			result.stats[i].n_diff_key_vals = index->stat_n_diff_key_vals[i];
 			result.stats[i].n_sample_sizes = total_pages;
 			result.stats[i].n_non_null_key_vals = index->stat_n_non_null_key_vals[i];
 		}
 		result.n_leaf_pages = index->stat_n_leaf_pages;
-		mutex_exit(&dict_sys.mutex);
+		dict_sys.unlock();
 
 		DBUG_RETURN(result);
 	}
@@ -2245,13 +2232,13 @@ dict_stats_update_persistent(
 	}
 
 	ut_ad(!dict_index_is_ibuf(index));
-	mutex_enter(&dict_sys.mutex);
+	dict_sys.lock();
 	dict_stats_empty_index(index, false);
-	mutex_exit(&dict_sys.mutex);
+	dict_sys.unlock();
 
 	index_stats_t stats = dict_stats_analyze_index(index);
 
-	mutex_enter(&dict_sys.mutex);
+	dict_sys.lock();
 	index->stat_index_size = stats.index_size;
 	index->stat_n_leaf_pages = stats.n_leaf_pages;
 	for (size_t i = 0; i < stats.stats.size(); ++i) {
@@ -2287,9 +2274,9 @@ dict_stats_update_persistent(
 		}
 
 		if (!(table->stats_bg_flag & BG_STAT_SHOULD_QUIT)) {
-			mutex_exit(&dict_sys.mutex);
+			dict_sys.unlock();
 			stats = dict_stats_analyze_index(index);
-			mutex_enter(&dict_sys.mutex);
+			dict_sys.lock();
 
 			index->stat_index_size = stats.index_size;
 			index->stat_n_leaf_pages = stats.n_leaf_pages;
@@ -2315,7 +2302,7 @@ dict_stats_update_persistent(
 
 	dict_stats_assert_initialized(table);
 
-	mutex_exit(&dict_sys.mutex);
+	dict_sys.unlock();
 
 	return(DB_SUCCESS);
 }
@@ -2482,7 +2469,7 @@ dict_stats_save(
 		     table_utf8, sizeof(table_utf8));
 
 	const time_t now = time(NULL);
-	dict_sys_lock();
+	dict_sys.lock();
 
 	pinfo = pars_info_create();
 
@@ -2521,7 +2508,7 @@ dict_stats_save(
 		ib::error() << "Cannot save table statistics for table "
 			<< table->name << ": " << ret;
 func_exit:
-		dict_sys_unlock();
+		dict_sys.unlock();
 		dict_stats_snapshot_free(table);
 		return ret;
 	}
@@ -2991,8 +2978,6 @@ dict_stats_fetch_from_ps(
 	char		db_utf8[MAX_DB_UTF8_LEN];
 	char		table_utf8[MAX_TABLE_UTF8_LEN];
 
-	ut_ad(!mutex_own(&dict_sys.mutex));
-
 	/* Initialize all stats to dummy values before fetching because if
 	the persistent storage contains incomplete stats (e.g. missing stats
 	for some index) then we would end up with (partially) uninitialized
@@ -3127,13 +3112,11 @@ dict_stats_update_for_index(
 {
 	DBUG_ENTER("dict_stats_update_for_index");
 
-	ut_ad(!mutex_own(&dict_sys.mutex));
-
 	if (dict_stats_is_persistent_enabled(index->table)) {
 
 		if (dict_stats_persistent_storage_check(false)) {
 			index_stats_t stats = dict_stats_analyze_index(index);
-			mutex_enter(&dict_sys.mutex);
+			dict_sys.lock();
 			index->stat_index_size = stats.index_size;
 			index->stat_n_leaf_pages = stats.n_leaf_pages;
 			for (size_t i = 0; i < stats.stats.size(); ++i) {
@@ -3146,7 +3129,7 @@ dict_stats_update_for_index(
 			}
 			index->table->stat_sum_of_other_index_sizes
 				+= index->stat_index_size;
-			mutex_exit(&dict_sys.mutex);
+			dict_sys.unlock();
 
 			dict_stats_save(index->table, &index->id);
 			DBUG_VOID_RETURN;
@@ -3187,8 +3170,6 @@ dict_stats_update(
 					the persistent statistics
 					storage */
 {
-	ut_ad(!mutex_own(&dict_sys.mutex));
-
 	if (!table->is_readable()) {
 		return (dict_stats_report_error(table));
 	} else if (srv_force_recovery > SRV_FORCE_NO_IBUF_MERGE) {
@@ -3321,8 +3302,7 @@ dict_stats_update(
 
 		switch (err) {
 		case DB_SUCCESS:
-
-			mutex_enter(&dict_sys.mutex);
+			dict_sys.lock();
 
 			/* Pass reset_ignored_indexes=true as parameter
 			to dict_stats_copy. This will cause statictics
@@ -3331,7 +3311,7 @@ dict_stats_update(
 
 			dict_stats_assert_initialized(table);
 
-			mutex_exit(&dict_sys.mutex);
+			dict_sys.unlock();
 
 			dict_stats_table_clone_free(t);
 
@@ -3417,8 +3397,6 @@ dict_stats_drop_index(
 	pars_info_t*	pinfo;
 	dberr_t		ret;
 
-	ut_ad(!mutex_own(&dict_sys.mutex));
-
 	/* skip indexes whose table names do not contain a database name
 	e.g. if we are dropping an index from SYS_TABLES */
 	if (strchr(db_and_table, '/') == NULL) {
@@ -3437,7 +3415,7 @@ dict_stats_drop_index(
 
 	pars_info_add_str_literal(pinfo, "index_name", iname);
 
-	dict_sys_lock();
+	dict_sys.lock();
 
 	ret = dict_stats_exec_sql(
 		pinfo,
@@ -3449,7 +3427,7 @@ dict_stats_drop_index(
 		"index_name = :index_name;\n"
 		"END;\n", NULL);
 
-	dict_sys_unlock();
+	dict_sys.unlock();
 
 	if (ret == DB_STATS_DO_NOT_EXIST) {
 		ret = DB_SUCCESS;
@@ -3744,7 +3722,7 @@ dict_stats_rename_table(
 	dict_fs2utf8(new_name, new_db_utf8, sizeof(new_db_utf8),
 		     new_table_utf8, sizeof(new_table_utf8));
 
-	dict_sys_lock();
+	dict_sys.lock();
 
 	ulint	n_attempts = 0;
 	do {
@@ -3764,9 +3742,9 @@ dict_stats_rename_table(
 		}
 
 		if (ret != DB_SUCCESS) {
-			dict_sys_unlock();
+			dict_sys.unlock();
 			os_thread_sleep(200000 /* 0.2 sec */);
-			dict_sys_lock();
+			dict_sys.lock();
 		}
 	} while ((ret == DB_DEADLOCK
 		  || ret == DB_DUPLICATE_KEY
@@ -3794,7 +3772,7 @@ dict_stats_rename_table(
 			 TABLE_STATS_NAME_PRINT,
 			 new_db_utf8, new_table_utf8,
 			 old_db_utf8, old_table_utf8);
-		dict_sys_unlock();
+		dict_sys.unlock();
 		return(ret);
 	}
 	/* else */
@@ -3817,16 +3795,16 @@ dict_stats_rename_table(
 		}
 
 		if (ret != DB_SUCCESS) {
-			dict_sys_unlock();
+			dict_sys.unlock();
 			os_thread_sleep(200000 /* 0.2 sec */);
-			dict_sys_lock();
+			dict_sys.lock();
 		}
 	} while ((ret == DB_DEADLOCK
 		  || ret == DB_DUPLICATE_KEY
 		  || ret == DB_LOCK_WAIT_TIMEOUT)
 		 && n_attempts < 5);
 
-	dict_sys_unlock();
+	dict_sys.unlock();
 
 	if (ret != DB_SUCCESS) {
 		snprintf(errstr, errstr_sz,
@@ -3867,10 +3845,10 @@ dict_stats_rename_index(
 	const char*		old_index_name,	/*!< in: old index name */
 	const char*		new_index_name)	/*!< in: new index name */
 {
-	dict_sys_lock();
+	dict_sys.lock();
 
 	if (!dict_stats_persistent_storage_check(true)) {
-		dict_sys_unlock();
+		dict_sys.unlock();
 		return(DB_STATS_DO_NOT_EXIST);
 	}
 
@@ -3903,7 +3881,7 @@ dict_stats_rename_index(
 		"index_name = :old_index_name;\n"
 		"END;\n", NULL);
 
-	dict_sys_unlock();
+	dict_sys.unlock();
 
 	return(ret);
 }
@@ -3953,7 +3931,7 @@ test_dict_table_schema_check()
 	/* prevent any data dictionary modifications while we are checking
 	the tables' structure */
 
-	mutex_enter(&dict_sys.mutex);
+	dict_sys.lock();
 
 	/* check that a valid table is reported as valid */
 	schema.n_cols = 7;
@@ -4029,7 +4007,7 @@ test_dict_table_schema_check()
 
 test_dict_table_schema_check_end:
 
-	mutex_exit(&dict_sys.mutex);
+	dict_sys.unlock();
 }
 /* @} */
 
diff --git a/storage/innobase/dict/dict0stats_bg.cc b/storage/innobase/dict/dict0stats_bg.cc
index 67769647624..8289d62cc29 100644
--- a/storage/innobase/dict/dict0stats_bg.cc
+++ b/storage/innobase/dict/dict0stats_bg.cc
@@ -147,7 +147,6 @@ void dict_stats_update_if_needed_func(dict_table_t *table)
 #endif
 {
 	ut_ad(table->stat_initialized);
-	ut_ad(!mutex_own(&dict_sys.mutex));
 
 	ulonglong	counter = table->stat_modified_counter++;
 	ulonglong	n_rows = dict_table_get_n_rows(table);
@@ -240,7 +239,7 @@ dict_stats_recalc_pool_del(
 	const dict_table_t*	table)	/*!< in: table to remove */
 {
 	ut_ad(!srv_read_only_mode);
-	ut_ad(mutex_own(&dict_sys.mutex));
+	ut_ad(dict_sys.frozen());
 
 	mutex_enter(&recalc_pool_mutex);
 
@@ -345,14 +344,15 @@ static bool dict_stats_process_entry_from_recalc_pool()
 
 	dict_table_t*	table;
 
-	mutex_enter(&dict_sys.mutex);
+	dict_sys.lock();
 
 	table = dict_table_open_on_id(table_id, TRUE, DICT_TABLE_OP_NORMAL);
 
 	if (table == NULL) {
 		/* table does not exist, must have been DROPped
 		after its id was enqueued */
-		mutex_exit(&dict_sys.mutex);
+no_table:
+		dict_sys.unlock();
 		goto next_table_id;
 	}
 
@@ -360,13 +360,12 @@ static bool dict_stats_process_entry_from_recalc_pool()
 
 	if (!table->is_accessible()) {
 		dict_table_close(table, TRUE, FALSE);
-		mutex_exit(&dict_sys.mutex);
-		goto next_table_id;
+		goto no_table;
 	}
 
 	table->stats_bg_flag |= BG_STAT_IN_PROGRESS;
 
-	mutex_exit(&dict_sys.mutex);
+	dict_sys.unlock();
 
 	/* time() could be expensive, the current function
 	is called once every time a table has been changed more than 10% and
@@ -391,13 +390,13 @@ static bool dict_stats_process_entry_from_recalc_pool()
 		ret = true;
 	}
 
-	mutex_enter(&dict_sys.mutex);
+	dict_sys.lock();
 
 	table->stats_bg_flag = BG_STAT_NONE;
 
 	dict_table_close(table, TRUE, FALSE);
 
-	mutex_exit(&dict_sys.mutex);
+	dict_sys.unlock();
 	return ret;
 }
 
diff --git a/storage/innobase/fil/fil0crypt.cc b/storage/innobase/fil/fil0crypt.cc
index 73f87173485..3e83cfc09c4 100644
--- a/storage/innobase/fil/fil0crypt.cc
+++ b/storage/innobase/fil/fil0crypt.cc
@@ -2379,16 +2379,16 @@ fil_space_crypt_close_tablespace(
 
 	while (cnt > 0 || flushing) {
 		mutex_exit(&crypt_data->mutex);
-		/* release dict mutex so that scrub threads can release their
-		* table references */
-		dict_mutex_exit_for_mysql();
+		/* FIXME: is this relevant? originally we wanted to
+		allow scrub threads to release their table references */
+		dict_sys.unlock();
 
 		/* wakeup throttle (all) sleepers */
 		os_event_set(fil_crypt_throttle_sleep_event);
 		os_event_set(fil_crypt_threads_event);
 
 		os_thread_sleep(20000);
-		dict_mutex_enter_for_mysql();
+		dict_sys.lock();
 		mutex_enter(&crypt_data->mutex);
 		cnt = crypt_data->rotate_state.active_threads;
 		flushing = crypt_data->rotate_state.flushing;
diff --git a/storage/innobase/fts/fts0config.cc b/storage/innobase/fts/fts0config.cc
index 9e2b40911ae..db3e5d8b620 100644
--- a/storage/innobase/fts/fts0config.cc
+++ b/storage/innobase/fts/fts0config.cc
@@ -120,9 +120,9 @@ fts_config_get_value(
 
 	error = fts_eval_sql(trx, graph);
 
-	mutex_enter(&dict_sys.mutex);
+	dict_sys.lock();
 	que_graph_free(graph);
-	mutex_exit(&dict_sys.mutex);
+	dict_sys.unlock();
 
 	return(error);
 }
diff --git a/storage/innobase/fts/fts0fts.cc b/storage/innobase/fts/fts0fts.cc
index e8664daadbc..a885a34a032 100644
--- a/storage/innobase/fts/fts0fts.cc
+++ b/storage/innobase/fts/fts0fts.cc
@@ -454,7 +454,7 @@ fts_load_user_stopword(
 	fts_stopword_t*	stopword_info)		/*!< in: Stopword info */
 {
 	if (!fts->dict_locked) {
-		mutex_enter(&dict_sys.mutex);
+		dict_sys.lock();
 	}
 
 	/* Validate the user table existence in the right format */
@@ -463,7 +463,7 @@ fts_load_user_stopword(
 	if (!stopword_info->charset) {
 cleanup:
 		if (!fts->dict_locked) {
-			mutex_exit(&dict_sys.mutex);
+			dict_sys.unlock();
 		}
 
 		return ret;
@@ -907,15 +907,13 @@ fts_que_graph_free_check_lock(
 	}
 
 	if (!has_dict) {
-		mutex_enter(&dict_sys.mutex);
+		dict_sys.lock();
 	}
 
-	ut_ad(mutex_own(&dict_sys.mutex));
-
 	que_graph_free(graph);
 
 	if (!has_dict) {
-		mutex_exit(&dict_sys.mutex);
+		dict_sys.unlock();
 	}
 }
 
@@ -6213,8 +6211,6 @@ fts_init_index(
 	fts_cache_t*    cache = table->fts->cache;
 	bool		need_init = false;
 
-	ut_ad(!mutex_own(&dict_sys.mutex));
-
 	/* First check cache->get_docs is initialized */
 	if (!has_cache_lock) {
 		mysql_mutex_lock(&cache->lock);
@@ -6278,10 +6274,10 @@ fts_init_index(
 	}
 
 	if (need_init) {
-		mutex_enter(&dict_sys.mutex);
+		dict_sys.lock();
 		/* Register the table with the optimize thread. */
 		fts_optimize_add_table(table);
-		mutex_exit(&dict_sys.mutex);
+		dict_sys.unlock();
 	}
 }
 
diff --git a/storage/innobase/fts/fts0opt.cc b/storage/innobase/fts/fts0opt.cc
index b0324bf7667..a5f711e8469 100644
--- a/storage/innobase/fts/fts0opt.cc
+++ b/storage/innobase/fts/fts0opt.cc
@@ -1005,9 +1005,7 @@ fts_table_fetch_doc_ids(
 	error = fts_eval_sql(trx, graph);
 	fts_sql_commit(trx);
 
-	mutex_enter(&dict_sys.mutex);
-	que_graph_free(graph);
-	mutex_exit(&dict_sys.mutex);
+	fts_que_graph_free(graph);
 
 	if (error == DB_SUCCESS) {
 		ib_vector_sort(doc_ids->doc_ids, fts_doc_id_cmp);
@@ -2617,8 +2615,6 @@ fts_optimize_remove_table(
 	remove->event = event;
 	msg->ptr = remove;
 
-	ut_ad(!mutex_own(&dict_sys.mutex));
-
 	add_msg(msg, true);
 
 	mutex_exit(&fts_optimize_wq->mutex);
@@ -2969,7 +2965,7 @@ fts_optimize_init(void)
 	/* Add fts tables to fts_slots which could be skipped
 	during dict_load_table_one() because fts_optimize_thread
 	wasn't even started. */
-	mutex_enter(&dict_sys.mutex);
+	dict_sys.freeze();
 	for (dict_table_t* table = UT_LIST_GET_FIRST(dict_sys.table_LRU);
 	     table != NULL;
 	     table = UT_LIST_GET_NEXT(table_LRU, table)) {
@@ -2984,7 +2980,7 @@ fts_optimize_init(void)
 		fts_optimize_new_table(table);
 		table->fts->in_queue = true;
 	}
-	mutex_exit(&dict_sys.mutex);
+	dict_sys.unfreeze();
 
 	fts_opt_shutdown_event = os_event_create(0);
 	last_check_sync_time = time(NULL);
@@ -3000,13 +2996,13 @@ fts_optimize_shutdown()
 
 	/* If there is an ongoing activity on dictionary, such as
 	srv_master_evict_from_table_cache(), wait for it */
-	dict_mutex_enter_for_mysql();
+	dict_sys.lock();
 
 	/* Tells FTS optimizer system that we are exiting from
 	optimizer thread, message send their after will not be
 	processed */
 	fts_opt_start_shutdown = true;
-	dict_mutex_exit_for_mysql();
+	dict_sys.unlock();
 
 	/* We tell the OPTIMIZE thread to switch to state done, we
 	can't delete the work queue here because the add thread needs
diff --git a/storage/innobase/fts/fts0sql.cc b/storage/innobase/fts/fts0sql.cc
index 180500f64a5..80dc570001a 100644
--- a/storage/innobase/fts/fts0sql.cc
+++ b/storage/innobase/fts/fts0sql.cc
@@ -93,7 +93,7 @@ char* fts_get_table_name_prefix(const fts_table_t* fts_table)
 	char		table_id[FTS_AUX_MIN_TABLE_ID_LENGTH];
 	const size_t table_id_len = size_t(fts_get_table_id(fts_table,
 							    table_id)) + 1;
-	mutex_enter(&dict_sys.mutex);
+	dict_sys.freeze();
 	/* Include the separator as well. */
 	const size_t dbname_len = fts_table->table->name.dblen() + 1;
 	ut_ad(dbname_len > 1);
@@ -101,7 +101,7 @@ char* fts_get_table_name_prefix(const fts_table_t* fts_table)
 	char* prefix_name = static_cast<char*>(
 		ut_malloc_nokey(prefix_name_len));
 	memcpy(prefix_name, fts_table->table->name.m_name, dbname_len);
-	mutex_exit(&dict_sys.mutex);
+	dict_sys.unfreeze();
 	memcpy(prefix_name + dbname_len, "FTS_", 4);
 	memcpy(prefix_name + dbname_len + 4, table_id, table_id_len);
 	return prefix_name;
@@ -115,15 +115,15 @@ void fts_get_table_name(const fts_table_t* fts_table, char* table_name,
 			bool dict_locked)
 {
 	if (!dict_locked) {
-		mutex_enter(&dict_sys.mutex);
+		dict_sys.freeze();
 	}
-	ut_ad(mutex_own(&dict_sys.mutex));
+	ut_ad(dict_sys.frozen());
 	/* Include the separator as well. */
 	const size_t dbname_len = fts_table->table->name.dblen() + 1;
 	ut_ad(dbname_len > 1);
 	memcpy(table_name, fts_table->table->name.m_name, dbname_len);
 	if (!dict_locked) {
-		mutex_exit(&dict_sys.mutex);
+		dict_sys.unfreeze();
 	}
 	memcpy(table_name += dbname_len, "FTS_", 4);
 	table_name += 4;
@@ -152,17 +152,15 @@ fts_parse_sql(
 		       && fts_table->table->fts->dict_locked);
 
 	if (!dict_locked) {
-		ut_ad(!mutex_own(&dict_sys.mutex));
-
 		/* The InnoDB SQL parser is not re-entrant. */
-		mutex_enter(&dict_sys.mutex);
+		dict_sys.lock();
 	}
 
 	graph = pars_sql(info, str);
 	ut_a(graph);
 
 	if (!dict_locked) {
-		mutex_exit(&dict_sys.mutex);
+		dict_sys.unlock();
 	}
 
 	ut_free(str);
@@ -182,7 +180,7 @@ fts_parse_sql_no_dict_lock(
 	char*		str;
 	que_t*		graph;
 
-	ut_ad(mutex_own(&dict_sys.mutex));
+	ut_d(dict_sys.assert_locked());
 
 	str = ut_str3cat(fts_sql_begin, sql, fts_sql_end);
 
diff --git a/storage/innobase/handler/ha_innodb.cc b/storage/innobase/handler/ha_innodb.cc
index 409d72601c9..edcf244250e 100644
--- a/storage/innobase/handler/ha_innodb.cc
+++ b/storage/innobase/handler/ha_innodb.cc
@@ -528,7 +528,6 @@ is defined */
 static PSI_mutex_info all_innodb_mutexes[] = {
 	PSI_KEY(buf_pool_mutex),
 	PSI_KEY(dict_foreign_err_mutex),
-	PSI_KEY(dict_sys_mutex),
 	PSI_KEY(recalc_pool_mutex),
 	PSI_KEY(fil_system_mutex),
 	PSI_KEY(flush_list_mutex),
@@ -5339,12 +5338,12 @@ innobase_build_v_templ(
 	ut_ad(n_v_col > 0);
 
 	if (!locked) {
-		mutex_enter(&dict_sys.mutex);
+		dict_sys.lock();
 	}
 
 	if (s_templ->vtempl) {
 		if (!locked) {
-			mutex_exit(&dict_sys.mutex);
+			dict_sys.unlock();
 		}
 		DBUG_VOID_RETURN;
 	}
@@ -5450,7 +5449,7 @@ innobase_build_v_templ(
 	}
 
 	if (!locked) {
-		mutex_exit(&dict_sys.mutex);
+		dict_sys.unlock();
 	}
 
 	s_templ->db_name = table->s->db.str;
@@ -5749,7 +5748,7 @@ ha_innobase::open(const char* name, int, uint)
 	key_used_on_scan = m_primary_key;
 
 	if (ib_table->n_v_cols) {
-		mutex_enter(&dict_sys.mutex);
+		dict_sys.lock();
 		if (ib_table->vc_templ == NULL) {
 			ib_table->vc_templ = UT_NEW_NOKEY(dict_vcol_templ_t());
 			innobase_build_v_templ(
@@ -5757,7 +5756,7 @@ ha_innobase::open(const char* name, int, uint)
 				true);
 		}
 
-		mutex_exit(&dict_sys.mutex);
+		dict_sys.unlock();
 	}
 
 	if (!check_index_consistency(table, ib_table)) {
@@ -9659,7 +9658,7 @@ wsrep_append_foreign_key(
 		foreign->referenced_table : foreign->foreign_table)) {
 		WSREP_DEBUG("pulling %s table into cache",
 			    (referenced) ? "referenced" : "foreign");
-		mutex_enter(&dict_sys.mutex);
+		dict_sys.lock();
 
 		if (referenced) {
 			foreign->referenced_table =
@@ -9689,7 +9688,7 @@ wsrep_append_foreign_key(
 						TRUE, FALSE);
 			}
 		}
-		mutex_exit(&dict_sys.mutex);
+		dict_sys.unlock();
 	}
 
 	if ( !((referenced) ?
@@ -12829,9 +12828,9 @@ create_table_info_t::create_table_update_dict()
 			DBUG_RETURN(-1);
 		}
 
-		mutex_enter(&dict_sys.mutex);
+		dict_sys.lock();
 		fts_optimize_add_table(innobase_table);
-		mutex_exit(&dict_sys.mutex);
+		dict_sys.unlock();
 	}
 
 	if (const Field* ai = m_form->found_next_number_field) {
@@ -13098,12 +13097,12 @@ ha_innobase::discard_or_import_tablespace(
 	btr_cur_instant_init(). */
 	table_id_t id = m_prebuilt->table->id;
 	ut_ad(id);
-	mutex_enter(&dict_sys.mutex);
+	dict_sys.lock();
 	dict_table_close(m_prebuilt->table, TRUE, FALSE);
 	dict_sys.remove(m_prebuilt->table);
 	m_prebuilt->table = dict_table_open_on_id(id, TRUE,
 						  DICT_TABLE_OP_NORMAL);
-	mutex_exit(&dict_sys.mutex);
+	dict_sys.unlock();
 	if (!m_prebuilt->table) {
 		err = DB_TABLE_NOT_FOUND;
 	} else {
@@ -14188,8 +14187,6 @@ ha_innobase::info_low(
 
 	DEBUG_SYNC_C("ha_innobase_info_low");
 
-	ut_ad(!mutex_own(&dict_sys.mutex));
-
 	/* If we are forcing recovery at a high level, we will suppress
 	statistics calculation on tables, because that may crash the
 	server if an index is badly corrupted. */
@@ -14248,7 +14245,7 @@ ha_innobase::info_low(
 		ulint	stat_clustered_index_size;
 		ulint	stat_sum_of_other_index_sizes;
 
-		mutex_enter(&dict_sys.mutex);
+		dict_sys.lock();
 
 		ut_a(ib_table->stat_initialized);
 
@@ -14260,7 +14257,7 @@ ha_innobase::info_low(
 		stat_sum_of_other_index_sizes
 			= ib_table->stat_sum_of_other_index_sizes;
 
-		mutex_exit(&dict_sys.mutex);
+		dict_sys.unlock();
 
 		/*
 		The MySQL optimizer seems to assume in a left join that n_rows
@@ -14377,8 +14374,8 @@ ha_innobase::info_low(
 		}
 
 		struct Locking {
-			Locking() { mutex_enter(&dict_sys.mutex); }
-			~Locking() { mutex_exit(&dict_sys.mutex); }
+			Locking() { dict_sys.freeze(); }
+			~Locking() { dict_sys.unfreeze(); }
 		} locking;
 
 		ut_a(ib_table->stat_initialized);
@@ -15119,7 +15116,7 @@ get_foreign_key_info(
 
 		dict_table_t*	ref_table;
 
-		ut_ad(mutex_own(&dict_sys.mutex));
+		ut_d(dict_sys.assert_locked());
 		ref_table = dict_table_open_on_name(
 			foreign->referenced_table_name_lookup,
 			TRUE, FALSE, DICT_ERR_IGNORE_NONE);
@@ -15174,7 +15171,7 @@ ha_innobase::get_foreign_key_list(
 
 	m_prebuilt->trx->op_info = "getting list of foreign keys";
 
-	mutex_enter(&dict_sys.mutex);
+	dict_sys.freeze();
 
 	for (dict_foreign_set::iterator it
 		= m_prebuilt->table->foreign_set.begin();
@@ -15191,7 +15188,7 @@ ha_innobase::get_foreign_key_list(
 		}
 	}
 
-	mutex_exit(&dict_sys.mutex);
+	dict_sys.unfreeze();
 
 	m_prebuilt->trx->op_info = "";
 
@@ -15212,7 +15209,7 @@ ha_innobase::get_parent_foreign_key_list(
 
 	m_prebuilt->trx->op_info = "getting list of referencing foreign keys";
 
-	mutex_enter(&dict_sys.mutex);
+	dict_sys.freeze();
 
 	for (dict_foreign_set::iterator it
 		= m_prebuilt->table->referenced_set.begin();
@@ -15229,7 +15226,7 @@ ha_innobase::get_parent_foreign_key_list(
 		}
 	}
 
-	mutex_exit(&dict_sys.mutex);
+	dict_sys.unfreeze();
 
 	m_prebuilt->trx->op_info = "";
 
@@ -20350,9 +20347,9 @@ TABLE* innobase_init_vc_templ(dict_table_t* table)
 		DBUG_RETURN(NULL);
 	}
 
-	mutex_enter(&dict_sys.mutex);
+	dict_sys.lock();
 	innobase_build_v_templ(mysql_table, table, table->vc_templ, NULL, true);
-	mutex_exit(&dict_sys.mutex);
+	dict_sys.unlock();
 	DBUG_RETURN(mysql_table);
 }
 
diff --git a/storage/innobase/handler/handler0alter.cc b/storage/innobase/handler/handler0alter.cc
index c54abca11d8..90ae65ead37 100644
--- a/storage/innobase/handler/handler0alter.cc
+++ b/storage/innobase/handler/handler0alter.cc
@@ -486,7 +486,7 @@ inline bool dict_table_t::instant_column(const dict_table_t& table,
 	DBUG_ASSERT(table.n_cols + table.n_dropped() >= n_cols + n_dropped());
 	DBUG_ASSERT(!table.persistent_autoinc
 		    || persistent_autoinc == table.persistent_autoinc);
-	ut_ad(mutex_own(&dict_sys.mutex));
+	ut_d(dict_sys.assert_locked());
 
 	{
 		const char* end = table.col_names;
@@ -2592,7 +2592,7 @@ innobase_init_foreign(
 	ulint		referenced_num_field)	/*!< in: number of referenced
 						columns */
 {
-	ut_ad(mutex_own(&dict_sys.mutex));
+	ut_d(dict_sys.assert_locked());
 
         if (constraint_name) {
                 ulint   db_len;
@@ -2996,7 +2996,7 @@ innobase_get_foreign_key_info(
 
 		add_fk[num_fk] = dict_mem_foreign_create();
 
-		mutex_enter(&dict_sys.mutex);
+		dict_sys.lock();
 
 		referenced_table_name = dict_get_referenced_table(
 			table->name.m_name,
@@ -3012,11 +3012,9 @@ innobase_get_foreign_key_info(
 				referenced_table = NULL;);
 
 		if (!referenced_table && trx->check_foreigns) {
-			mutex_exit(&dict_sys.mutex);
 			my_error(ER_FK_CANNOT_OPEN_PARENT,
 				 MYF(0), fk_key->ref_table.str);
-
-			goto err_exit;
+			goto err_exit_mutex;
 		}
 
 		if (fk_key->ref_columns.elements > 0) {
@@ -3045,12 +3043,11 @@ innobase_get_foreign_key_info(
 				/* Check whether there exist such
 				index in the the index create clause */
 				if (!referenced_index) {
-					mutex_exit(&dict_sys.mutex);
 					my_error(ER_FK_NO_INDEX_PARENT, MYF(0),
 						 fk_key->name.str
 						 ? fk_key->name.str : "",
 						 fk_key->ref_table.str);
-					goto err_exit;
+					goto err_exit_mutex;
 				}
 			} else {
 				ut_a(!trx->check_foreigns);
@@ -3060,10 +3057,9 @@ innobase_get_foreign_key_info(
 		} else {
 			/* Not possible to add a foreign key without a
 			referenced column */
-			mutex_exit(&dict_sys.mutex);
 			my_error(ER_CANNOT_ADD_FOREIGN, MYF(0),
 				 fk_key->ref_table.str);
-			goto err_exit;
+			goto err_exit_mutex;
 		}
 
 		if (!innobase_init_foreign(
@@ -3072,15 +3068,14 @@ innobase_get_foreign_key_info(
 			    num_col, referenced_table_name,
 			    referenced_table, referenced_index,
 			    referenced_column_names, referenced_num_col)) {
-			mutex_exit(&dict_sys.mutex);
 			my_error(
 				ER_DUP_CONSTRAINT_NAME,
 				MYF(0),
                                 "FOREIGN KEY", add_fk[num_fk]->id);
-			goto err_exit;
+			goto err_exit_mutex;
 		}
 
-		mutex_exit(&dict_sys.mutex);
+		dict_sys.unlock();
 
 		correct_option = innobase_set_foreign_key_option(
 			add_fk[num_fk], fk_key);
@@ -3111,6 +3106,8 @@ innobase_get_foreign_key_info(
 	*n_add_fk = num_fk;
 
 	DBUG_RETURN(true);
+err_exit_mutex:
+	dict_sys.unlock();
 err_exit:
 	for (ulint i = 0; i <= num_fk; i++) {
 		if (add_fk[i]) {
@@ -4024,7 +4021,7 @@ online_retry_drop_indexes_low(
 	dict_table_t*	table,	/*!< in/out: table */
 	trx_t*		trx)	/*!< in/out: transaction */
 {
-	ut_ad(mutex_own(&dict_sys.mutex));
+	ut_d(dict_sys.assert_locked());
 	ut_ad(trx->dict_operation_lock_mode == RW_X_LATCH);
 	ut_ad(trx_get_dict_operation(trx) == TRX_DICT_OP_INDEX);
 
@@ -4061,9 +4058,9 @@ online_retry_drop_indexes(
 		trx->free();
 	}
 
-	ut_d(mutex_enter(&dict_sys.mutex));
+	ut_d(dict_sys.freeze());
 	ut_d(dict_table_check_for_dup_indexes(table, CHECK_ALL_COMPLETE));
-	ut_d(mutex_exit(&dict_sys.mutex));
+	ut_d(dict_sys.unfreeze());
 	ut_ad(!table->drop_aborted);
 }
 
@@ -4138,7 +4135,7 @@ innobase_check_foreigns_low(
 	bool			drop)
 {
 	dict_foreign_t*	foreign;
-	ut_ad(mutex_own(&dict_sys.mutex));
+	ut_d(dict_sys.assert_locked());
 
 	/* Check if any FOREIGN KEY constraints are defined on this
 	column. */
@@ -6753,7 +6750,7 @@ prepare_inplace_alter_table_dict(
 			table. The new_table must be in the data
 			dictionary cache, because we are still holding
 			the dict_sys.mutex. */
-			ut_ad(mutex_own(&dict_sys.mutex));
+			ut_d(dict_sys.assert_locked());
 			temp_table = dict_table_open_on_name(
 				ctx->new_table->name.m_name, TRUE, FALSE,
 				DICT_ERR_IGNORE_NONE);
@@ -7077,10 +7074,10 @@ prepare_inplace_alter_table_dict(
 	case DB_SUCCESS:
 		ut_a(!dict_locked);
 
-		ut_d(mutex_enter(&dict_sys.mutex));
+		ut_d(dict_sys.freeze());
 		ut_d(dict_table_check_for_dup_indexes(
 			     user_table, CHECK_PARTIAL_OK));
-		ut_d(mutex_exit(&dict_sys.mutex));
+		ut_d(dict_sys.unfreeze());
 		DBUG_RETURN(false);
 	case DB_TABLESPACE_EXISTS:
 		my_error(ER_TABLESPACE_EXISTS, MYF(0), "(unknown)");
@@ -7504,10 +7501,10 @@ ha_innobase::prepare_inplace_alter_table(
 	}
 #endif /* UNIV_DEBUG */
 
-	ut_d(mutex_enter(&dict_sys.mutex));
+	ut_d(dict_sys.freeze());
 	ut_d(dict_table_check_for_dup_indexes(
 		     m_prebuilt->table, CHECK_ABORTED_OK));
-	ut_d(mutex_exit(&dict_sys.mutex));
+	ut_d(dict_sys.unfreeze());
 
 	if (!(ha_alter_info->handler_flags & ~INNOBASE_INPLACE_IGNORE)) {
 		/* Nothing to do */
@@ -8458,10 +8455,10 @@ ha_innobase::inplace_alter_table(
 		KEY*	dup_key;
 	all_done:
 	case DB_SUCCESS:
-		ut_d(mutex_enter(&dict_sys.mutex));
+		ut_d(dict_sys.freeze());
 		ut_d(dict_table_check_for_dup_indexes(
 			     m_prebuilt->table, CHECK_PARTIAL_OK));
-		ut_d(mutex_exit(&dict_sys.mutex));
+		ut_d(dict_sys.unfreeze());
 		/* prebuilt->table->n_ref_count can be anything here,
 		given that we hold at most a shared lock on the table. */
 		goto ok_exit;
@@ -8629,7 +8626,7 @@ operation.
 static
 ulint innobase_get_uncommitted_fts_indexes(const dict_table_t* table)
 {
-  ut_ad(mutex_own(&dict_sys.mutex));
+  ut_d(dict_sys.assert_locked());
   dict_index_t*	index = dict_table_get_first_index(table);
   ulint n_uncommitted_fts = 0;
 
@@ -9596,7 +9593,7 @@ innobase_update_foreign_cache(
 
 	DBUG_ENTER("innobase_update_foreign_cache");
 
-	ut_ad(mutex_own(&dict_sys.mutex));
+	ut_d(dict_sys.assert_locked());
 
 	user_table = ctx->old_table;
 
diff --git a/storage/innobase/handler/i_s.cc b/storage/innobase/handler/i_s.cc
index d1b0a18447e..e664100c25b 100644
--- a/storage/innobase/handler/i_s.cc
+++ b/storage/innobase/handler/i_s.cc
@@ -1354,7 +1354,7 @@ i_s_cmp_per_index_fill_low(
 	page_zip_stat_per_index_t		snap (page_zip_stat_per_index);
 	mutex_exit(&page_zip_stat_per_index_mutex);
 
-	mutex_enter(&dict_sys.mutex);
+	dict_sys.freeze();
 
 	page_zip_stat_per_index_t::iterator	iter;
 	ulint					i;
@@ -1407,18 +1407,18 @@ i_s_cmp_per_index_fill_low(
 			status = 1;
 			break;
 		}
-		/* Release and reacquire the dict mutex to allow other
+		/* Release and reacquire the dict_sys.latch to allow other
 		threads to proceed. This could eventually result in the
 		contents of INFORMATION_SCHEMA.innodb_cmp_per_index being
 		inconsistent, but it is an acceptable compromise. */
 		if (i == 1000) {
-			mutex_exit(&dict_sys.mutex);
+			dict_sys.unfreeze();
 			i = 0;
-			mutex_enter(&dict_sys.mutex);
+			dict_sys.freeze();
 		}
 	}
 
-	mutex_exit(&dict_sys.mutex);
+	dict_sys.unfreeze();
 
 	if (reset) {
 		page_zip_reset_stat_per_index();
@@ -2405,6 +2405,7 @@ i_s_fts_deleted_generic_fill(
 	fts_table_t		fts_table;
 	fts_doc_ids_t*		deleted;
 	dict_table_t*		user_table;
+	MDL_ticket*		mdl;
 
 	DBUG_ENTER("i_s_fts_deleted_generic_fill");
 
@@ -2415,20 +2416,20 @@ i_s_fts_deleted_generic_fill(
 
 	RETURN_IF_INNODB_NOT_STARTED(tables->schema_table_name.str);
 
-	/* Prevent DROP of the internal tables for fulltext indexes.
-	FIXME: acquire DDL-blocking MDL on the user table name! */
-	dict_sys.freeze();
-
 	user_table = dict_table_open_on_id(
-		innodb_ft_aux_table_id, FALSE, DICT_TABLE_OP_NORMAL);
+		innodb_ft_aux_table_id, FALSE, DICT_TABLE_OP_NORMAL,
+		thd, &mdl);
 
 	if (!user_table) {
-func_exit:
-		dict_sys.unfreeze();
 		DBUG_RETURN(0);
-	} else if (!dict_table_has_fts_index(user_table)) {
-		dict_table_close(user_table, FALSE, FALSE);
-		goto func_exit;
+	}
+
+	int	ret = 0;
+
+	if (!dict_table_has_fts_index(user_table)) {
+func_exit:
+		dict_table_close(user_table, FALSE, FALSE, thd, mdl);
+		DBUG_RETURN(ret);
 	}
 
 	deleted = fts_doc_ids_create();
@@ -2442,16 +2443,10 @@ i_s_fts_deleted_generic_fill(
 
 	fts_table_fetch_doc_ids(trx, &fts_table, deleted);
 
-	dict_table_close(user_table, FALSE, FALSE);
-
-	dict_sys.unfreeze();
-
 	trx->free();
 
 	fields = table->field;
 
-	int	ret = 0;
-
 	for (ulint j = 0; j < ib_vector_size(deleted->doc_ids); ++j) {
 		doc_id_t	doc_id;
 
@@ -2463,8 +2458,7 @@ i_s_fts_deleted_generic_fill(
 	}
 
 	fts_doc_ids_free(deleted);
-
-	DBUG_RETURN(ret);
+	goto func_exit;
 }
 
 /*******************************************************************//**
@@ -2781,6 +2775,7 @@ i_s_fts_index_cache_fill(
 {
 	dict_table_t*		user_table;
 	fts_cache_t*		cache;
+	MDL_ticket*		mdl;
 
 	DBUG_ENTER("i_s_fts_index_cache_fill");
 
@@ -2791,27 +2786,24 @@ i_s_fts_index_cache_fill(
 
 	RETURN_IF_INNODB_NOT_STARTED(tables->schema_table_name.str);
 
-	/* Prevent DROP of the internal tables for fulltext indexes.
-	FIXME: acquire DDL-blocking MDL on the user table name! */
-	dict_sys.freeze();
-
 	user_table = dict_table_open_on_id(
-		innodb_ft_aux_table_id, FALSE, DICT_TABLE_OP_NORMAL);
+		innodb_ft_aux_table_id, FALSE, DICT_TABLE_OP_NORMAL,
+		thd, &mdl);
 
 	if (!user_table) {
-no_fts:
-		dict_sys.unfreeze();
 		DBUG_RETURN(0);
 	}
 
+	int			ret = 0;
+
 	if (!user_table->fts || !user_table->fts->cache) {
-		dict_table_close(user_table, FALSE, FALSE);
-		goto no_fts;
+func_exit:
+		dict_table_close(user_table, FALSE, FALSE, thd, mdl);
+		DBUG_RETURN(ret);
 	}
 
 	cache = user_table->fts->cache;
 
-	int			ret = 0;
 	fts_string_t		conv_str;
 	byte			word[HA_FT_MAXBYTELEN + 1];
 	conv_str.f_len = sizeof word;
@@ -2830,10 +2822,7 @@ i_s_fts_index_cache_fill(
 	}
 
 	mysql_mutex_unlock(&cache->lock);
-	dict_table_close(user_table, FALSE, FALSE);
-	dict_sys.unfreeze();
-
-	DBUG_RETURN(ret);
+	goto func_exit;
 }
 
 /*******************************************************************//**
@@ -2988,9 +2977,7 @@ i_s_fts_index_table_fill_selected(
 		}
 	}
 
-	mutex_enter(&dict_sys.mutex);
-	que_graph_free(graph);
-	mutex_exit(&dict_sys.mutex);
+	fts_que_graph_free(graph);
 
 	trx->free();
 
@@ -3230,6 +3217,7 @@ i_s_fts_index_table_fill(
 	Item*		)	/*!< in: condition (ignored) */
 {
 	dict_table_t*		user_table;
+	MDL_ticket*		mdl;
 	dict_index_t*		index;
 
 	DBUG_ENTER("i_s_fts_index_table_fill");
@@ -3241,15 +3229,11 @@ i_s_fts_index_table_fill(
 
 	RETURN_IF_INNODB_NOT_STARTED(tables->schema_table_name.str);
 
-	/* Prevent DROP of the internal tables for fulltext indexes.
-	FIXME: acquire DDL-blocking MDL on the user table name! */
-	dict_sys.freeze();
-
 	user_table = dict_table_open_on_id(
-		innodb_ft_aux_table_id, FALSE, DICT_TABLE_OP_NORMAL);
+		innodb_ft_aux_table_id, FALSE, DICT_TABLE_OP_NORMAL,
+		thd, &mdl);
 
 	if (!user_table) {
-		dict_sys.unfreeze();
 		DBUG_RETURN(0);
 	}
 
@@ -3267,9 +3251,7 @@ i_s_fts_index_table_fill(
 		}
 	}
 
-	dict_table_close(user_table, FALSE, FALSE);
-
-	dict_sys.unfreeze();
+	dict_table_close(user_table, FALSE, FALSE, thd, mdl);
 
 	ut_free(conv_str.f_str);
 
@@ -3379,6 +3361,7 @@ i_s_fts_config_fill(
 {
 	Field**			fields;
 	TABLE*			table = (TABLE*) tables->table;
+	MDL_ticket*		mdl;
 	trx_t*			trx;
 	fts_table_t		fts_table;
 	dict_table_t*		user_table;
@@ -3395,22 +3378,20 @@ i_s_fts_config_fill(
 
 	RETURN_IF_INNODB_NOT_STARTED(tables->schema_table_name.str);
 
-	/* Prevent DROP of the internal tables for fulltext indexes.
-	FIXME: acquire DDL-blocking MDL on the user table name! */
-	dict_sys.freeze();
-
 	user_table = dict_table_open_on_id(
-		innodb_ft_aux_table_id, FALSE, DICT_TABLE_OP_NORMAL);
+		innodb_ft_aux_table_id, FALSE, DICT_TABLE_OP_NORMAL,
+		thd, &mdl);
 
 	if (!user_table) {
-no_fts:
-		dict_sys.unfreeze();
 		DBUG_RETURN(0);
 	}
 
+	int	ret = 0;
+
 	if (!dict_table_has_fts_index(user_table)) {
-		dict_table_close(user_table, FALSE, FALSE);
-		goto no_fts;
+func_exit:
+		dict_table_close(user_table, FALSE, FALSE, thd, mdl);
+		DBUG_RETURN(ret);
 	}
 
 	fields = table->field;
@@ -3426,8 +3407,6 @@ i_s_fts_config_fill(
 		DBUG_ASSERT(!dict_index_is_online_ddl(index));
 	}
 
-	int	ret = 0;
-
 	while (fts_config_key[i]) {
 		fts_string_t	value;
 		char*		key_name;
@@ -3465,14 +3444,9 @@ i_s_fts_config_fill(
 	}
 
 	fts_sql_commit(trx);
-
-	dict_table_close(user_table, FALSE, FALSE);
-
-	dict_sys.unfreeze();
-
 	trx->free();
 
-	DBUG_RETURN(ret);
+	goto func_exit;
 }
 
 /*******************************************************************//**
@@ -4011,7 +3985,7 @@ i_s_innodb_buffer_page_fill(
 		if (page_info->page_type == I_S_PAGE_TYPE_INDEX) {
 			bool ret = false;
 
-			mutex_enter(&dict_sys.mutex);
+			dict_sys.freeze();
 
 			const dict_index_t* index =
 				dict_index_get_if_in_cache_low(
@@ -4036,7 +4010,7 @@ i_s_innodb_buffer_page_fill(
 						system_charset_info);
 			}
 
-			mutex_exit(&dict_sys.mutex);
+			dict_sys.unfreeze();
 
 			OK(ret);
 
@@ -4515,7 +4489,7 @@ i_s_innodb_buf_page_lru_fill(
 		if (page_info->page_type == I_S_PAGE_TYPE_INDEX) {
 			bool ret = false;
 
-			mutex_enter(&dict_sys.mutex);
+			dict_sys.freeze();
 
 			const dict_index_t* index =
 				dict_index_get_if_in_cache_low(
@@ -4540,7 +4514,7 @@ i_s_innodb_buf_page_lru_fill(
 						system_charset_info);
 			}
 
-			mutex_exit(&dict_sys.mutex);
+			dict_sys.unfreeze();
 
 			OK(ret);
 
@@ -4861,8 +4835,8 @@ i_s_sys_tables_fill_table(
 	}
 
 	heap = mem_heap_create(1000);
-	mutex_enter(&dict_sys.mutex);
 	mtr_start(&mtr);
+	dict_sys.lock();
 
 	rec = dict_startscan_system(&pcur, &mtr, SYS_TABLES);
 
@@ -4875,7 +4849,7 @@ i_s_sys_tables_fill_table(
 		err_msg = dict_process_sys_tables_rec_and_mtr_commit(
 			heap, rec, &table_rec, false, &mtr);
 
-		mutex_exit(&dict_sys.mutex);
+		dict_sys.unlock();
 
 		if (!err_msg) {
 			i_s_dict_fill_sys_tables(thd, table_rec,
@@ -4893,13 +4867,13 @@ i_s_sys_tables_fill_table(
 		mem_heap_empty(heap);
 
 		/* Get the next record */
-		mutex_enter(&dict_sys.mutex);
 		mtr_start(&mtr);
+		dict_sys.lock();
 		rec = dict_getnext_system(&pcur, &mtr);
 	}
 
+	dict_sys.unlock();
 	mtr_commit(&mtr);
-	mutex_exit(&dict_sys.mutex);
 	mem_heap_free(heap);
 
 	DBUG_RETURN(0);
@@ -5040,8 +5014,8 @@ i_s_dict_fill_sys_tablestats(
 	{
 		struct Locking
 		{
-			Locking() { mutex_enter(&dict_sys.mutex); }
-			~Locking() { mutex_exit(&dict_sys.mutex); }
+			Locking() { dict_sys.freeze(); }
+			~Locking() { dict_sys.unfreeze(); }
 		} locking;
 
 		OK(fields[SYS_TABLESTATS_INIT]->store(table->stat_initialized,
@@ -5107,10 +5081,9 @@ i_s_sys_tables_fill_table_stats(
 	}
 
 	heap = mem_heap_create(1000);
-	dict_sys.freeze();
-	mutex_enter(&dict_sys.mutex);
 	mtr_start(&mtr);
 
+	dict_sys.lock();
 	rec = dict_startscan_system(&pcur, &mtr, SYS_TABLES);
 
 	while (rec) {
@@ -5123,7 +5096,7 @@ i_s_sys_tables_fill_table_stats(
 			heap, rec, &table_rec, true, &mtr);
 
 		ulint ref_count = table_rec ? table_rec->get_ref_count() : 0;
-		mutex_exit(&dict_sys.mutex);
+		dict_sys.unlock();
 
 		DBUG_EXECUTE_IF("test_sys_tablestats", {
 			if (strcmp("test/t1", table_rec->name.m_name) == 0 ) {
@@ -5141,20 +5114,17 @@ i_s_sys_tables_fill_table_stats(
 					    err_msg);
 		}
 
-		dict_sys.unfreeze();
 		mem_heap_empty(heap);
 
 		/* Get the next record */
-		dict_sys.freeze();
-		mutex_enter(&dict_sys.mutex);
 
 		mtr_start(&mtr);
+		dict_sys.lock();
 		rec = dict_getnext_system(&pcur, &mtr);
 	}
 
+	dict_sys.unlock();
 	mtr_commit(&mtr);
-	mutex_exit(&dict_sys.mutex);
-	dict_sys.unfreeze();
 	mem_heap_free(heap);
 
 	DBUG_RETURN(0);
@@ -5348,8 +5318,8 @@ i_s_sys_indexes_fill_table(
 	}
 
 	heap = mem_heap_create(1000);
-	mutex_enter(&dict_sys.mutex);
 	mtr_start(&mtr);
+	dict_sys.lock();
 
 	/* Start scan the SYS_INDEXES table */
 	rec = dict_startscan_system(&pcur, &mtr, SYS_INDEXES);
@@ -5370,7 +5340,7 @@ i_s_sys_indexes_fill_table(
 		space_id = space_id == 4 ? mach_read_from_4(field)
 			: ULINT_UNDEFINED;
 		mtr_commit(&mtr);
-		mutex_exit(&dict_sys.mutex);
+		dict_sys.unlock();
 
 		if (!err_msg) {
 			if (int err = i_s_dict_fill_sys_indexes(
@@ -5388,13 +5358,13 @@ i_s_sys_indexes_fill_table(
 		mem_heap_empty(heap);
 
 		/* Get the next record */
-		mutex_enter(&dict_sys.mutex);
+		dict_sys.lock();
 		mtr_start(&mtr);
 		rec = dict_getnext_system(&pcur, &mtr);
 	}
 
 	mtr_commit(&mtr);
-	mutex_exit(&dict_sys.mutex);
+	dict_sys.unlock();
 	mem_heap_free(heap);
 
 	DBUG_RETURN(0);
@@ -5567,8 +5537,8 @@ i_s_sys_columns_fill_table(
 	}
 
 	heap = mem_heap_create(1000);
-	mutex_enter(&dict_sys.mutex);
 	mtr_start(&mtr);
+	dict_sys.lock();
 
 	rec = dict_startscan_system(&pcur, &mtr, SYS_COLUMNS);
 
@@ -5585,7 +5555,7 @@ i_s_sys_columns_fill_table(
 						       &nth_v_col);
 
 		mtr_commit(&mtr);
-		mutex_exit(&dict_sys.mutex);
+		dict_sys.unlock();
 
 		if (!err_msg) {
 			i_s_dict_fill_sys_columns(thd, table_id, col_name,
@@ -5600,13 +5570,13 @@ i_s_sys_columns_fill_table(
 		mem_heap_empty(heap);
 
 		/* Get the next record */
-		mutex_enter(&dict_sys.mutex);
 		mtr_start(&mtr);
+		dict_sys.lock();
 		rec = dict_getnext_system(&pcur, &mtr);
 	}
 
 	mtr_commit(&mtr);
-	mutex_exit(&dict_sys.mutex);
+	dict_sys.unlock();
 	mem_heap_free(heap);
 
 	DBUG_RETURN(0);
@@ -5760,8 +5730,8 @@ i_s_sys_virtual_fill_table(
 		DBUG_RETURN(0);
 	}
 
-	mutex_enter(&dict_sys.mutex);
 	mtr_start(&mtr);
+	dict_sys.lock();
 
 	rec = dict_startscan_system(&pcur, &mtr, SYS_VIRTUAL);
 
@@ -5776,7 +5746,7 @@ i_s_sys_virtual_fill_table(
 						       &base_pos);
 
 		mtr_commit(&mtr);
-		mutex_exit(&dict_sys.mutex);
+		dict_sys.unlock();
 
 		if (!err_msg) {
 			i_s_dict_fill_sys_virtual(thd, table_id, pos, base_pos,
@@ -5788,13 +5758,13 @@ i_s_sys_virtual_fill_table(
 		}
 
 		/* Get the next record */
-		mutex_enter(&dict_sys.mutex);
 		mtr_start(&mtr);
+		dict_sys.lock();
 		rec = dict_getnext_system(&pcur, &mtr);
 	}
 
 	mtr_commit(&mtr);
-	mutex_exit(&dict_sys.mutex);
+	dict_sys.unlock();
 
 	DBUG_RETURN(0);
 }
@@ -5946,8 +5916,8 @@ i_s_sys_fields_fill_table(
 	}
 
 	heap = mem_heap_create(1000);
-	mutex_enter(&dict_sys.mutex);
 	mtr_start(&mtr);
+	dict_sys.lock();
 
 	/* will save last index id so that we know whether we move to
 	the next index. This is used to calculate prefix length */
@@ -5967,7 +5937,7 @@ i_s_sys_fields_fill_table(
 						      &pos, &index_id, last_id);
 
 		mtr_commit(&mtr);
-		mutex_exit(&dict_sys.mutex);
+		dict_sys.unlock();
 
 		if (!err_msg) {
 			i_s_dict_fill_sys_fields(thd, index_id, &field_rec,
@@ -5982,13 +5952,13 @@ i_s_sys_fields_fill_table(
 		mem_heap_empty(heap);
 
 		/* Get the next record */
-		mutex_enter(&dict_sys.mutex);
 		mtr_start(&mtr);
+		dict_sys.lock();
 		rec = dict_getnext_system(&pcur, &mtr);
 	}
 
 	mtr_commit(&mtr);
-	mutex_exit(&dict_sys.mutex);
+	dict_sys.unlock();
 	mem_heap_free(heap);
 
 	DBUG_RETURN(0);
@@ -6150,8 +6120,8 @@ i_s_sys_foreign_fill_table(
 	}
 
 	heap = mem_heap_create(1000);
-	mutex_enter(&dict_sys.mutex);
 	mtr_start(&mtr);
+	dict_sys.lock();
 
 	rec = dict_startscan_system(&pcur, &mtr, SYS_FOREIGN);
 
@@ -6164,7 +6134,7 @@ i_s_sys_foreign_fill_table(
 		err_msg = dict_process_sys_foreign_rec(heap, rec, &foreign_rec);
 
 		mtr_commit(&mtr);
-		mutex_exit(&dict_sys.mutex);
+		dict_sys.unlock();
 
 		if (!err_msg) {
 			i_s_dict_fill_sys_foreign(thd, &foreign_rec,
@@ -6179,12 +6149,12 @@ i_s_sys_foreign_fill_table(
 
 		/* Get the next record */
 		mtr_start(&mtr);
-		mutex_enter(&dict_sys.mutex);
+		dict_sys.lock();
 		rec = dict_getnext_system(&pcur, &mtr);
 	}
 
 	mtr_commit(&mtr);
-	mutex_exit(&dict_sys.mutex);
+	dict_sys.unlock();
 	mem_heap_free(heap);
 
 	DBUG_RETURN(0);
@@ -6342,8 +6312,8 @@ i_s_sys_foreign_cols_fill_table(
 	}
 
 	heap = mem_heap_create(1000);
-	mutex_enter(&dict_sys.mutex);
 	mtr_start(&mtr);
+	dict_sys.lock();
 
 	rec = dict_startscan_system(&pcur, &mtr, SYS_FOREIGN_COLS);
 
@@ -6359,7 +6329,7 @@ i_s_sys_foreign_cols_fill_table(
 			heap, rec, &name, &for_col_name, &ref_col_name, &pos);
 
 		mtr_commit(&mtr);
-		mutex_exit(&dict_sys.mutex);
+		dict_sys.unlock();
 
 		if (!err_msg) {
 			i_s_dict_fill_sys_foreign_cols(
@@ -6374,13 +6344,13 @@ i_s_sys_foreign_cols_fill_table(
 		mem_heap_empty(heap);
 
 		/* Get the next record */
-		mutex_enter(&dict_sys.mutex);
 		mtr_start(&mtr);
+		dict_sys.lock();
 		rec = dict_getnext_system(&pcur, &mtr);
 	}
 
 	mtr_commit(&mtr);
-	mutex_exit(&dict_sys.mutex);
+	dict_sys.unlock();
 	mem_heap_free(heap);
 
 	DBUG_RETURN(0);
@@ -6632,8 +6602,8 @@ i_s_sys_tablespaces_fill_table(
 	}
 
 	heap = mem_heap_create(1000);
-	mutex_enter(&dict_sys.mutex);
 	mtr_start(&mtr);
+	dict_sys.lock();
 
 	for (rec = dict_startscan_system(&pcur, &mtr, SYS_TABLESPACES);
 	     rec != NULL;
@@ -6649,7 +6619,7 @@ i_s_sys_tablespaces_fill_table(
 			heap, rec, &space, &name, &flags);
 
 		mtr_commit(&mtr);
-		mutex_exit(&dict_sys.mutex);
+		dict_sys.unlock();
 
 		if (!err_msg) {
 			i_s_dict_fill_sys_tablespaces(
@@ -6664,12 +6634,12 @@ i_s_sys_tablespaces_fill_table(
 		mem_heap_empty(heap);
 
 		/* Get the next record */
-		mutex_enter(&dict_sys.mutex);
 		mtr_start(&mtr);
+		dict_sys.lock();
 	}
 
 	mtr_commit(&mtr);
-	mutex_exit(&dict_sys.mutex);
+	dict_sys.unlock();
 	mem_heap_free(heap);
 
 	DBUG_RETURN(0);
@@ -6813,8 +6783,8 @@ i_s_sys_datafiles_fill_table(
 	}
 
 	heap = mem_heap_create(1000);
-	mutex_enter(&dict_sys.mutex);
 	mtr_start(&mtr);
+	dict_sys.lock();
 
 	rec = dict_startscan_system(&pcur, &mtr, SYS_DATAFILES);
 
@@ -6828,7 +6798,7 @@ i_s_sys_datafiles_fill_table(
 			heap, rec, &space, &path);
 
 		mtr_commit(&mtr);
-		mutex_exit(&dict_sys.mutex);
+		dict_sys.unlock();
 
 		if (!err_msg) {
 			i_s_dict_fill_sys_datafiles(
@@ -6842,13 +6812,13 @@ i_s_sys_datafiles_fill_table(
 		mem_heap_empty(heap);
 
 		/* Get the next record */
-		mutex_enter(&dict_sys.mutex);
 		mtr_start(&mtr);
+		dict_sys.lock();
 		rec = dict_getnext_system(&pcur, &mtr);
 	}
 
 	mtr_commit(&mtr);
-	mutex_exit(&dict_sys.mutex);
+	dict_sys.unlock();
 	mem_heap_free(heap);
 
 	DBUG_RETURN(0);
diff --git a/storage/innobase/include/dict0dict.h b/storage/innobase/include/dict0dict.h
index b65d170fb3e..b428a052b66 100644
--- a/storage/innobase/include/dict0dict.h
+++ b/storage/innobase/include/dict0dict.h
@@ -175,11 +175,10 @@ dict_table_close(
 	THD*		thd = NULL,
 	MDL_ticket*	mdl = NULL);
 
-/*********************************************************************//**
+/********************************************************************//**
 Closes the only open handle to a table and drops a table while assuring
-that dict_sys.mutex is held the whole time.  This assures that the table
-is not evicted after the close when the count of open handles goes to zero.
-Because dict_sys.mutex is held, we do not need to call prevent_eviction(). */
+that dict_sys.frozen() holds the whole time.  This assures that the table
+is not evicted after the close when table->n_ref_count reaches 0. */
 void
 dict_table_close_and_drop(
 /*======================*/
@@ -1174,7 +1173,6 @@ dict_field_get_col(
 
 /**********************************************************************//**
 Returns an index object if it is found in the dictionary cache.
-Assumes that dict_sys.mutex is already being held.
 @return index, NULL if not found */
 dict_index_t*
 dict_index_get_if_in_cache_low(
@@ -1321,9 +1319,6 @@ dict_index_calc_min_rec_len(
 	const dict_index_t*	index)	/*!< in: index */
 	MY_ATTRIBUTE((nonnull, warn_unused_result));
 
-#define dict_mutex_enter_for_mysql() mutex_enter(&dict_sys.mutex)
-#define dict_mutex_exit_for_mysql() mutex_exit(&dict_sys.mutex)
-
 /********************************************************************//**
 Checks if the database name in two table names is the same.
 @return TRUE if same db name */
@@ -1396,8 +1391,7 @@ class dict_sys_t
 private:
   /** @brief the data dictionary rw-latch protecting dict_sys
 
-  Table create, drop, etc. reserve this in X-mode (along with
-  acquiring dict_sys.mutex); implicit or
+  Table create, drop, etc. reserve this in X-mode; implicit or
   backround operations that are not fully covered by MDL
   (rollback, foreign key checks) reserve this in S-mode.
 
@@ -1407,15 +1401,10 @@ class dict_sys_t
 #ifdef UNIV_DEBUG
   /** whether latch is being held in exclusive mode (by any thread) */
   bool latch_ex;
+  /** number of S-latch holders */
+  Atomic_counter<uint32_t> latch_readers;
 #endif
 public:
-	DictSysMutex	mutex;		/*!< mutex protecting the data
-					dictionary; protects also the
-					disk-based dictionary system tables;
-					this mutex serializes CREATE TABLE
-					and DROP TABLE, as well as reading
-					the dictionary data for a table from
-					system tables */
 	hash_table_t	table_hash;	/*!< hash table of the tables, based
 					on name */
 	/** hash table of persistent table IDs */
@@ -1470,7 +1459,7 @@ class dict_sys_t
 	(should only happen during the rollback of CREATE...SELECT) */
 	dict_table_t* get_temporary_table(table_id_t id)
 	{
-		ut_ad(mutex_own(&mutex));
+		ut_ad(frozen());
 		dict_table_t* table;
 		ulint fold = ut_fold_ull(id);
 		HASH_SEARCH(id_hash, &temp_id_hash, fold, dict_table_t*, table,
@@ -1489,7 +1478,7 @@ class dict_sys_t
 	@retval	NULL	if not cached */
 	dict_table_t* get_table(table_id_t id)
 	{
-		ut_ad(mutex_own(&mutex));
+		ut_ad(frozen());
 		dict_table_t* table;
 		ulint fold = ut_fold_ull(id);
 		HASH_SEARCH(id_hash, &table_id_hash, fold, dict_table_t*,
@@ -1524,7 +1513,7 @@ class dict_sys_t
   {
     ut_ad(table);
     ut_ad(table->can_be_evicted == in_lru);
-    ut_ad(mutex_own(&mutex));
+    ut_ad(frozen());
     for (const dict_table_t* t = UT_LIST_GET_FIRST(in_lru
 					     ? table_LRU : table_non_LRU);
 	 t; t = UT_LIST_GET_NEXT(table_LRU, t))
@@ -1556,6 +1545,7 @@ class dict_sys_t
   /** Move a table to the non-LRU list from the LRU list. */
   void prevent_eviction(dict_table_t* table)
   {
+    ut_d(assert_locked());
     ut_ad(find(table));
     if (table->can_be_evicted)
     {
@@ -1568,38 +1558,53 @@ class dict_sys_t
   inline void acquire(dict_table_t* table);
 
 #ifdef UNIV_DEBUG
-  /** Assert that the data dictionary is locked */
-  void assert_locked() { ut_ad(mutex_own(&mutex)); }
+  /** @return whether any thread (not necessarily the current thread)
+  is holding the latch; that is, this check may return false
+  positives */
+  bool frozen() const { return latch_readers || locked(); }
+  /** @return whether any thread (not necessarily the current thread)
+  is holding the exclusive latch; that is, this check may return false
+  positives */
+  bool locked() const { return latch_ex; }
+  /** Assert that the data dictionary is exclusively locked by some thread
+  (not necessarily but hopefully the current one) */
+  void assert_locked() const { ut_ad(locked()); }
 #endif
   /** Lock the data dictionary cache. */
-  void lock(const char* file, unsigned line)
+  void lock()
   {
     latch.wr_lock();
     ut_ad(!latch_ex);
+    ut_ad(!latch_readers);
     ut_d(latch_ex= true);
-    mutex_enter_loc(&mutex, file, line);
   }
 
   /** Unlock the data dictionary cache. */
   void unlock()
   {
     ut_ad(latch_ex);
+    ut_ad(!latch_readers);
     ut_d(latch_ex= false);
-    mutex_exit(&mutex);
     latch.wr_unlock();
   }
 
   /** Prevent modifications of the data dictionary */
-  void freeze() { latch.rd_lock(); ut_ad(!latch_ex); }
+  void freeze() { latch.rd_lock(); ut_ad(!latch_ex); ut_d(latch_readers++); }
   /** Allow modifications of the data dictionary */
-  void unfreeze() { ut_ad(!latch_ex); latch.rd_unlock(); }
+  void unfreeze()
+  {
+    ut_ad(!latch_ex);
+    ut_ad(latch_readers);
+    latch_readers--;
+    latch.rd_unlock();
+  }
 
   /** Estimate the used memory occupied by the data dictionary
   table and index objects.
   @return number of bytes occupied */
   ulint rough_size() const
   {
-    /* No mutex; this is a very crude approximation anyway */
+    /* No frozen(); this is a very crude approximation anyway */
     ulint size = UT_LIST_GET_LEN(table_LRU) + UT_LIST_GET_LEN(table_non_LRU);
     size *= sizeof(dict_table_t)
       + sizeof(dict_index_t) * 2
@@ -1616,8 +1621,6 @@ class dict_sys_t
 extern dict_sys_t	dict_sys;
 
 #define dict_table_prevent_eviction(table) dict_sys.prevent_eviction(table)
-#define dict_sys_lock() dict_sys.lock(__FILE__, __LINE__)
-#define dict_sys_unlock() dict_sys.unlock()
 
 template<typename F>
 inline bool dict_sys_t::for_each_index(const F &f, dict_table_t *t)
@@ -1638,8 +1641,8 @@ inline bool dict_sys_t::for_each_index(const F &f)
 {
   struct Locking
   {
-    Locking() { mutex_enter(&dict_sys.mutex); }
-    ~Locking() { mutex_exit(&dict_sys.mutex); }
+    Locking() { dict_sys.freeze(); }
+    ~Locking() { dict_sys.unfreeze(); }
   } locking;
   for (const dict_table_t *t= UT_LIST_GET_FIRST(table_non_LRU);
        t; t= UT_LIST_GET_NEXT(table_LRU, t))
@@ -1692,7 +1695,6 @@ struct dict_table_schema_t {
 Checks whether a table exists and whether it has the given structure.
 The table must have the same number of columns with the same names and
 types. The order of the columns does not matter.
-The caller must own the dictionary mutex.
 dict_table_schema_check() @{
 @return DB_SUCCESS if the table exists and contains the necessary columns */
 dberr_t
diff --git a/storage/innobase/include/dict0dict.ic b/storage/innobase/include/dict0dict.ic
index eda639ba7c1..41c530d116c 100644
--- a/storage/innobase/include/dict0dict.ic
+++ b/storage/innobase/include/dict0dict.ic
@@ -1157,7 +1157,7 @@ inline
 void
 dict_table_t::acquire()
 {
-	ut_ad(mutex_own(&dict_sys.mutex));
+	ut_ad(dict_sys.frozen());
 	n_ref_count++;
 }
 
diff --git a/storage/innobase/include/dict0load.h b/storage/innobase/include/dict0load.h
index f067571ca5b..907d920bb12 100644
--- a/storage/innobase/include/dict0load.h
+++ b/storage/innobase/include/dict0load.h
@@ -81,12 +81,9 @@ dict_get_first_table_name_in_db(
 
 /** Make sure the data_file_name is saved in dict_table_t if needed.
 Try to read it from the fil_system first, then from SYS_DATAFILES.
-@param[in]	table		Table object
-@param[in]	dict_mutex_own	true if dict_sys.mutex is owned already */
-void
-dict_get_and_save_data_dir_path(
-	dict_table_t*	table,
-	bool		dict_mutex_own);
+@param[in,out]	table		Table object
+@param[in]	dict_locked	whether dict_sys.lock() was called already */
+void dict_get_and_save_data_dir_path(dict_table_t* table, bool dict_locked);
 
 /** Loads a table definition and also all its index definitions, and also
 the cluster definition if the table is a member in a cluster. Also loads
diff --git a/storage/innobase/include/dict0mem.h b/storage/innobase/include/dict0mem.h
index 1ad2517c8fb..55282eb4114 100644
--- a/storage/innobase/include/dict0mem.h
+++ b/storage/innobase/include/dict0mem.h
@@ -1939,7 +1939,7 @@ struct dict_table_t {
 	inline size_t get_overflow_field_local_len() const;
 
 	/** Parse the table file name into table name and database name.
-	@tparam		dict_locked	whether dict_sys.mutex is being held
+	@tparam		dict_locked	whether dict_sys.lock() was called
 	@param[in,out]	db_name		database name buffer
 	@param[in,out]	tbl_name	table name buffer
 	@param[out]	db_name_len	database name length
diff --git a/storage/innobase/include/dict0priv.ic b/storage/innobase/include/dict0priv.ic
index 2fcadc055e1..748e01c11c9 100644
--- a/storage/innobase/include/dict0priv.ic
+++ b/storage/innobase/include/dict0priv.ic
@@ -39,7 +39,7 @@ dict_table_get_low(
 	dict_table_t*	table;
 
 	ut_ad(table_name);
-	ut_ad(mutex_own(&dict_sys.mutex));
+	ut_d(dict_sys.assert_locked());
 
 	table = dict_table_check_if_in_cache_low(table_name);
 
@@ -79,7 +79,7 @@ dict_table_check_if_in_cache_low(
 		   ("table: '%s'", table_name));
 
 	ut_ad(table_name);
-	ut_ad(mutex_own(&dict_sys.mutex));
+	ut_ad(dict_sys.frozen());
 
 	/* Look for the table name in the hash table */
 	table_fold = ut_fold_string(table_name);
diff --git a/storage/innobase/include/dict0stats.ic b/storage/innobase/include/dict0stats.ic
index 4972efe8961..bf212e08aa5 100644
--- a/storage/innobase/include/dict0stats.ic
+++ b/storage/innobase/include/dict0stats.ic
@@ -148,8 +148,6 @@ dict_stats_init(
 /*============*/
 	dict_table_t*	table)	/*!< in/out: table */
 {
-	ut_ad(!mutex_own(&dict_sys.mutex));
-
 	if (table->stat_initialized) {
 		return;
 	}
@@ -174,7 +172,7 @@ dict_stats_deinit(
 /*==============*/
 	dict_table_t*	table)	/*!< in/out: table */
 {
-	ut_ad(mutex_own(&dict_sys.mutex));
+	ut_d(dict_sys.assert_locked());
 
 	ut_a(table->get_ref_count() == 0);
 
diff --git a/storage/innobase/include/dict0stats_bg.h b/storage/innobase/include/dict0stats_bg.h
index b210a2ec357..12866af5a25 100644
--- a/storage/innobase/include/dict0stats_bg.h
+++ b/storage/innobase/include/dict0stats_bg.h
@@ -67,7 +67,7 @@ dict_stats_stop_bg(
 	dict_table_t*	table)	/*!< in/out: table */
 {
 	ut_ad(!srv_read_only_mode);
-	ut_ad(mutex_own(&dict_sys.mutex));
+	ut_d(dict_sys.assert_locked());
 
 	if (!(table->stats_bg_flag & BG_STAT_IN_PROGRESS)) {
 		return(true);
diff --git a/storage/innobase/include/dict0types.h b/storage/innobase/include/dict0types.h
index d0da45ab218..9239b4a794f 100644
--- a/storage/innobase/include/dict0types.h
+++ b/storage/innobase/include/dict0types.h
@@ -1,7 +1,7 @@
 /*****************************************************************************
 
 Copyright (c) 1996, 2015, Oracle and/or its affiliates. All Rights Reserved.
-Copyright (c) 2013, 2019, MariaDB Corporation.
+Copyright (c) 2013, 2020, MariaDB Corporation.
 
 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
@@ -90,10 +90,6 @@ enum ib_quiesce_t {
 	QUIESCE_COMPLETE		/*!< All done */
 };
 
-#ifndef UNIV_INNOCHECKSUM
-typedef ib_mutex_t DictSysMutex;
-#endif /* !UNIV_INNOCHECKSUM */
-
 /** Prefix for tmp tables, adopted from sql/table.h */
 #define TEMP_FILE_PREFIX		"#sql"
 #define TEMP_FILE_PREFIX_LENGTH		4
diff --git a/storage/innobase/include/fts0fts.h b/storage/innobase/include/fts0fts.h
index 77649aa55ab..491a5843456 100644
--- a/storage/innobase/include/fts0fts.h
+++ b/storage/innobase/include/fts0fts.h
@@ -379,9 +379,9 @@ extern bool		fts_need_sync;
 
 #define	fts_que_graph_free(graph)			\
 do {							\
-	mutex_enter(&dict_sys.mutex);			\
+	dict_sys.lock();				\
 	que_graph_free(graph);				\
-	mutex_exit(&dict_sys.mutex);			\
+	dict_sys.unlock();				\
 } while (0)
 
 /******************************************************************//**
diff --git a/storage/innobase/include/log0log.ic b/storage/innobase/include/log0log.ic
index 36b16652e4e..66704cb1edf 100644
--- a/storage/innobase/include/log0log.ic
+++ b/storage/innobase/include/log0log.ic
@@ -306,8 +306,6 @@ log_free_check(void)
 
 #ifdef UNIV_DEBUG
 	static const latch_level_t latches[] = {
-		SYNC_DICT,		/* dict_sys.mutex during
-					commit_try_rebuild() */
 		SYNC_INDEX_TREE		/* index->lock */
 	};
 #endif /* UNIV_DEBUG */
diff --git a/storage/innobase/include/que0que.h b/storage/innobase/include/que0que.h
index af282b378ca..343f95a8c61 100644
--- a/storage/innobase/include/que0que.h
+++ b/storage/innobase/include/que0que.h
@@ -269,9 +269,9 @@ que_eval_sql(
 /*=========*/
 	pars_info_t*	info,	/*!< in: info struct, or NULL */
 	const char*	sql,	/*!< in: SQL string */
-	bool		reserve_dict_mutex,
-				/*!< in: whether to acquire/release
-				dict_sys.mutex around call to pars_sql. */
+	bool		lock_dict,
+				/*!< in: whether to invoke dict_sys.lock()
+				before calling pars_sql() */
 	trx_t*		trx);	/*!< in: trx */
 
 /**********************************************************************//**
diff --git a/storage/innobase/include/row0mysql.h b/storage/innobase/include/row0mysql.h
index 18114f18b14..c0249025c99 100644
--- a/storage/innobase/include/row0mysql.h
+++ b/storage/innobase/include/row0mysql.h
@@ -313,14 +313,7 @@ row_update_cascade_for_mysql(
 /*********************************************************************//**
 Locks the data dictionary exclusively for performing a table create or other
 data dictionary modification operation. */
-void
-row_mysql_lock_data_dictionary_func(
-/*================================*/
-	trx_t*		trx,	/*!< in/out: transaction */
-	const char*	file,	/*!< in: file name */
-	unsigned	line);	/*!< in: line number */
-#define row_mysql_lock_data_dictionary(trx)				\
-	row_mysql_lock_data_dictionary_func(trx, __FILE__, __LINE__)
+void row_mysql_lock_data_dictionary(trx_t *trx);
 /*********************************************************************//**
 Unlocks the data dictionary exclusive lock. */
 void
diff --git a/storage/innobase/include/sync0sync.h b/storage/innobase/include/sync0sync.h
index 9babbce4c68..978a423691b 100644
--- a/storage/innobase/include/sync0sync.h
+++ b/storage/innobase/include/sync0sync.h
@@ -41,7 +41,6 @@ Created 9/5/1995 Heikki Tuuri
 /* Key defines to register InnoDB mutexes with performance schema */
 extern mysql_pfs_key_t	buf_pool_mutex_key;
 extern mysql_pfs_key_t	dict_foreign_err_mutex_key;
-extern mysql_pfs_key_t	dict_sys_mutex_key;
 extern mysql_pfs_key_t	fil_system_mutex_key;
 extern mysql_pfs_key_t	flush_list_mutex_key;
 extern mysql_pfs_key_t	fts_cache_mutex_key;
diff --git a/storage/innobase/include/sync0types.h b/storage/innobase/include/sync0types.h
index f54a6728068..52761c062a7 100644
--- a/storage/innobase/include/sync0types.h
+++ b/storage/innobase/include/sync0types.h
@@ -231,7 +231,6 @@ enum latch_level_t {
 	SYNC_IBUF_HEADER,
 	SYNC_DICT_HEADER,
 	SYNC_STATS_AUTO_RECALC,
-	SYNC_DICT,
 
 	/** Level is varying. Only used with buffer pool page locks, which
 	do not have a fixed level, but instead have their level set after
@@ -251,7 +250,6 @@ up its meta-data. See sync0debug.cc. */
 enum latch_id_t {
 	LATCH_ID_NONE = 0,
 	LATCH_ID_DICT_FOREIGN_ERR,
-	LATCH_ID_DICT_SYS,
 	LATCH_ID_FIL_SYSTEM,
 	LATCH_ID_FTS_DELETE,
 	LATCH_ID_FTS_DOC_ID,
@@ -946,17 +944,7 @@ struct sync_checker : public sync_check_functor_t
 	@return whether a latch violation was detected */
 	bool operator()(const latch_level_t level) const override
 	{
-		if (some_allowed) {
-			switch (level) {
-			case SYNC_DICT:
-			case SYNC_NO_ORDER_CHECK:
-				return(false);
-			default:
-				return(true);
-			}
-		}
-
-		return(true);
+		return some_allowed || level != SYNC_NO_ORDER_CHECK;
 	}
 };
 
diff --git a/storage/innobase/pars/pars0pars.cc b/storage/innobase/pars/pars0pars.cc
index 2f3791aeb5e..75491553567 100644
--- a/storage/innobase/pars/pars0pars.cc
+++ b/storage/innobase/pars/pars0pars.cc
@@ -1987,7 +1987,7 @@ pars_sql(
 	heap = mem_heap_create(16000);
 
 	/* Currently, the parser is not reentrant: */
-	ut_ad(mutex_own(&dict_sys.mutex));
+	ut_d(dict_sys.assert_locked());
 
 	pars_sym_tab_global = sym_tab_create(heap);
 
diff --git a/storage/innobase/pars/pars0sym.cc b/storage/innobase/pars/pars0sym.cc
index 5e4c0e0f6e0..05d650e2593 100644
--- a/storage/innobase/pars/pars0sym.cc
+++ b/storage/innobase/pars/pars0sym.cc
@@ -67,7 +67,7 @@ sym_tab_free_private(
 	sym_node_t*	sym;
 	func_node_t*	func;
 
-	ut_ad(mutex_own(&dict_sys.mutex));
+	ut_d(dict_sys.assert_locked());
 
 	for (sym = UT_LIST_GET_FIRST(sym_tab->sym_list);
 	     sym != NULL;
diff --git a/storage/innobase/que/que0que.cc b/storage/innobase/que/que0que.cc
index 18fe7626d57..962ea23477b 100644
--- a/storage/innobase/que/que0que.cc
+++ b/storage/innobase/que/que0que.cc
@@ -1093,9 +1093,9 @@ que_eval_sql(
 /*=========*/
 	pars_info_t*	info,	/*!< in: info struct, or NULL */
 	const char*	sql,	/*!< in: SQL string */
-	bool		reserve_dict_mutex,
-				/*!< in: whether to acquire/release
-				dict_sys.mutex around call to pars_sql. */
+	bool		lock_dict,
+				/*!< in: whether to invoke dict_sys.lock()
+				before calling pars_sql() */
 	trx_t*		trx)	/*!< in: trx */
 {
 	que_thr_t*	thr;
@@ -1106,14 +1106,14 @@ que_eval_sql(
 
 	ut_a(trx->error_state == DB_SUCCESS);
 
-	if (reserve_dict_mutex) {
-		mutex_enter(&dict_sys.mutex);
+	if (lock_dict) {
+		dict_sys.lock();
 	}
 
 	graph = pars_sql(info, sql);
 
-	if (reserve_dict_mutex) {
-		mutex_exit(&dict_sys.mutex);
+	if (lock_dict) {
+		dict_sys.unlock();
 	}
 
 	graph->trx = trx;
@@ -1125,14 +1125,14 @@ que_eval_sql(
 
 	que_run_threads(thr);
 
-	if (reserve_dict_mutex) {
-		mutex_enter(&dict_sys.mutex);
+	if (lock_dict) {
+		dict_sys.lock();
 	}
 
 	que_graph_free(graph);
 
-	if (reserve_dict_mutex) {
-		mutex_exit(&dict_sys.mutex);
+	if (lock_dict) {
+		dict_sys.unlock();
 	}
 
 	DBUG_RETURN(trx->error_state);
diff --git a/storage/innobase/row/row0import.cc b/storage/innobase/row/row0import.cc
index b61212a869d..7b1f9b874e3 100644
--- a/storage/innobase/row/row0import.cc
+++ b/storage/innobase/row/row0import.cc
@@ -1411,7 +1411,8 @@ row_import::set_root_by_heuristic() UNIV_NOTHROW
 			" the tablespace has " << m_n_indexes << " indexes";
 	}
 
-	dict_mutex_enter_for_mysql();
+	/* It should be safe to access the table even without
+	dict_sys.freeze() because we should be holding MDL on it. */
 
 	ulint	i = 0;
 	dberr_t	err = DB_SUCCESS;
@@ -1421,7 +1422,9 @@ row_import::set_root_by_heuristic() UNIV_NOTHROW
 	     index = UT_LIST_GET_NEXT(indexes, index)) {
 
 		if (index->type & DICT_FTS) {
+			dict_sys.lock();
 			index->type |= DICT_CORRUPT;
+			dict_sys.unlock();
 			ib::warn() << "Skipping FTS index: " << index->name;
 		} else if (i < m_n_indexes) {
 
@@ -1452,8 +1455,6 @@ row_import::set_root_by_heuristic() UNIV_NOTHROW
 		}
 	}
 
-	dict_mutex_exit_for_mysql();
-
 	return(err);
 }
 
diff --git a/storage/innobase/row/row0ins.cc b/storage/innobase/row/row0ins.cc
index b86f4fe34ce..05435dd4324 100644
--- a/storage/innobase/row/row0ins.cc
+++ b/storage/innobase/row/row0ins.cc
@@ -3010,9 +3010,9 @@ row_ins_sec_index_entry_low(
 			if (!index->is_committed()) {
 				ut_ad(!thr_get_trx(thr)
 				      ->dict_operation_lock_mode);
-				mutex_enter(&dict_sys.mutex);
+				dict_sys.lock();
 				dict_set_corrupted_index_cache_only(index);
-				mutex_exit(&dict_sys.mutex);
+				dict_sys.unlock();
 				/* Do not return any error to the
 				caller. The duplicate will be reported
 				by ALTER TABLE or CREATE UNIQUE INDEX.
diff --git a/storage/innobase/row/row0mysql.cc b/storage/innobase/row/row0mysql.cc
index 85e54da7c6f..ac12ea21ebd 100644
--- a/storage/innobase/row/row0mysql.cc
+++ b/storage/innobase/row/row0mysql.cc
@@ -2256,15 +2256,10 @@ row_update_cascade_for_mysql(
 /*********************************************************************//**
 Locks the data dictionary exclusively for performing a table create or other
 data dictionary modification operation. */
-void
-row_mysql_lock_data_dictionary_func(
-/*================================*/
-	trx_t*		trx,	/*!< in/out: transaction */
-	const char*	file,	/*!< in: file name */
-	unsigned	line)	/*!< in: line number */
+void row_mysql_lock_data_dictionary(trx_t *trx)
 {
 	ut_ad(trx->dict_operation_lock_mode == 0);
-	dict_sys.lock(file, line);
+	dict_sys.lock();
 	trx->dict_operation_lock_mode = RW_X_LATCH;
 }
 
@@ -3969,10 +3964,9 @@ row_drop_database_for_mysql(
 
 		dict_table_close(table, TRUE, FALSE);
 
-		/* The dict_table_t object must not be accessed before
-		dict_table_open() or after dict_table_close(). But this is OK
-		if we are holding, the dict_sys.mutex. */
-		ut_ad(mutex_own(&dict_sys.mutex));
+		/* The dict_table_t object must not normally be accessed before
+		dict_table_open() or after dict_table_close(). */
+		ut_d(dict_sys.assert_locked());
 
 		/* Disable statistics on the found table. */
 		if (!dict_stats_stop_bg(table)) {
diff --git a/storage/innobase/row/row0quiesce.cc b/storage/innobase/row/row0quiesce.cc
index 0bdf52dfd56..aecc4a2630a 100644
--- a/storage/innobase/row/row0quiesce.cc
+++ b/storage/innobase/row/row0quiesce.cc
@@ -486,7 +486,7 @@ row_quiesce_table_has_fts_index(
 {
 	bool			exists = false;
 
-	dict_mutex_enter_for_mysql();
+	dict_sys.freeze();
 
 	for (const dict_index_t* index = UT_LIST_GET_FIRST(table->indexes);
 	     index != 0;
@@ -498,7 +498,7 @@ row_quiesce_table_has_fts_index(
 		}
 	}
 
-	dict_mutex_exit_for_mysql();
+	dict_sys.unfreeze();
 
 	return(exists);
 }
diff --git a/storage/innobase/row/row0uins.cc b/storage/innobase/row/row0uins.cc
index 114d83c8564..89baa300da1 100644
--- a/storage/innobase/row/row0uins.cc
+++ b/storage/innobase/row/row0uins.cc
@@ -367,9 +367,9 @@ static bool row_undo_ins_parse_undo_rec(undo_node_t* node, bool dict_locked)
 		node->table = dict_table_open_on_id(table_id, dict_locked,
 						    DICT_TABLE_OP_NORMAL);
 	} else if (!dict_locked) {
-		mutex_enter(&dict_sys.mutex);
+		dict_sys.freeze();
 		node->table = dict_sys.get_temporary_table(table_id);
-		mutex_exit(&dict_sys.mutex);
+		dict_sys.unfreeze();
 	} else {
 		node->table = dict_sys.get_temporary_table(table_id);
 	}
@@ -553,16 +553,14 @@ row_undo_ins(
 
 		log_free_check();
 
-		if (node->table->id == DICT_INDEXES_ID) {
+		if (!dict_locked && node->table->id == DICT_INDEXES_ID) {
 			ut_ad(!node->table->is_temporary());
-			if (!dict_locked) {
-				mutex_enter(&dict_sys.mutex);
-			}
+			dict_sys.lock();
 			err = row_undo_ins_remove_clust_rec(node);
-			if (!dict_locked) {
-				mutex_exit(&dict_sys.mutex);
-			}
+			dict_sys.unlock();
 		} else {
+			ut_ad(node->table->id != DICT_INDEXES_ID
+			      || !node->table->is_temporary());
 			err = row_undo_ins_remove_clust_rec(node);
 		}
 
diff --git a/storage/innobase/row/row0umod.cc b/storage/innobase/row/row0umod.cc
index 8f33f78f82e..25e2a092a96 100644
--- a/storage/innobase/row/row0umod.cc
+++ b/storage/innobase/row/row0umod.cc
@@ -916,9 +916,9 @@ row_undo_mod_sec_flag_corrupted(
 		on the data dictionary during normal rollback,
 		we can only mark the index corrupted in the
 		data dictionary cache. TODO: fix this somehow.*/
-		mutex_enter(&dict_sys.mutex);
+		dict_sys.lock();
 		dict_set_corrupted_index_cache_only(index);
-		mutex_exit(&dict_sys.mutex);
+		dict_sys.unlock();
 		break;
 	default:
 		ut_ad(0);
@@ -1234,9 +1234,9 @@ static bool row_undo_mod_parse_undo_rec(undo_node_t* node, bool dict_locked)
 		node->table = dict_table_open_on_id(table_id, dict_locked,
 						    DICT_TABLE_OP_NORMAL);
 	} else if (!dict_locked) {
-		mutex_enter(&dict_sys.mutex);
+		dict_sys.freeze();
 		node->table = dict_sys.get_temporary_table(table_id);
-		mutex_exit(&dict_sys.mutex);
+		dict_sys.unfreeze();
 	} else {
 		node->table = dict_sys.get_temporary_table(table_id);
 	}
diff --git a/storage/innobase/srv/srv0srv.cc b/storage/innobase/srv/srv0srv.cc
index eb5d87b677e..9959ed0f221 100644
--- a/storage/innobase/srv/srv0srv.cc
+++ b/storage/innobase/srv/srv0srv.cc
@@ -1600,12 +1600,12 @@ srv_master_evict_from_table_cache(
 {
 	ulint	n_tables_evicted = 0;
 
-	dict_sys_lock();
+	dict_sys.lock();
 
 	n_tables_evicted = dict_make_room_in_cache(
 		innobase_get_table_cache_size(), pct_check);
 
-	dict_sys_unlock();
+	dict_sys.unlock();
 
 	return(n_tables_evicted);
 }
diff --git a/storage/innobase/sync/sync0debug.cc b/storage/innobase/sync/sync0debug.cc
index 057109f50b9..eb4c32321a6 100644
--- a/storage/innobase/sync/sync0debug.cc
+++ b/storage/innobase/sync/sync0debug.cc
@@ -489,7 +489,6 @@ LatchDebug::LatchDebug()
 	LEVEL_MAP_INSERT(SYNC_IBUF_HEADER);
 	LEVEL_MAP_INSERT(SYNC_DICT_HEADER);
 	LEVEL_MAP_INSERT(SYNC_STATS_AUTO_RECALC);
-	LEVEL_MAP_INSERT(SYNC_DICT);
 	LEVEL_MAP_INSERT(SYNC_LEVEL_VARYING);
 	LEVEL_MAP_INSERT(SYNC_NO_ORDER_CHECK);
 
@@ -872,10 +871,6 @@ LatchDebug::check_order(
 		ut_a(find(latches, SYNC_IBUF_PESS_INSERT_MUTEX) == NULL);
 		break;
 
-	case SYNC_DICT:
-		basic_check(latches, level, SYNC_DICT);
-		break;
-
 	case SYNC_MUTEX:
 	case SYNC_UNKNOWN:
 	case SYNC_LEVEL_VARYING:
@@ -1188,8 +1183,6 @@ sync_latch_meta_init()
 	LATCH_ADD_MUTEX(DICT_FOREIGN_ERR, SYNC_NO_ORDER_CHECK,
 			dict_foreign_err_mutex_key);
 
-	LATCH_ADD_MUTEX(DICT_SYS, SYNC_DICT, dict_sys_mutex_key);
-
 	LATCH_ADD_MUTEX(FIL_SYSTEM, SYNC_ANY_LATCH, fil_system_mutex_key);
 
 	LATCH_ADD_MUTEX(FTS_DELETE, SYNC_FTS_OPTIMIZE, fts_delete_mutex_key);
diff --git a/storage/innobase/sync/sync0sync.cc b/storage/innobase/sync/sync0sync.cc
index d7f0947004b..d8d632e32cc 100644
--- a/storage/innobase/sync/sync0sync.cc
+++ b/storage/innobase/sync/sync0sync.cc
@@ -38,7 +38,6 @@ Created 9/5/1995 Heikki Tuuri
 #ifdef UNIV_PFS_MUTEX
 mysql_pfs_key_t	buf_pool_mutex_key;
 mysql_pfs_key_t	dict_foreign_err_mutex_key;
-mysql_pfs_key_t	dict_sys_mutex_key;
 mysql_pfs_key_t	fil_system_mutex_key;
 mysql_pfs_key_t	flush_list_mutex_key;
 mysql_pfs_key_t	fts_cache_mutex_key;
diff --git a/storage/innobase/trx/trx0trx.cc b/storage/innobase/trx/trx0trx.cc
index bd54af76d9d..e660644feda 100644
--- a/storage/innobase/trx/trx0trx.cc
+++ b/storage/innobase/trx/trx0trx.cc
@@ -613,10 +613,10 @@ trx_resurrect_table_locks(
 		if (dict_table_t* table = dict_table_open_on_id(
 			    *i, FALSE, DICT_TABLE_OP_LOAD_TABLESPACE)) {
 			if (!table->is_readable()) {
-				mutex_enter(&dict_sys.mutex);
+				dict_sys.lock();
 				dict_table_close(table, TRUE, FALSE);
 				dict_sys.remove(table);
-				mutex_exit(&dict_sys.mutex);
+				dict_sys.unlock();
 				continue;
 			}
 
@@ -1235,7 +1235,7 @@ trx_update_mod_tables_timestamp(
 	const bool preserve_tables = !innodb_evict_tables_on_commit_debug
 		|| trx->is_recovered /* avoid trouble with XA recovery */
 # if 1 /* if dict_stats_exec_sql() were not playing dirty tricks */
-		|| mutex_own(&dict_sys.mutex)
+		|| dict_sys.locked()
 # else /* this would be more proper way to do it */
 		|| trx->dict_operation_lock_mode || trx->dict_operation
 # endif
@@ -1266,7 +1266,7 @@ trx_update_mod_tables_timestamp(
 		}
 		/* recheck while holding the mutex that blocks
 		table->acquire() */
-		mutex_enter(&dict_sys.mutex);
+		dict_sys.lock();
 		mutex_enter(&lock_sys.mutex);
 		const bool do_evict = !table->get_ref_count()
 			&& !UT_LIST_GET_LEN(table->locks);
@@ -1274,7 +1274,7 @@ trx_update_mod_tables_timestamp(
 		if (do_evict) {
 			dict_sys.remove(table, true);
 		}
-		mutex_exit(&dict_sys.mutex);
+		dict_sys.unlock();
 #endif
 	}
 
