Commit 05fa4558 authored by Marko Mäkelä's avatar Marko Mäkelä

MDEV-22110 InnoDB unnecessarily writes unmodified pages

At least since commit 6a7be48b
InnoDB appears to be invoking buf_flush_note_modification() on pages
that were exclusively latched but not modified in a mini-transaction.

MTR_MEMO_MODIFY, mtr_t::modify(): Define not only in debug code,
but also in release code. We will set the MTR_MEMO_MODIFY flag
on the earliest mtr_t::m_memo entry that we find.

MTR_LOG_NONE: Only use this mode in cases where the previous
mode will be restored before anything is modified in the mini-transaction.

MTR_MEMO_PAGE_X_MODIFY, MTR_MEMO_PAGE_SX_MODIFY: The allowed flag
combinations that include MTR_MEMO_MODIFY.

ReleaseBlocks: Only invoke buf_flush_note_modification()
on those buffer pool blocks on which mtr_t::set_modified()
and mtr_t::modify() were invoked.
parent cf3c3cce
...@@ -1344,7 +1344,7 @@ btr_write_autoinc(dict_index_t* index, ib_uint64_t autoinc, bool reset) ...@@ -1344,7 +1344,7 @@ btr_write_autoinc(dict_index_t* index, ib_uint64_t autoinc, bool reset)
static void btr_page_reorganize_low(page_cur_t *cursor, dict_index_t *index, static void btr_page_reorganize_low(page_cur_t *cursor, dict_index_t *index,
mtr_t *mtr) mtr_t *mtr)
{ {
const mtr_log_t log_mode= mtr->set_log_mode(MTR_LOG_NONE); const mtr_log_t log_mode= mtr->set_log_mode(MTR_LOG_NO_REDO);
buf_block_t *const block= cursor->block; buf_block_t *const block= cursor->block;
......
...@@ -46,7 +46,7 @@ ibuf_mtr_start( ...@@ -46,7 +46,7 @@ ibuf_mtr_start(
mtr->enter_ibuf(); mtr->enter_ibuf();
if (high_level_read_only || srv_read_only_mode) { if (high_level_read_only || srv_read_only_mode) {
mtr_set_log_mode(mtr, MTR_LOG_NONE); mtr_set_log_mode(mtr, MTR_LOG_NO_REDO);
} }
} }
......
...@@ -141,10 +141,15 @@ struct mtr_t { ...@@ -141,10 +141,15 @@ struct mtr_t {
return static_cast<mtr_log_t>(m_log_mode); return static_cast<mtr_log_t>(m_log_mode);
} }
/** Change the logging mode. /** Change the logging mode.
@param mode logging mode @param mode logging mode
@return old mode */ @return old mode */
inline mtr_log_t set_log_mode(mtr_log_t mode); mtr_log_t set_log_mode(mtr_log_t mode)
{
const mtr_log_t old_mode= get_log_mode();
m_log_mode= mode & 3;
return old_mode;
}
/** Copy the tablespaces associated with the mini-transaction /** Copy the tablespaces associated with the mini-transaction
(needed for generating FILE_MODIFY records) (needed for generating FILE_MODIFY records)
...@@ -281,19 +286,13 @@ struct mtr_t { ...@@ -281,19 +286,13 @@ struct mtr_t {
private: private:
/** Note that the mini-transaction will modify data. */ /** Note that the mini-transaction will modify data. */
void flag_modified() { m_modifications = true; } void flag_modified() { m_modifications = true; }
#ifdef UNIV_DEBUG
/** Mark the given latched page as modified. /** Mark the given latched page as modified.
@param block page that will be modified */ @param block page that will be modified */
void modify(const buf_block_t& block); void modify(const buf_block_t& block);
public: public:
/** Note that the mini-transaction will modify a block. */ /** Note that the mini-transaction will modify a block. */
void set_modified(const buf_block_t &block) void set_modified(const buf_block_t &block)
{ flag_modified(); if (m_log_mode == MTR_LOG_ALL) modify(block); } { flag_modified(); if (m_log_mode != MTR_LOG_NONE) modify(block); }
#else /* UNIV_DEBUG */
public:
/** Note that the mini-transaction will modify a block. */
void set_modified(const buf_block_t &) { flag_modified(); }
#endif /* UNIV_DEBUG */
/** Set the state to not-modified. This will not log the changes. /** Set the state to not-modified. This will not log the changes.
This is only used during redo log apply, to avoid logging the changes. */ This is only used during redo log apply, to avoid logging the changes. */
......
...@@ -171,39 +171,3 @@ mtr_t::release_block_at_savepoint( ...@@ -171,39 +171,3 @@ mtr_t::release_block_at_savepoint(
slot->object = NULL; slot->object = NULL;
} }
/**
Changes the logging mode of a mini-transaction.
@return old mode */
mtr_log_t
mtr_t::set_log_mode(mtr_log_t mode)
{
ut_ad(mode >= MTR_LOG_ALL);
ut_ad(mode <= MTR_LOG_NO_REDO);
const mtr_log_t old_mode = get_log_mode();
switch (old_mode) {
case MTR_LOG_NO_REDO:
/* Once this mode is set, it must not be changed. */
ut_ad(mode == MTR_LOG_NO_REDO || mode == MTR_LOG_NONE);
return(old_mode);
case MTR_LOG_NONE:
if (mode == old_mode) {
/* Keep MTR_LOG_NONE. */
return(old_mode);
}
ut_ad(mode == MTR_LOG_ALL);
/* fall through */
case MTR_LOG_ALL:
/* MTR_LOG_NO_REDO can only be set before generating
any redo log records. */
ut_ad(mode != MTR_LOG_NO_REDO || m_log.empty());
m_log_mode = mode & 3;
return(old_mode);
}
ut_ad(0);
return(old_mode);
}
...@@ -41,8 +41,7 @@ enum mtr_log_t { ...@@ -41,8 +41,7 @@ enum mtr_log_t {
MTR_LOG_ALL = 0, MTR_LOG_ALL = 0,
/** Log no operations and dirty pages are not added to the flush list. /** Log no operations and dirty pages are not added to the flush list.
Set when applying log in crash recovery or when a modification of a Set for attempting modification of a ROW_FORMAT=COMPRESSED page. */
ROW_FORMAT=COMPRESSED page is attempted. */
MTR_LOG_NONE, MTR_LOG_NONE,
/** Don't generate REDO log but add dirty pages to flush list */ /** Don't generate REDO log but add dirty pages to flush list */
...@@ -329,9 +328,10 @@ enum mtr_memo_type_t { ...@@ -329,9 +328,10 @@ enum mtr_memo_type_t {
MTR_MEMO_BUF_FIX = RW_NO_LATCH, MTR_MEMO_BUF_FIX = RW_NO_LATCH,
#ifdef UNIV_DEBUG
MTR_MEMO_MODIFY = 16, MTR_MEMO_MODIFY = 16,
#endif /* UNIV_DEBUG */
MTR_MEMO_PAGE_X_MODIFY = MTR_MEMO_PAGE_X_FIX | MTR_MEMO_MODIFY,
MTR_MEMO_PAGE_SX_MODIFY = MTR_MEMO_PAGE_SX_FIX | MTR_MEMO_MODIFY,
MTR_MEMO_S_LOCK = RW_S_LATCH << 5, MTR_MEMO_S_LOCK = RW_S_LATCH << 5,
......
...@@ -2492,7 +2492,7 @@ void recv_recover_page(fil_space_t* space, buf_page_t* bpage) ...@@ -2492,7 +2492,7 @@ void recv_recover_page(fil_space_t* space, buf_page_t* bpage)
{ {
mtr_t mtr; mtr_t mtr;
mtr.start(); mtr.start();
mtr.set_log_mode(MTR_LOG_NONE); mtr.set_log_mode(MTR_LOG_NO_REDO);
ut_ad(bpage->state() == BUF_BLOCK_FILE_PAGE); ut_ad(bpage->state() == BUF_BLOCK_FILE_PAGE);
buf_block_t* block = reinterpret_cast<buf_block_t*>(bpage); buf_block_t* block = reinterpret_cast<buf_block_t*>(bpage);
...@@ -2576,7 +2576,7 @@ inline buf_block_t *recv_sys_t::recover_low(const page_id_t page_id, ...@@ -2576,7 +2576,7 @@ inline buf_block_t *recv_sys_t::recover_low(const page_id_t page_id,
else if (fil_space_t *space= fil_space_acquire_for_io(page_id.space())) else if (fil_space_t *space= fil_space_acquire_for_io(page_id.space()))
{ {
mtr.start(); mtr.start();
mtr.set_log_mode(MTR_LOG_NONE); mtr.set_log_mode(MTR_LOG_NO_REDO);
block= buf_page_create(space, page_id.page_no(), space->zip_size(), &mtr); block= buf_page_create(space, page_id.page_no(), space->zip_size(), &mtr);
p= recv_sys.pages.find(page_id); p= recv_sys.pages.find(page_id);
if (p == recv_sys.pages.end()) if (p == recv_sys.pages.end())
...@@ -2693,7 +2693,7 @@ void recv_sys_t::apply(bool last_batch) ...@@ -2693,7 +2693,7 @@ void recv_sys_t::apply(bool last_batch)
continue; continue;
case page_recv_t::RECV_NOT_PROCESSED: case page_recv_t::RECV_NOT_PROCESSED:
mtr.start(); mtr.start();
mtr.set_log_mode(MTR_LOG_NONE); mtr.set_log_mode(MTR_LOG_NO_REDO);
if (buf_block_t *block= buf_page_get_low(page_id, 0, RW_X_LATCH, if (buf_block_t *block= buf_page_get_low(page_id, 0, RW_X_LATCH,
nullptr, BUF_GET_IF_IN_POOL, nullptr, BUF_GET_IF_IN_POOL,
__FILE__, __LINE__, __FILE__, __LINE__,
......
...@@ -205,13 +205,6 @@ struct FindPage ...@@ -205,13 +205,6 @@ struct FindPage
static void memo_slot_release(mtr_memo_slot_t *slot) static void memo_slot_release(mtr_memo_slot_t *slot)
{ {
switch (slot->type) { switch (slot->type) {
#ifdef UNIV_DEBUG
default:
ut_ad("invalid type" == 0);
break;
case MTR_MEMO_MODIFY:
break;
#endif /* UNIV_DEBUG */
case MTR_MEMO_S_LOCK: case MTR_MEMO_S_LOCK:
rw_lock_s_unlock(reinterpret_cast<rw_lock_t*>(slot->object)); rw_lock_s_unlock(reinterpret_cast<rw_lock_t*>(slot->object));
break; break;
...@@ -228,12 +221,21 @@ static void memo_slot_release(mtr_memo_slot_t *slot) ...@@ -228,12 +221,21 @@ static void memo_slot_release(mtr_memo_slot_t *slot)
case MTR_MEMO_X_LOCK: case MTR_MEMO_X_LOCK:
rw_lock_x_unlock(reinterpret_cast<rw_lock_t*>(slot->object)); rw_lock_x_unlock(reinterpret_cast<rw_lock_t*>(slot->object));
break; break;
case MTR_MEMO_BUF_FIX: default:
case MTR_MEMO_PAGE_S_FIX: #ifdef UNIV_DEBUG
case MTR_MEMO_PAGE_SX_FIX: switch (slot->type & ~MTR_MEMO_MODIFY) {
case MTR_MEMO_PAGE_X_FIX: case MTR_MEMO_BUF_FIX:
case MTR_MEMO_PAGE_S_FIX:
case MTR_MEMO_PAGE_SX_FIX:
case MTR_MEMO_PAGE_X_FIX:
break;
default:
ut_ad("invalid type" == 0);
break;
}
#endif /* UNIV_DEBUG */
buf_block_t *block= reinterpret_cast<buf_block_t*>(slot->object); buf_block_t *block= reinterpret_cast<buf_block_t*>(slot->object);
buf_page_release_latch(block, slot->type); buf_page_release_latch(block, slot->type & ~MTR_MEMO_MODIFY);
block->unfix(); block->unfix();
break; break;
} }
...@@ -248,13 +250,6 @@ struct ReleaseLatches { ...@@ -248,13 +250,6 @@ struct ReleaseLatches {
if (!slot->object) if (!slot->object)
return true; return true;
switch (slot->type) { switch (slot->type) {
#ifdef UNIV_DEBUG
default:
ut_ad("invalid type" == 0);
break;
case MTR_MEMO_MODIFY:
break;
#endif /* UNIV_DEBUG */
case MTR_MEMO_S_LOCK: case MTR_MEMO_S_LOCK:
rw_lock_s_unlock(reinterpret_cast<rw_lock_t*>(slot->object)); rw_lock_s_unlock(reinterpret_cast<rw_lock_t*>(slot->object));
break; break;
...@@ -271,12 +266,21 @@ struct ReleaseLatches { ...@@ -271,12 +266,21 @@ struct ReleaseLatches {
case MTR_MEMO_SX_LOCK: case MTR_MEMO_SX_LOCK:
rw_lock_sx_unlock(reinterpret_cast<rw_lock_t*>(slot->object)); rw_lock_sx_unlock(reinterpret_cast<rw_lock_t*>(slot->object));
break; break;
case MTR_MEMO_BUF_FIX: default:
case MTR_MEMO_PAGE_S_FIX: #ifdef UNIV_DEBUG
case MTR_MEMO_PAGE_SX_FIX: switch (slot->type & ~MTR_MEMO_MODIFY) {
case MTR_MEMO_PAGE_X_FIX: case MTR_MEMO_BUF_FIX:
case MTR_MEMO_PAGE_S_FIX:
case MTR_MEMO_PAGE_SX_FIX:
case MTR_MEMO_PAGE_X_FIX:
break;
default:
ut_ad("invalid type" == 0);
break;
}
#endif /* UNIV_DEBUG */
buf_block_t *block= reinterpret_cast<buf_block_t*>(slot->object); buf_block_t *block= reinterpret_cast<buf_block_t*>(slot->object);
buf_page_release_latch(block, slot->type); buf_page_release_latch(block, slot->type & ~MTR_MEMO_MODIFY);
block->unfix(); block->unfix();
break; break;
} }
...@@ -308,50 +312,42 @@ struct DebugCheck { ...@@ -308,50 +312,42 @@ struct DebugCheck {
}; };
#endif #endif
/** Release a resource acquired by the mini-transaction. */ /** Release page latches held by the mini-transaction. */
struct ReleaseBlocks { struct ReleaseBlocks
/** Release specific object */ {
ReleaseBlocks(lsn_t start_lsn, lsn_t end_lsn) const lsn_t start, end;
: #ifdef UNIV_DEBUG
m_end_lsn(end_lsn), const mtr_buf_t &memo;
m_start_lsn(start_lsn)
{
/* Do nothing */
}
/** Add the modified page to the buffer flush list. */
void add_dirty_page_to_flush_list(mtr_memo_slot_t* slot) const
{
ut_ad(m_end_lsn > 0);
ut_ad(m_start_lsn > 0);
buf_block_t* block;
block = reinterpret_cast<buf_block_t*>(slot->object);
buf_flush_note_modification(block, m_start_lsn, m_end_lsn);
}
/** @return true always. */
bool operator()(mtr_memo_slot_t* slot) const
{
if (slot->object != NULL) {
if (slot->type == MTR_MEMO_PAGE_X_FIX
|| slot->type == MTR_MEMO_PAGE_SX_FIX) {
add_dirty_page_to_flush_list(slot);
}
}
return(true); ReleaseBlocks(lsn_t start, lsn_t end, const mtr_buf_t &memo) :
} start(start), end(end), memo(memo)
#else /* UNIV_DEBUG */
ReleaseBlocks(lsn_t start, lsn_t end, const mtr_buf_t&) :
start(start), end(end)
#endif /* UNIV_DEBUG */
{
ut_ad(start);
ut_ad(end);
}
/** Mini-transaction REDO start LSN */ /** @return true always */
lsn_t m_end_lsn; bool operator()(mtr_memo_slot_t* slot) const
{
if (!slot->object)
return true;
switch (slot->type) {
case MTR_MEMO_PAGE_X_MODIFY:
case MTR_MEMO_PAGE_SX_MODIFY:
break;
default:
ut_ad(!(slot->type & MTR_MEMO_MODIFY));
return true;
}
/** Mini-transaction REDO end LSN */ buf_flush_note_modification(static_cast<buf_block_t*>(slot->object),
lsn_t m_start_lsn; start, end);
return true;
}
}; };
/** Write the block contents to the REDO log */ /** Write the block contents to the REDO log */
...@@ -457,7 +453,8 @@ void mtr_t::commit() ...@@ -457,7 +453,8 @@ void mtr_t::commit()
} }
m_memo.for_each_block_in_reverse(CIterate<const ReleaseBlocks> m_memo.for_each_block_in_reverse(CIterate<const ReleaseBlocks>
(ReleaseBlocks(start_lsn, m_commit_lsn))); (ReleaseBlocks(start_lsn, m_commit_lsn,
m_memo)));
if (m_made_dirty) if (m_made_dirty)
log_flush_order_mutex_exit(); log_flush_order_mutex_exit();
...@@ -834,19 +831,32 @@ mtr_t::memo_contains_page_flagged( ...@@ -834,19 +831,32 @@ mtr_t::memo_contains_page_flagged(
? NULL : iteration.functor.get_block(); ? NULL : iteration.functor.get_block();
} }
/** Print info of an mtr handle. */
void
mtr_t::print() const
{
ib::info() << "Mini-transaction handle: memo size "
<< m_memo.size() << " bytes log size "
<< get_log()->size() << " bytes";
}
#endif /* UNIV_DEBUG */
/** Find a block, preferrably in MTR_MEMO_MODIFY state */ /** Find a block, preferrably in MTR_MEMO_MODIFY state */
struct FindModified struct FindModified
{ {
const mtr_memo_slot_t *found= nullptr; mtr_memo_slot_t *found= nullptr;
const buf_block_t& block; const buf_block_t& block;
FindModified(const buf_block_t &block) : block(block) {} FindModified(const buf_block_t &block) : block(block) {}
bool operator()(const mtr_memo_slot_t* slot) bool operator()(mtr_memo_slot_t *slot)
{ {
if (slot->object != &block) if (slot->object != &block)
return true; return true;
found= slot; found= slot;
return slot->type != MTR_MEMO_MODIFY; return !(slot->type & (MTR_MEMO_MODIFY |
MTR_MEMO_PAGE_X_FIX | MTR_MEMO_PAGE_SX_FIX));
} }
}; };
...@@ -854,20 +864,20 @@ struct FindModified ...@@ -854,20 +864,20 @@ struct FindModified
@param block page that will be modified */ @param block page that will be modified */
void mtr_t::modify(const buf_block_t &block) void mtr_t::modify(const buf_block_t &block)
{ {
Iterate<FindModified> iteration(block); if (UNIV_UNLIKELY(m_memo.empty()))
m_memo.for_each_block_in_reverse(iteration); {
ut_ad(iteration.functor.found); /* This must be PageConverter::update_page() in IMPORT TABLESPACE. */
if (iteration.functor.found->type != MTR_MEMO_MODIFY) ut_ad(!block.page.in_LRU_list);
memo_push(const_cast<buf_block_t*>(&block), MTR_MEMO_MODIFY); ut_ad(!buf_pool.is_uncompressed(&block));
} return;
}
/** Print info of an mtr handle. */ Iterate<FindModified> iteration((FindModified(block)));
void if (UNIV_UNLIKELY(m_memo.for_each_block(iteration)))
mtr_t::print() const {
{ ut_ad("modifying an unlatched page" == 0);
ib::info() << "Mini-transaction handle: memo size " return;
<< m_memo.size() << " bytes log size " }
<< get_log()->size() << " bytes"; iteration.functor.found->type= static_cast<mtr_memo_type_t>
(iteration.functor.found->type | MTR_MEMO_MODIFY);
} }
#endif /* UNIV_DEBUG */
...@@ -234,7 +234,7 @@ class RecIterator { ...@@ -234,7 +234,7 @@ class RecIterator {
memset(&m_cur, 0x0, sizeof(m_cur)); memset(&m_cur, 0x0, sizeof(m_cur));
/* Make page_cur_delete_rec() happy. */ /* Make page_cur_delete_rec() happy. */
m_mtr.start(); m_mtr.start();
m_mtr.set_log_mode(MTR_LOG_NONE); m_mtr.set_log_mode(MTR_LOG_NO_REDO);
} }
/** Position the cursor on the first user record. */ /** Position the cursor on the first user record. */
......
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