diff options
author | Sergey Vojtovich <svoj@mariadb.org> | 2018-01-20 17:45:42 +0400 |
---|---|---|
committer | Sergey Vojtovich <svoj@mariadb.org> | 2018-01-22 16:23:15 +0400 |
commit | 4dc30f3c170b9deab1ff0b472930baba5c65b01c (patch) | |
tree | c0dd8d5920eebb89bb8d87a579164466c97b81d9 /storage/innobase/read | |
parent | ec32c050726bad8c0504c6b6b74a0fa3f8f8acbb (diff) | |
download | mariadb-git-4dc30f3c170b9deab1ff0b472930baba5c65b01c.tar.gz |
MDEV-15019 - InnoDB: store ReadView on trx
This will allow us to reduce critical section protected by
trx_sys.mutex:
- no need to maintain global m_free list
- eliminate if (trx->read_view == NULL) condition.
On x86_64 sizeof(Readview) is 144 mostly due to padding, sizeof(trx_t)
with ReadView is 1200.
Also don't close ReadView for read-write transactions, just mark it
closed similarly to read-only.
Clean-up: removed n_prepared_recovered_trx and n_prepared_trx, which
accidentally re-appeared after some rebase.
Diffstat (limited to 'storage/innobase/read')
-rw-r--r-- | storage/innobase/read/read0read.cc | 199 |
1 files changed, 86 insertions, 113 deletions
diff --git a/storage/innobase/read/read0read.cc b/storage/innobase/read/read0read.cc index 4d79a12f52f..0ddb8c37feb 100644 --- a/storage/innobase/read/read0read.cc +++ b/storage/innobase/read/read0read.cc @@ -182,8 +182,9 @@ struct ViewCheck { void operator()(const ReadView* view) { + ut_ad(view->is_registered()); ut_a(m_prev_view == NULL - || view->is_closed() + || !view->is_open() || view->le(m_prev_view)); m_prev_view = view; @@ -325,7 +326,9 @@ ReadView::ReadView() m_up_limit_id(), m_creator_trx_id(), m_ids(), - m_low_limit_no() + m_low_limit_no(), + m_open(false), + m_registered(false) { ut_d(::memset(&m_view_list, 0x0, sizeof(m_view_list))); } @@ -337,34 +340,6 @@ ReadView::~ReadView() // Do nothing } -/** Constructor -@param size Number of views to pre-allocate */ -void MVCC::create(ulint size) -{ - UT_LIST_INIT(m_free, &ReadView::m_view_list); - UT_LIST_INIT(m_views, &ReadView::m_view_list); - - for (ulint i = 0; i < size; ++i) { - ReadView* view = UT_NEW_NOKEY(ReadView()); - - UT_LIST_ADD_FIRST(m_free, view); - } -} - -void MVCC::close() -{ - for (ReadView* view = UT_LIST_GET_FIRST(m_free); - view != NULL; - view = UT_LIST_GET_FIRST(m_free)) { - - UT_LIST_REMOVE(m_free, view); - - UT_DELETE(view); - } - - ut_a(UT_LIST_GET_LEN(m_views) == 0); -} - /** Copy the transaction ids from the source vector */ @@ -488,76 +463,89 @@ ReadView::complete() m_up_limit_id = !m_ids.empty() ? m_ids.front() : m_low_limit_id; ut_ad(m_up_limit_id <= m_low_limit_id); - - m_closed = false; + set_open(true); } -/** -Allocate and create a view. -@param view view owned by this class created for the - caller. Must be freed by calling view_close() -@param trx transaction instance of caller */ -void -MVCC::view_open(ReadView*& view, trx_t* trx) -{ - ut_ad(!srv_read_only_mode); - - /** If no new RW transaction has been started since the last view - was created then reuse the the existing view. */ - if (view != NULL) { - - uintptr_t p = reinterpret_cast<uintptr_t>(view); - - view = reinterpret_cast<ReadView*>(p & ~1); - - ut_ad(view->m_closed); - - /* NOTE: This can be optimised further, for now we only - resuse the view iff there are no active RW transactions. - - There is an inherent race here between purge and this - thread. Purge will skip views that are marked as closed. - Therefore we must set the low limit id after we reset the - closed status after the check. */ - - if (trx_is_autocommit_non_locking(trx) && view->empty()) { - view->m_closed = false; - - if (view->m_low_limit_id == trx_sys.get_max_trx_id()) { - return; - } else { - view->m_closed = true; - } - } - - mutex_enter(&trx_sys.mutex); - - UT_LIST_REMOVE(m_views, view); - - } else { - mutex_enter(&trx_sys.mutex); - if ((view = UT_LIST_GET_FIRST(m_free))) { - UT_LIST_REMOVE(m_free, view); - } else if (!(view = UT_NEW_NOKEY(ReadView()))) { - ib::error() << "Failed to allocate MVCC view"; - } - } - - if (view != NULL) { - - view->prepare(trx->id); - - view->complete(); - - UT_LIST_ADD_FIRST(m_views, view); +/** + Create a view. - ut_ad(!view->is_closed()); + Assigns a read view for a consistent read query. All the consistent reads + within the same transaction will get the same read view, which is created + when this function is first called for a new started transaction. - ut_ad(validate()); - } + @param trx transaction instance of caller +*/ - mutex_exit(&trx_sys.mutex); +void MVCC::view_open(trx_t* trx) +{ + if (srv_read_only_mode) + { + ut_ad(!trx->read_view.is_open()); + return; + } + else if (trx->read_view.is_open()) + return; + + /* + Reuse closed view if there were no read-write transactions since (and at) it's + creation time. + */ + if (trx->read_view.is_registered() && + trx_is_autocommit_non_locking(trx) && + trx->read_view.empty() && + trx->read_view.m_low_limit_id == trx_sys.get_max_trx_id()) + { + /* + Original comment states: there is an inherent race here between purge + and this thread. + + To avoid this race we should've checked trx_sys.get_max_trx_id() and + do trx->read_view.set_open(true) atomically under trx_sys.mutex + protection. But we're cutting edges to achieve great scalability. + + There're at least two types of concurrent threads interested in this + value: purge coordinator thread (see MVCC::clone_oldest_view()) and + InnoDB monitor thread (see lock_trx_print_wait_and_mvcc_state()). + + What bad things can happen because we allow this race? + + First, purge thread may be affected by this race condition only if this + view is the oldest open view. In other words this view is either last in + m_views list or there're no open views beyond. + + In this case purge may not catch this view and clone some younger view + instead. It might be kind of alright, because there were no read-write + transactions and there should be nothing to purge. Besides younger view + must have exactly the same values. + + Second, scary things start when there's a read-write transaction starting + concurrently. + + Speculative execution may reorder set_open() before get_max_trx_id(). In + this case purge thread has short gap to clone outdated view. Which is + probably not that bad: it just won't be able to purge things that it was + actually allowed to purge for a short while. + + This thread may as well get suspended after trx_sys.get_max_trx_id() and + before trx->read_view.set_open(true). New read-write transaction may get + started, committed and purged meanwhile. It is acceptable as well, since + this view doesn't see it. + */ + trx->read_view.set_open(true); + return; + } + + mutex_enter(&trx_sys.mutex); + trx->read_view.prepare(trx->id); + trx->read_view.complete(); + if (trx->read_view.is_registered()) + UT_LIST_REMOVE(m_views, &trx->read_view); + else + trx->read_view.set_registered(true); + UT_LIST_ADD_FIRST(m_views, &trx->read_view); + ut_ad(validate()); + mutex_exit(&trx_sys.mutex); } /** @@ -625,7 +613,7 @@ MVCC::clone_oldest_view(ReadView* view) oldest_view != NULL; oldest_view = UT_LIST_GET_PREV(m_view_list, oldest_view)) { - if (!oldest_view->is_closed()) + if (oldest_view->is_open()) { view->copy_prepare(*oldest_view); mutex_exit(&trx_sys.mutex); @@ -642,18 +630,18 @@ MVCC::clone_oldest_view(ReadView* view) /** @return the number of active views */ -ulint +size_t MVCC::size() const { mutex_enter(&trx_sys.mutex); - ulint size = 0; + size_t size = 0; for (const ReadView* view = UT_LIST_GET_FIRST(m_views); view != NULL; view = UT_LIST_GET_NEXT(m_view_list, view)) { - if (!view->is_closed()) { + if (view->is_open()) { ++size; } } @@ -662,18 +650,3 @@ MVCC::size() const return(size); } - -/** -Close a view created by the above function. -@param view view allocated by trx_open. */ - -void -MVCC::view_close(ReadView*& view) -{ - ut_ad(mutex_own(&trx_sys.mutex)); - view->close(); - UT_LIST_REMOVE(m_views, view); - UT_LIST_ADD_LAST(m_free, view); - ut_ad(validate()); - view = NULL; -} |