summaryrefslogtreecommitdiff
path: root/storage/innobase/read
diff options
context:
space:
mode:
authorSergey Vojtovich <svoj@mariadb.org>2018-01-20 17:45:42 +0400
committerSergey Vojtovich <svoj@mariadb.org>2018-01-22 16:23:15 +0400
commit4dc30f3c170b9deab1ff0b472930baba5c65b01c (patch)
treec0dd8d5920eebb89bb8d87a579164466c97b81d9 /storage/innobase/read
parentec32c050726bad8c0504c6b6b74a0fa3f8f8acbb (diff)
downloadmariadb-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.cc199
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;
-}