Commit 8b6a308e authored by Marko Mäkelä's avatar Marko Mäkelä

MDEV-29883 Deadlock between InnoDB statistics update and BLOB insert

The test innodb.innodb-wl5522-debug would occasionally hang
(especially when run with ./mtr --rr) due to a deadlock between
btr_store_big_rec_extern_fields() and dict_stats_analyze_index().
The two threads would acquire the clustered index root page latch and
the tablespace latch in the opposite order. The deadlock was possible
because dict_stats_analyze_index() was holding the index latch in
shared mode and an index root page latch, while waiting for the
tablespace latch. If a stronger dict_index_t::lock had been held
by dict_stats_analyze_index(), any operations that free or allocate
index pages would have been blocked.

In each caller of fseg_n_reserved_pages() except ibuf_init_at_db_start()
which is a special case for ibuf.index at database startup, we must hold
an index latch that prevents concurrent allocation or freeing of index
pages.

Any operation that allocates or free pages that belong to an index tree
must first acquire an index latch in Update or Exclusive mode, and while
holding that, acquire an index root page latch in Update or Exclusive
mode.

dict_index_t::clear(): Also acquire an index latch. Otherwise,
the test innodb.insert_into_empty could hang.

btr_get_size_and_reserved(): Assert that a strong enough index latch
is being held. Only acquire a shared fil_space_t::latch; we are only
reading, not modifying any data.

dict_stats_update_transient_for_index(),
dict_stats_analyze_index(): Acquire a strong enough index latch. Only
acquire a shared fil_space_t::latch.

These operations had followed the same order of acquiring latches in
every InnoDB version since the very beginning
(commit c533308a).
The calls for acquiring tablespace latch had previously been moved in
commit 87839258 and
commit 1e9c922f.

The hang was introduced in
commit 2e814d47 which imported
mysql/mysql-server@ac74632293bea967b352d1b472abedeeaa921b98
which failed to strengthen the locking requirements of the function
btr_get_size().
parent 78a04a4c
...@@ -1102,6 +1102,7 @@ dberr_t dict_index_t::clear(que_thr_t *thr) ...@@ -1102,6 +1102,7 @@ dberr_t dict_index_t::clear(que_thr_t *thr)
mtr.set_log_mode(MTR_LOG_NO_REDO); mtr.set_log_mode(MTR_LOG_NO_REDO);
else else
set_modified(mtr); set_modified(mtr);
mtr_sx_lock_index(this, &mtr);
dberr_t err; dberr_t err;
if (buf_block_t *root_block= if (buf_block_t *root_block=
......
...@@ -297,7 +297,7 @@ btr_get_size_and_reserved( ...@@ -297,7 +297,7 @@ btr_get_size_and_reserved(
{ {
ulint dummy; ulint dummy;
ut_ad(mtr->memo_contains(index->lock, MTR_MEMO_S_LOCK)); ut_ad(mtr->memo_contains(index->lock, MTR_MEMO_SX_LOCK));
ut_a(flag == BTR_N_LEAF_PAGES || flag == BTR_TOTAL_SIZE); ut_a(flag == BTR_N_LEAF_PAGES || flag == BTR_TOTAL_SIZE);
if (index->page == FIL_NULL if (index->page == FIL_NULL
...@@ -314,7 +314,7 @@ btr_get_size_and_reserved( ...@@ -314,7 +314,7 @@ btr_get_size_and_reserved(
return ULINT_UNDEFINED; return ULINT_UNDEFINED;
} }
mtr->x_lock_space(index->table->space); mtr->s_lock_space(index->table->space);
ulint n = fseg_n_reserved_pages(*root, PAGE_HEADER + PAGE_BTR_SEG_LEAF ulint n = fseg_n_reserved_pages(*root, PAGE_HEADER + PAGE_BTR_SEG_LEAF
+ root->page.frame, used, mtr); + root->page.frame, used, mtr);
...@@ -345,7 +345,7 @@ dict_stats_save_defrag_stats( ...@@ -345,7 +345,7 @@ dict_stats_save_defrag_stats(
mtr_t mtr; mtr_t mtr;
ulint n_leaf_pages; ulint n_leaf_pages;
mtr.start(); mtr.start();
mtr_s_lock_index(index, &mtr); mtr_sx_lock_index(index, &mtr);
ulint n_leaf_reserved= btr_get_size_and_reserved(index, BTR_N_LEAF_PAGES, ulint n_leaf_reserved= btr_get_size_and_reserved(index, BTR_N_LEAF_PAGES,
&n_leaf_pages, &mtr); &n_leaf_pages, &mtr);
mtr.commit(); mtr.commit();
......
...@@ -1418,7 +1418,8 @@ dict_stats_update_transient_for_index( ...@@ -1418,7 +1418,8 @@ dict_stats_update_transient_for_index(
mtr_t mtr; mtr_t mtr;
mtr.start(); mtr.start();
mtr_s_lock_index(index, &mtr); mtr_sx_lock_index(index, &mtr);
dberr_t err; dberr_t err;
buf_block_t* root = btr_root_block_get(index, RW_SX_LATCH, buf_block_t* root = btr_root_block_get(index, RW_SX_LATCH,
&mtr, &err); &mtr, &err);
...@@ -1434,7 +1435,7 @@ dict_stats_update_transient_for_index( ...@@ -1434,7 +1435,7 @@ dict_stats_update_transient_for_index(
goto invalid; goto invalid;
} }
mtr.x_lock_space(index->table->space); mtr.s_lock_space(index->table->space);
ulint dummy, size; ulint dummy, size;
index->stat_index_size index->stat_index_size
...@@ -2559,9 +2560,9 @@ static index_stats_t dict_stats_analyze_index(dict_index_t* index) ...@@ -2559,9 +2560,9 @@ static index_stats_t dict_stats_analyze_index(dict_index_t* index)
DEBUG_PRINTF(" %s(index=%s)\n", __func__, index->name()); DEBUG_PRINTF(" %s(index=%s)\n", __func__, index->name());
mtr.start(); mtr.start();
mtr_s_lock_index(index, &mtr); mtr_sx_lock_index(index, &mtr);
dberr_t err; dberr_t err;
buf_block_t* root = btr_root_block_get(index, RW_SX_LATCH, &mtr, &err); buf_block_t* root = btr_root_block_get(index, RW_SX_LATCH, &mtr, &err);
if (!root) { if (!root) {
empty_index: empty_index:
mtr.commit(); mtr.commit();
...@@ -2570,7 +2571,7 @@ static index_stats_t dict_stats_analyze_index(dict_index_t* index) ...@@ -2570,7 +2571,7 @@ static index_stats_t dict_stats_analyze_index(dict_index_t* index)
} }
uint16_t root_level = btr_page_get_level(root->page.frame); uint16_t root_level = btr_page_get_level(root->page.frame);
mtr.x_lock_space(index->table->space); mtr.s_lock_space(index->table->space);
ulint dummy, size; ulint dummy, size;
result.index_size result.index_size
= fseg_n_reserved_pages(*root, PAGE_HEADER + PAGE_BTR_SEG_LEAF = fseg_n_reserved_pages(*root, PAGE_HEADER + PAGE_BTR_SEG_LEAF
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment