Commit 3e2ad0e9 authored by Marko Mäkelä's avatar Marko Mäkelä

Merge 10.5 into 10.6

parents db245e11 0de3be8c
SET @save_frequency=@@GLOBAL.innodb_purge_rseg_truncate_frequency;
SET @save_dbug=@@GLOBAL.debug_dbug;
SET GLOBAL innodb_purge_rseg_truncate_frequency=1;
CREATE TABLE t1(f1 INT NOT NULL, f2 int not null,
f3 int generated always as (f2 * 2) VIRTUAL,
primary key(f1), INDEX (f3))ENGINE=InnoDB;
connect con1,localhost,root,,,;
InnoDB 0 transactions not purged
START TRANSACTION WITH CONSISTENT SNAPSHOT;
connection default;
INSERT INTO t1(f1, f2) VALUES(1,2);
......@@ -18,5 +22,6 @@ commit;
disconnect con1;
disconnect con2;
connection default;
set global debug_dbug=default;
SET GLOBAL innodb_purge_rseg_truncate_frequency=@save_frequency;
SET GLOBAL debug_dbug=@save_dbug;
DROP TABLE t1;
--source include/have_innodb.inc
--source include/have_debug.inc
SET @save_frequency=@@GLOBAL.innodb_purge_rseg_truncate_frequency;
SET @save_dbug=@@GLOBAL.debug_dbug;
SET GLOBAL innodb_purge_rseg_truncate_frequency=1;
CREATE TABLE t1(f1 INT NOT NULL, f2 int not null,
f3 int generated always as (f2 * 2) VIRTUAL,
primary key(f1), INDEX (f3))ENGINE=InnoDB;
connect(con1,localhost,root,,,);
--source ../innodb/include/wait_all_purged.inc
START TRANSACTION WITH CONSISTENT SNAPSHOT;
connection default;
......@@ -26,5 +31,6 @@ commit;
disconnect con1;
disconnect con2;
connection default;
set global debug_dbug=default;
SET GLOBAL innodb_purge_rseg_truncate_frequency=@save_frequency;
SET GLOBAL debug_dbug=@save_dbug;
DROP TABLE t1;
SET @save_freq=@@GLOBAL.innodb_purge_rseg_truncate_frequency;
SET GLOBAL innodb_purge_rseg_truncate_frequency=1;
CREATE TABLE t (a int PRIMARY KEY, b int NOT NULL UNIQUE) engine = InnoDB;
InnoDB 0 transactions not purged
connect prevent_purge,localhost,root,,;
start transaction with consistent snapshot;
connect con_del_1,localhost,root,,;
......@@ -34,3 +37,4 @@ disconnect con_del_2;
connection default;
SET DEBUG_SYNC = 'RESET';
DROP TABLE t;
SET GLOBAL innodb_purge_rseg_truncate_frequency=@save_freq;
......@@ -7,6 +7,7 @@ SET GLOBAL innodb_purge_rseg_truncate_frequency = 1;
SET GLOBAL innodb_purge_rseg_truncate_frequency = 1;
CREATE TABLE t1(a INT PRIMARY KEY, b INT NOT NULL)
ROW_FORMAT=REDUNDANT ENGINE=InnoDB;
InnoDB 0 transactions not purged
connect prevent_purge,localhost,root;
START TRANSACTION WITH CONSISTENT SNAPSHOT;
connection default;
......@@ -19,7 +20,11 @@ UPDATE t1 SET b=4 WHERE a=3;
disconnect prevent_purge;
connection default;
InnoDB 0 transactions not purged
connection con1;
ROLLBACK;
disconnect con1;
connection default;
InnoDB 0 transactions not purged
FLUSH TABLE t1 FOR EXPORT;
Clustered index root page contents:
N_RECS=3; LEVEL=0
......
......@@ -3,6 +3,7 @@ SET GLOBAL innodb_purge_rseg_truncate_frequency=1;
CREATE TABLE t1(id INT PRIMARY key, val VARCHAR(16000)) ENGINE=InnoDB;
INSERT INTO t1 (id,val) SELECT 2*seq,'x' FROM seq_0_to_1023;
connect con1,localhost,root,,;
InnoDB 0 transactions not purged
START TRANSACTION WITH CONSISTENT SNAPSHOT;
connection default;
DELETE FROM t1 WHERE id=1788;
......
......@@ -9,12 +9,10 @@ SET GLOBAL innodb_purge_rseg_truncate_frequency= 1;
CREATE PROCEDURE insert_n(start int, end int)
BEGIN
DECLARE i INT DEFAULT start;
START TRANSACTION;
WHILE i <= end do
INSERT INTO t1 VALUES (1, 2, 3) ON DUPLICATE KEY UPDATE c = i;
SET i = i + 1;
END WHILE;
COMMIT;
END~~
CREATE FUNCTION num_pages_get()
RETURNS INT
......@@ -30,6 +28,7 @@ END~~
#
CREATE TABLE t1 (a INT, b INT, c INT, PRIMARY KEY(a,b), KEY (b,c))
ENGINE=InnoDB STATS_PERSISTENT=0;
InnoDB 0 transactions not purged
BEGIN;
SELECT * FROM t1;
a b c
......@@ -38,20 +37,24 @@ a b c
#
connect con2, localhost, root,,;
connection con2;
BEGIN;
INSERT INTO t1 VALUES (1, 2, 3) ON DUPLICATE KEY UPDATE c = NULL;
CALL insert_n(1, 50);;
connect con3, localhost, root,,;
connection con3;
BEGIN;
CALL insert_n(51, 100);;
connection con2;
COMMIT;
connection con3;
INSERT INTO t1 VALUES (1, 2, 1) ON DUPLICATE KEY UPDATE c = NULL;
COMMIT;
connection default;
#
# Connect to default and record how many pages were accessed
# when selecting the record using the secondary key.
#
InnoDB 4 transactions not purged
InnoDB 2 transactions not purged
SET @num_pages_1 = num_pages_get();
SELECT * FROM t1 force index (b);
a b c
......
......@@ -3,8 +3,11 @@
source include/have_debug.inc;
source include/have_debug_sync.inc;
SET @save_freq=@@GLOBAL.innodb_purge_rseg_truncate_frequency;
SET GLOBAL innodb_purge_rseg_truncate_frequency=1;
CREATE TABLE t (a int PRIMARY KEY, b int NOT NULL UNIQUE) engine = InnoDB;
--source include/wait_all_purged.inc
--connect(prevent_purge,localhost,root,,)
start transaction with consistent snapshot;
......@@ -80,4 +83,5 @@ INSERT INTO t VALUES(30, 20);
SET DEBUG_SYNC = 'RESET';
DROP TABLE t;
SET GLOBAL innodb_purge_rseg_truncate_frequency=@save_freq;
--source include/wait_until_count_sessions.inc
......@@ -14,6 +14,7 @@ SET GLOBAL innodb_purge_rseg_truncate_frequency = 1;
CREATE TABLE t1(a INT PRIMARY KEY, b INT NOT NULL)
ROW_FORMAT=REDUNDANT ENGINE=InnoDB;
--source include/wait_all_purged.inc
--connect (prevent_purge,localhost,root)
START TRANSACTION WITH CONSISTENT SNAPSHOT;
......@@ -33,7 +34,12 @@ UPDATE t1 SET b=4 WHERE a=3;
# Initiate a full purge, which should reset the DB_TRX_ID except for a=3.
--source include/wait_all_purged.inc
# Initiate a ROLLBACK of the update, which should reset the DB_TRX_ID for a=3.
--connection con1
ROLLBACK;
--disconnect con1
--connection default
# Reset the DB_TRX_ID for the hidden ADD COLUMN metadata record.
--source include/wait_all_purged.inc
FLUSH TABLE t1 FOR EXPORT;
# The following is based on innodb.table_flags:
......
......@@ -9,6 +9,7 @@ CREATE TABLE t1(id INT PRIMARY key, val VARCHAR(16000)) ENGINE=InnoDB;
INSERT INTO t1 (id,val) SELECT 2*seq,'x' FROM seq_0_to_1023;
connect(con1,localhost,root,,);
source include/wait_all_purged.inc;
# Prevent purge.
START TRANSACTION WITH CONSISTENT SNAPSHOT;
connection default;
......
......@@ -13,12 +13,10 @@ DELIMITER ~~;
CREATE PROCEDURE insert_n(start int, end int)
BEGIN
DECLARE i INT DEFAULT start;
START TRANSACTION;
WHILE i <= end do
INSERT INTO t1 VALUES (1, 2, 3) ON DUPLICATE KEY UPDATE c = i;
SET i = i + 1;
END WHILE;
COMMIT;
END~~
CREATE FUNCTION num_pages_get()
......@@ -37,6 +35,7 @@ DELIMITER ;~~
--echo #
CREATE TABLE t1 (a INT, b INT, c INT, PRIMARY KEY(a,b), KEY (b,c))
ENGINE=InnoDB STATS_PERSISTENT=0;
--source include/wait_all_purged.inc
BEGIN;
SELECT * FROM t1;
......@@ -45,18 +44,22 @@ SELECT * FROM t1;
--echo #
connect (con2, localhost, root,,);
connection con2;
BEGIN;
INSERT INTO t1 VALUES (1, 2, 3) ON DUPLICATE KEY UPDATE c = NULL;
--send CALL insert_n(1, 50);
connect (con3, localhost, root,,);
connection con3;
BEGIN;
--send CALL insert_n(51, 100);
connection con2;
reap;
COMMIT;
connection con3;
reap;
INSERT INTO t1 VALUES (1, 2, 1) ON DUPLICATE KEY UPDATE c = NULL;
COMMIT;
connection default;
......@@ -64,7 +67,7 @@ connection default;
--echo # Connect to default and record how many pages were accessed
--echo # when selecting the record using the secondary key.
--echo #
--let $wait_all_purged=4
--let $wait_all_purged=2
--source include/wait_all_purged.inc
SET @num_pages_1 = num_pages_get();
SELECT * FROM t1 force index (b);
......
......@@ -65,53 +65,44 @@ struct alignas(CPU_LEVEL1_DCACHE_LINESIZE) trx_rseg_t
/** length of the TRX_RSEG_HISTORY list (number of transactions) */
uint32_t history_size;
/** Last known transaction that has not been purged yet,
or 0 if everything has been purged. */
trx_id_t needs_purge;
private:
/** Reference counter to track rseg allocated transactions,
with SKIP and NEEDS_PURGE flags. */
/** Reference counter to track is_persistent() transactions,
with SKIP flag. */
std::atomic<uint32_t> ref;
/** Whether undo tablespace truncation is pending */
static constexpr uint32_t SKIP= 1;
/** Whether the log segment needs purge */
static constexpr uint32_t NEEDS_PURGE= 2;
/** Transaction reference count multiplier */
static constexpr uint32_t REF= 4;
static constexpr uint32_t REF= 2;
uint32_t ref_load() const { return ref.load(std::memory_order_relaxed); }
/** Set a bit in ref */
template<bool needs_purge> void ref_set()
/** Set the SKIP bit */
void ref_set_skip()
{
static_assert(SKIP == 1U << 0, "compatibility");
static_assert(NEEDS_PURGE == 1U << 1, "compatibility");
static_assert(SKIP == 1U, "compatibility");
#if defined __GNUC__ && (defined __i386__ || defined __x86_64__)
if (needs_purge)
__asm__ __volatile__("lock btsl $1, %0" : "+m" (ref));
else
__asm__ __volatile__("lock btsl $0, %0" : "+m" (ref));
#elif defined _MSC_VER && (defined _M_IX86 || defined _M_X64)
_interlockedbittestandset(reinterpret_cast<volatile long*>(&ref),
needs_purge);
_interlockedbittestandset(reinterpret_cast<volatile long*>(&ref), 0);
#else
ref.fetch_or(needs_purge ? NEEDS_PURGE : SKIP, std::memory_order_relaxed);
ref.fetch_or(SKIP, std::memory_order_relaxed);
#endif
}
/** Clear a bit in ref */
template<bool needs_purge> void ref_reset()
void ref_reset_skip()
{
static_assert(SKIP == 1U << 0, "compatibility");
static_assert(NEEDS_PURGE == 1U << 1, "compatibility");
static_assert(SKIP == 1U, "compatibility");
#if defined __GNUC__ && (defined __i386__ || defined __x86_64__)
if (needs_purge)
__asm__ __volatile__("lock btrl $1, %0" : "+m" (ref));
else
__asm__ __volatile__("lock btrl $0, %0" : "+m" (ref));
#elif defined _MSC_VER && (defined _M_IX86 || defined _M_X64)
_interlockedbittestandreset(reinterpret_cast<volatile long*>(&ref),
needs_purge);
_interlockedbittestandreset(reinterpret_cast<volatile long*>(&ref), 0);
#else
ref.fetch_and(needs_purge ? ~NEEDS_PURGE : ~SKIP,
std::memory_order_relaxed);
ref.fetch_and(~SKIP, std::memory_order_relaxed);
#endif
}
......@@ -125,26 +116,20 @@ struct alignas(CPU_LEVEL1_DCACHE_LINESIZE) trx_rseg_t
void destroy();
/** Note that undo tablespace truncation was started. */
void set_skip_allocation() { ut_ad(is_persistent()); ref_set<false>(); }
void set_skip_allocation() { ut_ad(is_persistent()); ref_set_skip(); }
/** Note that undo tablespace truncation was completed. */
void clear_skip_allocation()
{
ut_ad(is_persistent());
#if defined DBUG_OFF
ref_reset<false>();
ref_reset_skip();
#else
ut_d(auto r=) ref.fetch_and(~SKIP, std::memory_order_relaxed);
ut_ad(r == SKIP);
#endif
}
/** Note that the rollback segment requires purge. */
void set_needs_purge() { ref_set<true>(); }
/** Note that the rollback segment will not require purge. */
void clear_needs_purge() { ref_reset<true>(); }
/** @return whether the segment is marked for undo truncation */
bool skip_allocation() const { return ref_load() & SKIP; }
/** @return whether the segment needs purge */
bool needs_purge() const { return ref_load() & NEEDS_PURGE; }
/** Increment the reference count */
void acquire()
{ ut_d(auto r=) ref.fetch_add(REF); ut_ad(!(r & SKIP)); }
......
......@@ -246,12 +246,10 @@ trx_undo_free_at_shutdown(trx_t *trx);
@param[in,out] rseg rollback segment
@param[in] id rollback segment slot
@param[in] page_no undo log segment page number
@param[in,out] max_trx_id the largest observed transaction ID
@return the undo log
@retval nullptr on error */
trx_undo_t *
trx_undo_mem_create_at_db_start(trx_rseg_t *rseg, ulint id, uint32_t page_no,
trx_id_t &max_trx_id);
trx_undo_mem_create_at_db_start(trx_rseg_t *rseg, ulint id, uint32_t page_no);
#endif /* !UNIV_INNOCHECKSUM */
......@@ -493,6 +491,8 @@ or 0 if the transaction has not been committed */
/** Before MariaDB 10.3.1, when purge did not reset DB_TRX_ID of
surviving user records, this used to be called TRX_UNDO_DEL_MARKS.
This field is redundant; it is only being read by some debug assertions.
The value 1 indicates that purge needs to process the undo log segment.
The value 0 indicates that all of it has been processed, and
trx_purge_free_segment() has been invoked, so the log is not safe to access.
......
......@@ -263,6 +263,7 @@ trx_purge_add_undo_to_history(const trx_t* trx, trx_undo_t*& undo, mtr_t* mtr)
+ undo->hdr_offset;
ut_ad(mach_read_from_2(undo_header + TRX_UNDO_NEEDS_PURGE) <= 1);
ut_ad(rseg->needs_purge > trx->id);
if (UNIV_UNLIKELY(mach_read_from_4(TRX_RSEG + TRX_RSEG_FORMAT
+ rseg_header->page.frame))) {
......@@ -356,7 +357,6 @@ trx_purge_add_undo_to_history(const trx_t* trx, trx_undo_t*& undo, mtr_t* mtr)
rseg->last_page_no = undo->hdr_page_no;
rseg->set_last_commit(undo->hdr_offset,
trx->rw_trx_hash_element->no);
rseg->set_needs_purge();
}
rseg->history_size++;
......@@ -387,24 +387,21 @@ static dberr_t trx_purge_remove_log_hdr(buf_block_t *rseg, buf_block_t* log,
MY_ATTRIBUTE((nonnull, warn_unused_result))
/** Free an undo log segment, and remove the header from the history list.
@param[in,out] mtr mini-transaction
@param[in,out] rseg rollback segment
@param[in] hdr_addr file address of log_hdr
@return error code */
static dberr_t trx_purge_free_segment(trx_rseg_t *rseg, fil_addr_t hdr_addr)
static dberr_t
trx_purge_free_segment(mtr_t &mtr, trx_rseg_t* rseg, fil_addr_t hdr_addr)
{
const page_id_t hdr_page_id{rseg->space->id, hdr_addr.page};
mtr_t mtr;
mtr.commit();
mtr.start();
/* We only need the latch to maintain rseg->curr_size. To follow the
latching order, we must acquire it before acquiring any related
page latch. */
rseg->latch.wr_lock(SRW_LOCK_CALL);
const page_id_t hdr_page_id{rseg->space->id, hdr_addr.page};
dberr_t err;
buf_block_t *rseg_hdr= rseg->get(&mtr, &err);
if (!rseg_hdr)
goto func_exit;
return err;
if (buf_block_t *block= buf_page_get_gen(hdr_page_id, 0, RW_X_LATCH,
nullptr, BUF_GET_POSSIBLY_FREED,
&mtr, &err))
......@@ -419,12 +416,10 @@ static dberr_t trx_purge_free_segment(trx_rseg_t *rseg, fil_addr_t hdr_addr)
while (!fseg_free_step_not_header(TRX_UNDO_SEG_HDR + TRX_UNDO_FSEG_HEADER +
block->page.frame, &mtr))
{
rseg->latch.wr_unlock();
rseg_hdr->fix();
block->fix();
mtr.commit();
mtr.start();
rseg->latch.wr_lock(SRW_LOCK_CALL);
rseg_hdr->page.lock.x_lock();
block->page.lock.x_lock();
mtr.memo_push(rseg_hdr, MTR_MEMO_PAGE_X_FIX);
......@@ -443,13 +438,10 @@ static dberr_t trx_purge_free_segment(trx_rseg_t *rseg, fil_addr_t hdr_addr)
could become inaccessible garbage in the file space. */
err= trx_purge_remove_log_hdr(rseg_hdr, block, hdr_addr.boffset, &mtr);
if (UNIV_UNLIKELY(err != DB_SUCCESS))
goto func_exit;
return err;
byte *hist= TRX_RSEG + TRX_RSEG_HISTORY_SIZE + rseg_hdr->page.frame;
if (UNIV_UNLIKELY(mach_read_from_4(hist) < seg_size))
{
err= DB_CORRUPTION;
goto func_exit;
}
return DB_CORRUPTION;
mtr.write<4>(*rseg_hdr, hist, mach_read_from_4(hist) - seg_size);
/* Here we assume that a file segment with just the header page
......@@ -464,9 +456,6 @@ static dberr_t trx_purge_free_segment(trx_rseg_t *rseg, fil_addr_t hdr_addr)
rseg->curr_size -= seg_size;
}
func_exit:
rseg->latch.wr_unlock();
mtr.commit();
return err;
}
......@@ -484,8 +473,6 @@ trx_purge_truncate_rseg_history(
mtr_t mtr;
mtr.start();
ut_ad(rseg.is_persistent());
rseg.latch.wr_lock(SRW_LOCK_CALL);
dberr_t err;
buf_block_t* rseg_hdr = rseg.get(&mtr, &err);
......@@ -501,7 +488,6 @@ trx_purge_truncate_rseg_history(
loop:
if (hdr_addr.page == FIL_NULL) {
func_exit:
rseg.latch.wr_unlock();
mtr.commit();
return err;
}
......@@ -533,38 +519,30 @@ trx_purge_truncate_rseg_history(
prev_hdr_addr.boffset = static_cast<uint16_t>(prev_hdr_addr.boffset
- TRX_UNDO_HISTORY_NODE);
if (mach_read_from_2(TRX_UNDO_SEG_HDR + TRX_UNDO_STATE
if (!rseg.is_referenced()
&& rseg.needs_purge <= (purge_sys.head.trx_no
? purge_sys.head.trx_no
: purge_sys.tail.trx_no)
&& mach_read_from_2(TRX_UNDO_SEG_HDR + TRX_UNDO_STATE
+ block->page.frame)
== TRX_UNDO_TO_PURGE
&& !mach_read_from_2(block->page.frame + hdr_addr.boffset
+ TRX_UNDO_NEXT_LOG)) {
/* We can free the whole log segment */
rseg.latch.wr_unlock();
mtr.commit();
/* calls the trx_purge_remove_log_hdr()
inside trx_purge_free_segment(). */
err = trx_purge_free_segment(&rseg, hdr_addr);
if (err != DB_SUCCESS) {
return err;
}
/* We can free the whole log segment.
This will call trx_purge_remove_log_hdr(). */
err = trx_purge_free_segment(mtr, &rseg, hdr_addr);
} else {
/* Remove the log hdr from the rseg history. */
rseg.history_size--;
err = trx_purge_remove_log_hdr(rseg_hdr, block,
hdr_addr.boffset, &mtr);
if (err != DB_SUCCESS) {
goto func_exit;
}
rseg.history_size--;
rseg.latch.wr_unlock();
mtr.commit();
if (err != DB_SUCCESS) {
return err;
}
mtr.start();
rseg.latch.wr_lock(SRW_LOCK_CALL);
hdr_addr = prev_hdr_addr;
......@@ -640,8 +618,13 @@ TRANSACTIONAL_TARGET static void trx_purge_truncate_history()
dberr_t err= DB_SUCCESS;
for (auto &rseg : trx_sys.rseg_array)
if (rseg.space)
{
ut_ad(rseg.is_persistent());
rseg.latch.wr_lock(SRW_LOCK_CALL);
if (dberr_t e= trx_purge_truncate_rseg_history(rseg, head))
err= e;
rseg.latch.wr_unlock();
}
if (err != DB_SUCCESS || srv_undo_tablespaces_active < 2)
return;
......@@ -693,26 +676,21 @@ TRANSACTIONAL_TARGET static void trx_purge_truncate_history()
{
if (rseg.space != &space)
continue;
#ifdef SUX_LOCK_GENERIC
rseg.latch.rd_lock(SRW_LOCK_CALL);
#else
transactional_shared_lock_guard<srw_spin_lock> g{rseg.latch};
#endif
ut_ad(rseg.skip_allocation());
if (rseg.is_referenced())
if (rseg.is_referenced() || rseg.needs_purge > head.trx_no)
{
not_free:
#ifdef SUX_LOCK_GENERIC
rseg.latch.rd_unlock();
#endif
return;
}
if (rseg.curr_size != 1)
{
ut_ad(UT_LIST_GET_LEN(rseg.undo_list) == 0);
/* Check if all segments are cached and safe to remove. */
ulint cached= 0;
for (trx_undo_t *undo= UT_LIST_GET_FIRST(rseg.undo_cached); undo;
for (const trx_undo_t *undo= UT_LIST_GET_FIRST(rseg.undo_cached); undo;
undo= UT_LIST_GET_NEXT(undo_list, undo))
{
if (head.trx_no < undo->trx_id)
......@@ -722,14 +700,10 @@ TRANSACTIONAL_TARGET static void trx_purge_truncate_history()
}
ut_ad(rseg.curr_size > cached);
if (rseg.curr_size > cached + 1)
goto not_free;
}
#ifdef SUX_LOCK_GENERIC
rseg.latch.rd_unlock();
#endif
}
ib::info() << "Truncating " << file->name;
......@@ -848,7 +822,9 @@ TRANSACTIONAL_TARGET static void trx_purge_truncate_history()
if (rseg.space != &space)
continue;
dberr_t err;
ut_ad(!rseg.is_referenced());
ut_ad(rseg.needs_purge <= head.trx_no);
buf_block_t *rblock= trx_rseg_header_create(&space,
&rseg - trx_sys.rseg_array,
trx_sys.get_max_trx_id(),
......@@ -883,10 +859,6 @@ TRANSACTIONAL_TARGET static void trx_purge_truncate_history()
log_buffer_flush_to_disk();
DBUG_SUICIDE(););
for (auto &rseg : trx_sys.rseg_array)
if (rseg.space == &space)
rseg.clear_skip_allocation();
ib::info() << "Truncated " << file->name;
purge_sys.truncate.last= purge_sys.truncate.current;
ut_ad(&space == purge_sys.truncate.current);
......@@ -946,7 +918,6 @@ static void trx_purge_rseg_get_next_history_log(
/* Read the previous log header. */
mtr.start();
byte needs_purge= 0;
trx_id_t trx_no= 0;
if (const buf_block_t* undo_page=
......@@ -957,7 +928,6 @@ static void trx_purge_rseg_get_next_history_log(
trx_no= mach_read_from_8(log_hdr + TRX_UNDO_TRX_NO);
ut_ad(mach_read_from_2(log_hdr + TRX_UNDO_NEEDS_PURGE) <= 1);
needs_purge= log_hdr[TRX_UNDO_NEEDS_PURGE + 1];
}
mtr.commit();
......@@ -969,11 +939,6 @@ static void trx_purge_rseg_get_next_history_log(
purge_sys.rseg->last_page_no= prev_log_addr.page;
purge_sys.rseg->set_last_commit(prev_log_addr.boffset, trx_no);
if (needs_purge)
purge_sys.rseg->set_needs_purge();
else
purge_sys.rseg->clear_needs_purge();
/* Purge can also produce events, however these are already ordered
in the rollback segment and any user generated event will be greater
than the events that Purge produces. ie. Purge can never produce
......@@ -995,7 +960,7 @@ static void trx_purge_read_undo_rec()
purge_sys.hdr_offset = purge_sys.rseg->last_offset();
page_no = purge_sys.hdr_page_no = purge_sys.rseg->last_page_no;
if (purge_sys.rseg->needs_purge()) {
if (purge_sys.rseg->needs_purge) {
mtr_t mtr;
mtr.start();
const buf_block_t* undo_page;
......
......@@ -399,7 +399,7 @@ void trx_rseg_t::reinit(uint32_t page)
}
ut_ad(!is_referenced());
clear_needs_purge();
needs_purge= 0;
last_commit_and_offset= 0;
last_page_no= FIL_NULL;
curr_size= 1;
......@@ -407,10 +407,9 @@ void trx_rseg_t::reinit(uint32_t page)
/** Read the undo log lists.
@param[in,out] rseg rollback segment
@param[in,out] max_trx_id maximum observed transaction identifier
@param[in] rseg_header rollback segment header
@return error code */
static dberr_t trx_undo_lists_init(trx_rseg_t *rseg, trx_id_t &max_trx_id,
static dberr_t trx_undo_lists_init(trx_rseg_t *rseg,
const buf_block_t *rseg_header)
{
ut_ad(srv_force_recovery < SRV_FORCE_NO_UNDO_LOG_SCAN);
......@@ -420,8 +419,8 @@ static dberr_t trx_undo_lists_init(trx_rseg_t *rseg, trx_id_t &max_trx_id,
uint32_t page_no= trx_rsegf_get_nth_undo(rseg_header, i);
if (page_no != FIL_NULL)
{
const trx_undo_t *undo= trx_undo_mem_create_at_db_start(rseg, i, page_no,
max_trx_id);
const trx_undo_t *undo=
trx_undo_mem_create_at_db_start(rseg, i, page_no);
if (!undo)
return DB_CORRUPTION;
rseg->curr_size+= undo->size;
......@@ -434,11 +433,9 @@ static dberr_t trx_undo_lists_init(trx_rseg_t *rseg, trx_id_t &max_trx_id,
/** Restore the state of a persistent rollback segment.
@param[in,out] rseg persistent rollback segment
@param[in,out] max_trx_id maximum observed transaction identifier
@param[in,out] mtr mini-transaction
@return error code */
static dberr_t trx_rseg_mem_restore(trx_rseg_t *rseg, trx_id_t &max_trx_id,
mtr_t *mtr)
static dberr_t trx_rseg_mem_restore(trx_rseg_t *rseg, mtr_t *mtr)
{
if (!rseg->space)
return DB_TABLESPACE_NOT_FOUND;
......@@ -454,8 +451,8 @@ static dberr_t trx_rseg_mem_restore(trx_rseg_t *rseg, trx_id_t &max_trx_id,
trx_id_t id= mach_read_from_8(TRX_RSEG + TRX_RSEG_MAX_TRX_ID +
rseg_hdr->page.frame);
if (id > max_trx_id)
max_trx_id= id;
if (id > rseg->needs_purge)
rseg->needs_purge= id;
const byte *binlog_name=
TRX_RSEG + TRX_RSEG_BINLOG_NAME + rseg_hdr->page.frame;
......@@ -491,7 +488,7 @@ static dberr_t trx_rseg_mem_restore(trx_rseg_t *rseg, trx_id_t &max_trx_id,
rseg->curr_size = mach_read_from_4(TRX_RSEG + TRX_RSEG_HISTORY_SIZE +
rseg_hdr->page.frame) + 1;
err= trx_undo_lists_init(rseg, max_trx_id, rseg_hdr);
err= trx_undo_lists_init(rseg, rseg_hdr);
if (err != DB_SUCCESS);
else if (auto len= flst_get_len(TRX_RSEG + TRX_RSEG_HISTORY +
rseg_hdr->page.frame))
......@@ -512,19 +509,16 @@ static dberr_t trx_rseg_mem_restore(trx_rseg_t *rseg, trx_id_t &max_trx_id,
trx_id_t id= mach_read_from_8(block->page.frame + node_addr.boffset +
TRX_UNDO_TRX_ID);
if (id > max_trx_id)
max_trx_id= id;
if (id > rseg->needs_purge)
rseg->needs_purge= id;
id= mach_read_from_8(block->page.frame + node_addr.boffset +
TRX_UNDO_TRX_NO);
if (id > max_trx_id)
max_trx_id= id;
if (id > rseg->needs_purge)
rseg->needs_purge= id;
rseg->set_last_commit(node_addr.boffset, id);
unsigned purge= mach_read_from_2(block->page.frame + node_addr.boffset +
TRX_UNDO_NEEDS_PURGE);
ut_ad(purge <= 1);
if (purge != 0)
rseg->set_needs_purge();
ut_ad(mach_read_from_2(block->page.frame + node_addr.boffset +
TRX_UNDO_NEEDS_PURGE) <= 1);
if (rseg->last_page_no != FIL_NULL)
/* There is no need to cover this operation by the purge
......@@ -597,9 +591,11 @@ dberr_t trx_rseg_array_init()
sys, rseg_id)),
page_no);
ut_ad(rseg.is_persistent());
if ((err = trx_rseg_mem_restore(
&rseg, max_trx_id, &mtr))
!= DB_SUCCESS) {
err = trx_rseg_mem_restore(&rseg, &mtr);
if (rseg.needs_purge > max_trx_id) {
max_trx_id = rseg.needs_purge;
}
if (err != DB_SUCCESS) {
mtr.commit();
break;
}
......
......@@ -651,6 +651,7 @@ static dberr_t trx_resurrect(trx_undo_t *undo, trx_rseg_t *rseg,
uint64_t *rows_to_undo)
{
trx_state_t state;
ut_ad(rseg->needs_purge >= undo->trx_id);
/*
This is single-threaded startup code, we do not need the
protection of trx->mutex here.
......@@ -673,6 +674,7 @@ static dberr_t trx_resurrect(trx_undo_t *undo, trx_rseg_t *rseg,
return DB_SUCCESS;
}
rseg->acquire();
trx_t *trx= trx_create();
trx->state= state;
ut_d(trx->start_file= __FILE__);
......@@ -681,12 +683,6 @@ static dberr_t trx_resurrect(trx_undo_t *undo, trx_rseg_t *rseg,
trx->rsegs.m_redo.undo= undo;
trx->undo_no= undo->top_undo_no + 1;
trx->rsegs.m_redo.rseg= rseg;
/*
For transactions with active data will not have rseg size = 1
or will not qualify for purge limit criteria. So it is safe to increment
this trx_ref_count w/o mutex protection.
*/
trx->rsegs.m_redo.rseg->acquire();
trx->xid= undo->xid;
trx->id= undo->trx_id;
trx->is_recovered= true;
......@@ -759,6 +755,7 @@ dberr_t trx_lists_init_at_db_start()
ut_ad(trx->is_recovered);
ut_ad(trx->rsegs.m_redo.rseg == &rseg);
ut_ad(rseg.is_referenced());
ut_ad(rseg.needs_purge);
trx->rsegs.m_redo.undo = undo;
if (undo->top_undo_no >= trx->undo_no) {
......@@ -794,20 +791,18 @@ dberr_t trx_lists_init_at_db_start()
/** Assign a persistent rollback segment in a round-robin fashion,
evenly distributed between 0 and innodb_undo_logs-1
@return persistent rollback segment
@retval NULL if innodb_read_only */
static trx_rseg_t* trx_assign_rseg_low()
@param trx transaction */
static void trx_assign_rseg_low(trx_t *trx)
{
if (high_level_read_only) {
ut_ad(!srv_available_undo_logs);
return(NULL);
}
ut_ad(!trx->rsegs.m_redo.rseg);
ut_ad(srv_available_undo_logs == TRX_SYS_N_RSEGS);
/* The first slot is always assigned to the system tablespace. */
ut_ad(trx_sys.rseg_array[0].space == fil_system.sys_space);
trx_sys.register_rw(trx);
ut_ad(trx->id);
/* Choose a rollback segment evenly distributed between 0 and
innodb_undo_logs-1 in a round-robin fashion, skipping those
undo tablespaces that are scheduled for truncation. */
......@@ -821,7 +816,7 @@ static trx_rseg_t* trx_assign_rseg_low()
bool look_for_rollover = false;
#endif /* UNIV_DEBUG */
bool allocated = false;
bool allocated;
do {
for (;;) {
......@@ -871,9 +866,7 @@ static trx_rseg_t* trx_assign_rseg_low()
allocated = rseg->acquire_if_available();
} while (!allocated);
ut_ad(rseg->is_referenced());
ut_ad(rseg->is_persistent());
return(rseg);
trx->rsegs.m_redo.rseg = rseg;
}
/** Assign a rollback segment for modifying temporary tables.
......@@ -956,15 +949,11 @@ trx_start_low(
if (!trx->read_only
&& (!trx->mysql_thd || read_write || trx->dict_operation)) {
/* Temporary rseg is assigned only if the transaction
updates a temporary table */
trx->rsegs.m_redo.rseg = trx_assign_rseg_low();
ut_ad(trx->rsegs.m_redo.rseg != 0
|| srv_read_only_mode
|| srv_force_recovery >= SRV_FORCE_NO_TRX_UNDO);
trx_sys.register_rw(trx);
if (!high_level_read_only) {
trx_assign_rseg_low(trx);
}
} else {
if (!trx->is_autocommit_non_locking()) {
......@@ -1055,25 +1044,21 @@ trx_write_serialisation_history(
trx_undo_t*& undo = trx->rsegs.m_redo.undo;
if (!undo) {
return;
}
ut_ad(!trx->read_only);
ut_ad(!undo || undo->rseg == rseg);
rseg->latch.wr_lock(SRW_LOCK_CALL);
/* Assign the transaction serialisation number and add any
undo log to the purge queue. */
trx_serialise(trx);
if (undo) {
rseg->latch.wr_lock(SRW_LOCK_CALL);
ut_ad(undo->rseg == rseg);
trx_serialise(trx);
UT_LIST_REMOVE(rseg->undo_list, undo);
trx_purge_add_undo_to_history(trx, undo, mtr);
}
MONITOR_INC(MONITOR_TRX_COMMIT_UNDO);
rseg->latch.wr_unlock();
}
MONITOR_INC(MONITOR_TRX_COMMIT_UNDO);
rseg->release();
}
/********************************************************************
......@@ -1318,10 +1303,6 @@ TRANSACTIONAL_INLINE inline void trx_t::commit_in_memory(const mtr_t *mtr)
release_locks();
}
if (trx_rseg_t *rseg= rsegs.m_redo.rseg)
/* This is safe due to us having detached the persistent undo log. */
rseg->release();
if (mtr)
{
if (trx_undo_t *&undo= rsegs.m_noredo.undo)
......@@ -1455,6 +1436,13 @@ TRANSACTIONAL_TARGET void trx_t::commit_low(mtr_t *mtr)
mtr->commit();
}
else if (trx_rseg_t *rseg= rsegs.m_redo.rseg)
{
ut_ad(id);
ut_ad(!rsegs.m_redo.undo);
rseg->release();
}
#ifdef ENABLED_DEBUG_SYNC
if (debug_sync)
DEBUG_SYNC_C("before_trx_state_committed_in_memory");
......@@ -2167,11 +2155,7 @@ trx_set_rw_mode(
return;
}
trx->rsegs.m_redo.rseg = trx_assign_rseg_low();
ut_ad(trx->rsegs.m_redo.rseg != 0);
trx_sys.register_rw(trx);
ut_ad(trx->id);
trx_assign_rseg_low(trx);
/* So that we can see our own changes. */
if (trx->read_view.is_open()) {
......
......@@ -1011,12 +1011,10 @@ static void trx_undo_seg_free(const trx_undo_t *undo)
@param[in,out] rseg rollback segment
@param[in] id rollback segment slot
@param[in] page_no undo log segment page number
@param[in,out] max_trx_id the largest observed transaction ID
@return the undo log
@retval nullptr on error */
trx_undo_t *
trx_undo_mem_create_at_db_start(trx_rseg_t *rseg, ulint id, uint32_t page_no,
trx_id_t &max_trx_id)
trx_undo_mem_create_at_db_start(trx_rseg_t *rseg, ulint id, uint32_t page_no)
{
mtr_t mtr;
XID xid;
......@@ -1054,10 +1052,21 @@ trx_undo_mem_create_at_db_start(trx_rseg_t *rseg, ulint id, uint32_t page_no,
const trx_ulogf_t* const undo_header = block->page.frame + offset;
uint16_t state = mach_read_from_2(TRX_UNDO_SEG_HDR + TRX_UNDO_STATE
+ block->page.frame);
const trx_id_t trx_id= mach_read_from_8(undo_header + TRX_UNDO_TRX_ID);
if (trx_id >> 48) {
sql_print_error("InnoDB: corrupted TRX_ID %llx", trx_id);
goto corrupted;
}
/* We will increment rseg->needs_purge, like trx_undo_reuse_cached()
would do it, to avoid trouble on rollback or XA COMMIT. */
trx_id_t trx_no = trx_id + 1;
switch (state) {
case TRX_UNDO_ACTIVE:
case TRX_UNDO_PREPARED:
if (UNIV_LIKELY(type != 1)) {
trx_no = trx_id + 1;
break;
}
sql_print_error("InnoDB: upgrade from older version than"
......@@ -1080,13 +1089,14 @@ trx_undo_mem_create_at_db_start(trx_rseg_t *rseg, ulint id, uint32_t page_no,
goto corrupted_type;
}
read_trx_no:
trx_id_t id = mach_read_from_8(TRX_UNDO_TRX_NO + undo_header);
if (id >> 48) {
sql_print_error("InnoDB: corrupted TRX_NO %llx", id);
trx_no = mach_read_from_8(TRX_UNDO_TRX_NO + undo_header);
if (trx_no >> 48) {
sql_print_error("InnoDB: corrupted TRX_NO %llx",
trx_no);
goto corrupted;
}
if (id > max_trx_id) {
max_trx_id = id;
if (trx_no < trx_id) {
trx_no = trx_id;
}
}
......@@ -1099,13 +1109,8 @@ trx_undo_mem_create_at_db_start(trx_rseg_t *rseg, ulint id, uint32_t page_no,
xid.null();
}
trx_id_t trx_id = mach_read_from_8(undo_header + TRX_UNDO_TRX_ID);
if (trx_id >> 48) {
sql_print_error("InnoDB: corrupted TRX_ID %llx", trx_id);
goto corrupted;
}
if (trx_id > max_trx_id) {
max_trx_id = trx_id;
if (trx_no > rseg->needs_purge) {
rseg->needs_purge = trx_no;
}
trx_undo_t* undo = trx_undo_mem_create(
......@@ -1296,6 +1301,22 @@ buf_block_t*
trx_undo_reuse_cached(trx_t* trx, trx_rseg_t* rseg, trx_undo_t** pundo,
mtr_t* mtr)
{
if (rseg->is_persistent()) {
ut_ad(rseg->is_referenced());
if (rseg->needs_purge <= trx->id) {
/* trx_purge_truncate_history() compares
rseg->needs_purge <= head.trx_no
so we need to compensate for that.
The rseg->needs_purge after crash
recovery would be at least trx->id + 1,
because that is the minimum possible value
assigned by trx_serialise() on commit. */
rseg->needs_purge = trx->id + 1;
}
} else {
ut_ad(!rseg->is_referenced());
}
trx_undo_t* undo = UT_LIST_GET_FIRST(rseg->undo_cached);
if (!undo) {
return NULL;
......@@ -1396,8 +1417,7 @@ trx_undo_assign_low(trx_t* trx, trx_rseg_t* rseg, trx_undo_t** undo,
dberr_t* err, mtr_t* mtr)
{
ut_d(const bool is_temp = rseg == trx->rsegs.m_noredo.rseg);
ut_ad(rseg == trx->rsegs.m_redo.rseg
|| rseg == trx->rsegs.m_noredo.rseg);
ut_ad(is_temp || rseg == trx->rsegs.m_redo.rseg);
ut_ad(undo == (is_temp
? &trx->rsegs.m_noredo.undo
: &trx->rsegs.m_redo.undo));
......@@ -1417,7 +1437,6 @@ trx_undo_assign_low(trx_t* trx, trx_rseg_t* rseg, trx_undo_t** undo,
);
rseg->latch.wr_lock(SRW_LOCK_CALL);
buf_block_t* block = trx_undo_reuse_cached(trx, rseg, undo, mtr);
if (!block) {
......
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