diff options
author | Marko Mäkelä <marko.makela@mariadb.com> | 2020-01-17 10:37:25 +0200 |
---|---|---|
committer | Marko Mäkelä <marko.makela@mariadb.com> | 2020-01-17 14:27:28 +0200 |
commit | 3e38d15585f03e794a83a1d141ead33e8c878f27 (patch) | |
tree | 1e0adc3042f944cb299ed52d8488982fac1497da /storage/innobase | |
parent | 9cae7bdcc0055776064b3ba08830b1577b18f5c8 (diff) | |
download | mariadb-git-3e38d15585f03e794a83a1d141ead33e8c878f27.tar.gz |
MDEV-21509 Possible hang during purge of history, or rollback
WL#6326 in MariaDB 10.2.2 introduced a potential hang on purge or rollback
when an index tree is being shrunk by multiple levels.
This fix is based on
mysql/mysql-server@f2c58526300c0d84837effa26d37cbd5d2694967
with the main difference that our version of the test case uses
DEBUG_SYNC instrumentation on ROLLBACK, not on purge.
btr_cur_will_modify_tree(): Simplify the check further.
This is the actual bug fix.
row_undo_mod_remove_clust_low(), row_undo_mod_clust(): Add DEBUG_SYNC
instrumentation for the test case.
Diffstat (limited to 'storage/innobase')
-rw-r--r-- | storage/innobase/btr/btr0cur.cc | 80 | ||||
-rw-r--r-- | storage/innobase/include/page0page.h | 20 | ||||
-rw-r--r-- | storage/innobase/include/page0page.ic | 24 | ||||
-rw-r--r-- | storage/innobase/row/row0umod.cc | 16 |
4 files changed, 100 insertions, 40 deletions
diff --git a/storage/innobase/btr/btr0cur.cc b/storage/innobase/btr/btr0cur.cc index f8498fa1748..97e7e3d47a2 100644 --- a/storage/innobase/btr/btr0cur.cc +++ b/storage/innobase/btr/btr0cur.cc @@ -583,46 +583,66 @@ btr_cur_will_modify_tree( const ulint n_recs = page_get_n_recs(page); if (lock_intention <= BTR_INTENTION_BOTH) { - ulint margin; + compile_time_assert(BTR_INTENTION_DELETE < BTR_INTENTION_BOTH); + compile_time_assert(BTR_INTENTION_BOTH < BTR_INTENTION_INSERT); - /* check delete will cause. (BTR_INTENTION_BOTH - or BTR_INTENTION_DELETE) */ - /* first, 2nd, 2nd-last and last records are 4 records */ - if (n_recs < 5) { - return(true); + if (!page_has_siblings(page)) { + return true; } - /* is first, 2nd or last record */ - if (page_rec_is_first(rec, page) - || (page_has_next(page) - && (page_rec_is_last(rec, page) - || page_rec_is_second_last(rec, page))) - || (page_has_prev(page) - && page_rec_is_second(rec, page))) { - return(true); - } + ulint margin = rec_size; if (lock_intention == BTR_INTENTION_BOTH) { + ulint level = btr_page_get_level(page, mtr); + + /* This value is the worst expectation for the node_ptr + records to be deleted from this page. It is used to + expect whether the cursor position can be the left_most + record in this page or not. */ + ulint max_nodes_deleted = 0; + + /* By modifying tree operations from the under of this + level, logically (2 ^ (level - 1)) opportunities to + deleting records in maximum even unreally rare case. */ + if (level > 7) { + /* TODO: adjust this practical limit. */ + max_nodes_deleted = 64; + } else if (level > 0) { + max_nodes_deleted = (ulint)1 << (level - 1); + } + /* check delete will cause. (BTR_INTENTION_BOTH + or BTR_INTENTION_DELETE) */ + if (n_recs <= max_nodes_deleted * 2 + || page_rec_is_first(rec, page)) { + /* The cursor record can be the left most record + in this page. */ + return true; + } + + if (page_has_prev(page) + && page_rec_distance_is_at_most( + page_get_infimum_rec(page), rec, + max_nodes_deleted)) { + return true; + } + + if (page_has_next(page) + && page_rec_distance_is_at_most( + rec, page_get_supremum_rec(page), + max_nodes_deleted)) { + return true; + } + /* Delete at leftmost record in a page causes delete & insert at its parent page. After that, the delete might cause btr_compress() and delete record at its - parent page. Thus we should consider max 2 deletes. */ - - margin = rec_size * 2; - } else { - ut_ad(lock_intention == BTR_INTENTION_DELETE); - - margin = rec_size; + parent page. Thus we should consider max deletes. */ + margin *= max_nodes_deleted; } - /* NOTE: call mach_read_from_4() directly to avoid assertion - failure. It is safe because we already have SX latch of the - index tree */ + + /* Safe because we already have SX latch of the index tree */ if (page_get_data_size(page) - < margin + BTR_CUR_PAGE_COMPRESS_LIMIT(index) - || (mach_read_from_4(page + FIL_PAGE_NEXT) - == FIL_NULL - && mach_read_from_4(page + FIL_PAGE_PREV) - == FIL_NULL)) { + < margin + BTR_CUR_PAGE_COMPRESS_LIMIT(index)) { return(true); } } diff --git a/storage/innobase/include/page0page.h b/storage/innobase/include/page0page.h index 87de16f9abf..54edf034ac6 100644 --- a/storage/innobase/include/page0page.h +++ b/storage/innobase/include/page0page.h @@ -1,6 +1,6 @@ /***************************************************************************** -Copyright (c) 1994, 2016, Oracle and/or its affiliates. All Rights Reserved. -Copyright (c) 2013, 2019, MariaDB Corporation. +Copyright (c) 1994, 2019, Oracle and/or its affiliates. All Rights Reserved. +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 @@ -814,6 +814,22 @@ page_rec_is_last( MY_ATTRIBUTE((warn_unused_result)); /************************************************************//** +true if distance between the records (measured in number of times we have to +move to the next record) is at most the specified value +@param[in] left_rec lefter record +@param[in] right_rec righter record +@param[in] val specified value to compare +@return true if the distance is smaller than the value */ +UNIV_INLINE +bool +page_rec_distance_is_at_most( +/*=========================*/ + const rec_t* left_rec, + const rec_t* right_rec, + ulint val) + MY_ATTRIBUTE((warn_unused_result)); + +/************************************************************//** true if the record is the second last user record on a page. @return true if the second last user record */ UNIV_INLINE diff --git a/storage/innobase/include/page0page.ic b/storage/innobase/include/page0page.ic index 98b518187b5..75bfa56e2a6 100644 --- a/storage/innobase/include/page0page.ic +++ b/storage/innobase/include/page0page.ic @@ -1,7 +1,7 @@ /***************************************************************************** -Copyright (c) 1994, 2015, Oracle and/or its affiliates. All Rights Reserved. -Copyright (c) 2016, 2019, MariaDB Corporation. +Copyright (c) 1994, 2019, Oracle and/or its affiliates. All Rights Reserved. +Copyright (c) 2016, 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 @@ -359,6 +359,26 @@ page_rec_is_last( } /************************************************************//** +true if distance between the records (measured in number of times we have to +move to the next record) is at most the specified value */ +UNIV_INLINE +bool +page_rec_distance_is_at_most( +/*=========================*/ + const rec_t* left_rec, + const rec_t* right_rec, + ulint val) +{ + for (ulint i = 0; i <= val; i++) { + if (left_rec == right_rec) { + return (true); + } + left_rec = page_rec_get_next_const(left_rec); + } + return (false); +} + +/************************************************************//** true if the record is the second last user record on a page. @return true if the second last user record */ UNIV_INLINE diff --git a/storage/innobase/row/row0umod.cc b/storage/innobase/row/row0umod.cc index 4598e8959d6..8e2775a050b 100644 --- a/storage/innobase/row/row0umod.cc +++ b/storage/innobase/row/row0umod.cc @@ -1,7 +1,7 @@ /***************************************************************************** Copyright (c) 1997, 2017, Oracle and/or its affiliates. All Rights Reserved. -Copyright (c) 2017, 2019, MariaDB Corporation. +Copyright (c) 2017, 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 @@ -169,12 +169,15 @@ row_undo_mod_remove_clust_low( /* Find out if the record has been purged already or if we can remove it. */ - if (!btr_pcur_restore_position(mode, &node->pcur, mtr) - || row_vers_must_preserve_del_marked(node->new_trx_id, - node->table->name, - mtr)) { + if (!btr_pcur_restore_position(mode, &node->pcur, mtr)) { + return DB_SUCCESS; + } - return(DB_SUCCESS); + DEBUG_SYNC_C("rollback_purge_clust"); + + if (row_vers_must_preserve_del_marked(node->new_trx_id, + node->table->name, mtr)) { + return DB_SUCCESS; } btr_cur = btr_pcur_get_btr_cur(&node->pcur); @@ -361,6 +364,7 @@ row_undo_mod_clust( == node->new_trx_id); btr_pcur_commit_specify_mtr(pcur, &mtr); + DEBUG_SYNC_C("rollback_undo_pk"); if (err == DB_SUCCESS && node->rec_type == TRX_UNDO_UPD_DEL_REC) { |