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

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.
parent 9cae7bdc
SET GLOBAL innodb_adaptive_hash_index = false;
SET GLOBAL innodb_stats_persistent = false;
connect purge_control,localhost,root,,;
START TRANSACTION WITH CONSISTENT SNAPSHOT;
connect con2,localhost,root,,;
CREATE TABLE t1 (
a00 CHAR(255) NOT NULL DEFAULT 'a',
a01 CHAR(255) NOT NULL DEFAULT 'a',
a02 CHAR(255) NOT NULL DEFAULT 'a',
b INT NOT NULL DEFAULT 0,
CONSTRAINT pkey PRIMARY KEY(a00, a01, a02)
) charset latin1 ENGINE = InnoDB COMMENT='MERGE_THRESHOLD=45';
SET GLOBAL innodb_limit_optimistic_insert_debug = 3;
CREATE PROCEDURE data_load_t1()
BEGIN
DECLARE c1 INT DEFAULT 97;
DECLARE c2 INT DEFAULT 97;
DECLARE c3 INT DEFAULT 97;
WHILE c1 < 102 DO
WHILE c2 < 123 DO
WHILE c3 < 123 DO
INSERT INTO t1 (a00) VALUES (CHAR(c1,c2,c3));
SET c3 = c3 + 1;
END WHILE;
SET c3 = 97;
SET c2 = c2 + 1;
END WHILE;
SET c2 = 97;
SET c1 = c1 + 1;
END WHILE;
END |
call data_load_t1();
DROP PROCEDURE data_load_t1;
ANALYZE TABLE t1;
Table Op Msg_type Msg_text
test.t1 analyze status OK
SELECT CLUST_INDEX_SIZE FROM information_schema.INNODB_SYS_TABLESTATS WHERE NAME = 'test/t1';
CLUST_INDEX_SIZE
1856
connection con2;
DELETE FROM t1 WHERE a00 = 'cnm';
COMMIT;
BEGIN;
INSERT INTO t1 SET a00 = 'cnm';
connection purge_control;
COMMIT;
connection con2;
SET GLOBAL innodb_limit_optimistic_insert_debug = 0;
ROLLBACK;
# Test start
connection purge_control;
START TRANSACTION WITH CONSISTENT SNAPSHOT;
connection con2;
DELETE FROM t1 WHERE a00 = 'bii';
COMMIT;
BEGIN;
INSERT INTO t1 SET a00 = 'bii';
SET DEBUG_SYNC = 'rollback_undo_pk SIGNAL roll1_wait WAIT_FOR roll2';
SET DEBUG_SYNC = 'rollback_purge_clust SIGNAL rollback_waiting WAIT_FOR resume';
ROLLBACK;
connection purge_control;
SET DEBUG_SYNC = 'now WAIT_FOR roll1_wait';
COMMIT;
SET DEBUG_SYNC = 'now SIGNAL roll2';
connect con1,localhost,root,,;
SET DEBUG_SYNC = 'now WAIT_FOR rollback_waiting';
SET DEBUG_SYNC = 'rw_s_lock_waiting SIGNAL lockwait1';
SELECT a00 FROM t1 WHERE a00 = 'bii';
connection default;
SET DEBUG_SYNC = 'now WAIT_FOR lockwait1';
SET DEBUG_SYNC = 'now SIGNAL resume';
connection con1;
a00
connection con2;
connection default;
ANALYZE TABLE t1;
Table Op Msg_type Msg_text
test.t1 analyze status OK
SELECT CLUST_INDEX_SIZE FROM information_schema.INNODB_SYS_TABLESTATS WHERE NAME = 'test/t1';
CLUST_INDEX_SIZE
1856
DELETE FROM t1 WHERE a00 = 'dpn';
COMMIT;
INSERT INTO t1 SET a00 = 'dpn';
ROLLBACK;
ALTER TABLE t1 COMMENT='MERGE_THRESHOLD=35';
connection purge_control;
START TRANSACTION WITH CONSISTENT SNAPSHOT;
connection con2;
DELETE FROM t1 WHERE a00 = 'cnd';
COMMIT;
BEGIN;
INSERT INTO t1 SET a00 = 'cnd';
SET DEBUG_SYNC = 'rollback_undo_pk SIGNAL roll1_wait WAIT_FOR roll2';
SET DEBUG_SYNC = 'rollback_purge_clust SIGNAL rollback_waiting WAIT_FOR resume EXECUTE 2';
ROLLBACK;
connection purge_control;
SET DEBUG_SYNC = 'now WAIT_FOR roll1_wait';
START TRANSACTION WITH CONSISTENT SNAPSHOT;
SET DEBUG_SYNC = 'now SIGNAL roll2';
connection con1;
SET DEBUG_SYNC = 'now WAIT_FOR rollback_waiting TIMEOUT 1';
SET DEBUG_SYNC = 'now SIGNAL resume';
SET DEBUG_SYNC = 'now WAIT_FOR rollback_waiting TIMEOUT 1';
disconnect purge_control;
connection default;
SET DEBUG_SYNC = 'now SIGNAL resume';
disconnect con1;
connection con2;
disconnect con2;
connection default;
ANALYZE TABLE t1;
Table Op Msg_type Msg_text
test.t1 analyze status OK
SELECT CLUST_INDEX_SIZE FROM information_schema.INNODB_SYS_TABLESTATS WHERE NAME = 'test/t1';
CLUST_INDEX_SIZE
1856
SET DEBUG_SYNC = 'RESET';
DROP TABLE t1;
...@@ -299,10 +299,10 @@ SELECT CLUST_INDEX_SIZE FROM information_schema.INNODB_SYS_TABLESTATS WHERE NAME ...@@ -299,10 +299,10 @@ SELECT CLUST_INDEX_SIZE FROM information_schema.INNODB_SYS_TABLESTATS WHERE NAME
CLUST_INDEX_SIZE CLUST_INDEX_SIZE
30 30
SET DEBUG_SYNC = 'RESET'; SET DEBUG_SYNC = 'RESET';
INSERT INTO t1 (a00) VALUES ('cva'); INSERT INTO t1 (a00) VALUES ('coa');
connection con1; connection con1;
SET DEBUG_SYNC = 'before_insert_pessimitic_row_ins_clust SIGNAL reached WAIT_FOR continue'; SET DEBUG_SYNC = 'before_insert_pessimitic_row_ins_clust SIGNAL reached WAIT_FOR continue';
INSERT INTO t1 (a00) VALUES ('cvb'); INSERT INTO t1 (a00) VALUES ('cob');
connection con2; connection con2;
SET DEBUG_SYNC = 'now WAIT_FOR reached'; SET DEBUG_SYNC = 'now WAIT_FOR reached';
SET DEBUG_SYNC = 'rw_s_lock_waiting SIGNAL lockwait1'; SET DEBUG_SYNC = 'rw_s_lock_waiting SIGNAL lockwait1';
......
#
# Test for Bug#30113362 : BTR_CUR_WILL_MODIFY_TREE() IS INSUFFICIENT FOR HIGHER TREE LEVEL
#
--source include/have_innodb.inc
--source include/have_debug.inc
--source include/have_debug_sync.inc
--source include/have_innodb_16k.inc
--disable_query_log
SET @old_innodb_limit_optimistic_insert_debug = @@innodb_limit_optimistic_insert_debug;
SET @old_innodb_adaptive_hash_index = @@innodb_adaptive_hash_index;
SET @old_innodb_stats_persistent = @@innodb_stats_persistent;
--enable_query_log
# Save the initial number of concurrent sessions
--source include/count_sessions.inc
SET GLOBAL innodb_adaptive_hash_index = false;
SET GLOBAL innodb_stats_persistent = false;
connect (purge_control,localhost,root,,);
START TRANSACTION WITH CONSISTENT SNAPSHOT;
--connect (con2,localhost,root,,)
CREATE TABLE t1 (
a00 CHAR(255) NOT NULL DEFAULT 'a',
a01 CHAR(255) NOT NULL DEFAULT 'a',
a02 CHAR(255) NOT NULL DEFAULT 'a',
b INT NOT NULL DEFAULT 0,
CONSTRAINT pkey PRIMARY KEY(a00, a01, a02)
) charset latin1 ENGINE = InnoDB COMMENT='MERGE_THRESHOLD=45';
#
# Prepare primary key index tree to be used for this test.
#
SET GLOBAL innodb_limit_optimistic_insert_debug = 3;
delimiter |;
CREATE PROCEDURE data_load_t1()
BEGIN
DECLARE c1 INT DEFAULT 97;
DECLARE c2 INT DEFAULT 97;
DECLARE c3 INT DEFAULT 97;
WHILE c1 < 102 DO
WHILE c2 < 123 DO
WHILE c3 < 123 DO
INSERT INTO t1 (a00) VALUES (CHAR(c1,c2,c3));
SET c3 = c3 + 1;
END WHILE;
SET c3 = 97;
SET c2 = c2 + 1;
END WHILE;
SET c2 = 97;
SET c1 = c1 + 1;
END WHILE;
END |
delimiter ;|
call data_load_t1();
DROP PROCEDURE data_load_t1;
# all node pages are sparse (max 3 node_ptrs)
ANALYZE TABLE t1;
SELECT CLUST_INDEX_SIZE FROM information_schema.INNODB_SYS_TABLESTATS WHERE NAME = 'test/t1';
connection con2;
DELETE FROM t1 WHERE a00 = 'cnm';
COMMIT;
BEGIN;
INSERT INTO t1 SET a00 = 'cnm';
# causes "domino falling" merges to upper level
connection purge_control;
COMMIT;
connection con2;
SET GLOBAL innodb_limit_optimistic_insert_debug = 0;
ROLLBACK;
# at this moment, in the tree,
# ...
# level 4: ...(ast,avw,ayz)(bcc,bff,bii,bll,boo,brr,buu,bxx,cba,ced,cqp,cts)(cwv,czy,ddb)...
# ...
--echo # Test start
# (1) Similar case to the first reported corefile at bug#30113362
# - Deleting 'bii' causes "domino falling" merges and the node_ptr becomes left_most of level 4.
# So, the operation needs upper level pages' X-latch, though doesn't cause merge more.
connection purge_control;
START TRANSACTION WITH CONSISTENT SNAPSHOT;
connection con2;
DELETE FROM t1 WHERE a00 = 'bii';
COMMIT;
BEGIN;
INSERT INTO t1 SET a00 = 'bii';
SET DEBUG_SYNC = 'rollback_undo_pk SIGNAL roll1_wait WAIT_FOR roll2';
SET DEBUG_SYNC = 'rollback_purge_clust SIGNAL rollback_waiting WAIT_FOR resume';
send ROLLBACK;
connection purge_control;
SET DEBUG_SYNC = 'now WAIT_FOR roll1_wait';
COMMIT;
SET DEBUG_SYNC = 'now SIGNAL roll2';
connect (con1,localhost,root,,);
SET DEBUG_SYNC = 'now WAIT_FOR rollback_waiting';
SET DEBUG_SYNC = 'rw_s_lock_waiting SIGNAL lockwait1';
send SELECT a00 FROM t1 WHERE a00 = 'bii';
connection default;
SET DEBUG_SYNC = 'now WAIT_FOR lockwait1';
# bug#30113362 caused deadlock
SET DEBUG_SYNC = 'now SIGNAL resume';
connection con1;
reap;
connection con2;
reap;
connection default;
ANALYZE TABLE t1;
SELECT CLUST_INDEX_SIZE FROM information_schema.INNODB_SYS_TABLESTATS WHERE NAME = 'test/t1';
# (2) Confirm blocking domain caused by DELETE modify_tree for tall index tree
# at this moment, in the tree,
# ...
# level 4: ...(ajk,amn,apq)(ast,avw,ayz,bll,boo,brr,buu,bxx,cba,ced,cqp,cts)(cwv,czy,ddb)(dge,djh,dmk)(dpn,dsq,dvt)(dyw,ebz,efc)...
# ...
# makes >17 records in level4 [(2^(4-1))*2 + 1]. (causes never left_most records)
DELETE FROM t1 WHERE a00 = 'dpn';
COMMIT;
INSERT INTO t1 SET a00 = 'dpn';
ROLLBACK;
# at this moment, in the tree,
# (* before "]" and after "[" records are treated as left_most possible records)
# ...
# level 4: ...(ajk,amn,apq)(ast,avw,ayz,bll,boo,brr,buu,bxx],cba,ced,[cqp,cts,cwv,czy,ddb,dge,dsq,dvt)(dyw,ebz,efc)...
# level 3: ...(cba,ccb,cdc)(ced,cfe,cgf,chg],cih,cji,[ckj,clk,con,cpo)(cqp,crq,csr)...
# level 2: ...(ckj,cks,clb)(clk,clt],cmc,cml,cmu,cnd,[cnv,coe)(con,cow,cpf)...
# level 1: ...(cmu,cmx,cna)(cnd],cng,cnj,cnp,[cns)(cnv,cny,cob)...
# level 0: ...(cnd,cne,cnf)(cng,cnh,cni)(cnj,cnk,cnl,cnn,cno)(cnp,cnq,cnr)...
# deletes just 'ced' node_ptr only from level 4. doesn't cause merge and never left_most.
# adjusts MERGE_THRESHOLD to do so.
ALTER TABLE t1 COMMENT='MERGE_THRESHOLD=35';
connection purge_control;
START TRANSACTION WITH CONSISTENT SNAPSHOT;
connection con2;
DELETE FROM t1 WHERE a00 = 'cnd';
COMMIT;
BEGIN;
INSERT INTO t1 SET a00 = 'cnd';
SET DEBUG_SYNC = 'rollback_undo_pk SIGNAL roll1_wait WAIT_FOR roll2';
SET DEBUG_SYNC = 'rollback_purge_clust SIGNAL rollback_waiting WAIT_FOR resume EXECUTE 2';
send ROLLBACK;
connection purge_control;
SET DEBUG_SYNC = 'now WAIT_FOR roll1_wait';
START TRANSACTION WITH CONSISTENT SNAPSHOT;
SET DEBUG_SYNC = 'now SIGNAL roll2';
connection con1;
# FIXME: For some reason, we will not always receive these signals!
--disable_warnings
# An optimistic row_undo_mod_remove_clust_low() will fail.
SET DEBUG_SYNC = 'now WAIT_FOR rollback_waiting TIMEOUT 1';
SET DEBUG_SYNC = 'now SIGNAL resume';
# Wait for the pessimistic row_undo_mod_remove_clust_low() attempt.
SET DEBUG_SYNC = 'now WAIT_FOR rollback_waiting TIMEOUT 1';
--enable_warnings
disconnect purge_control;
# The expectation should be...
# level 0: (#cnd#,cne,cnf): causes merge
# level 1: (#cnd#],cng,cnj,cnp,[cns): left_most
# level 2: (clk,clt],cmc,cml,cmu,#cnd#,[cnv,coe): causes merge
# level 3: (ced,cfe,cgf,chg],cih,cji,[ckj,#clk#,con,cpo): left_most possible (not cause merge)
# level 4: (ast,avw,ayz,bll,boo,brr,buu,bxx],cba,#ced#,[cqp,cts,cwv,czy,ddb,dge,dsq,dvt): no merge, not left_most possible
# So, the top X-latch page is at level4. (ast~dvt)
# blocking domain based on whether its ancestor is latched or not.
# (*[]: ancestor is X-latched)
# level 0: ...(asq,asr,ass) [(ast,asu,asv)...(dyt,dyu,dyv)] (dyw,dyx,dyy)...
# Not blocked searches
## In MariaDB, both these will block, because we use different DEBUG_SYNC
## instrumentation (in rollback, not purge) and the root page (number 3)
## is being latched in row_undo_mod_remove_clust_low().
## SELECT a00 FROM t1 WHERE a00 = 'ass';
## SELECT a00 FROM t1 WHERE a00 = 'dyx';
## SET DEBUG_SYNC = 'rw_s_lock_waiting SIGNAL lockwait1';
## send SELECT a00 FROM t1 WHERE a00 = 'ast';
## connection con2;
## SET DEBUG_SYNC = 'rw_s_lock_waiting SIGNAL lockwait2';
## send SELECT a00 FROM t1 WHERE a00 = 'dyw';
connection default;
## SET DEBUG_SYNC = 'now WAIT_FOR lockwait1';
## SET DEBUG_SYNC = 'now WAIT_FOR lockwait2';
SET DEBUG_SYNC = 'now SIGNAL resume';
## connection con1;
## reap;
disconnect con1;
connection con2;
reap;
disconnect con2;
connection default;
ANALYZE TABLE t1;
SELECT CLUST_INDEX_SIZE FROM information_schema.INNODB_SYS_TABLESTATS WHERE NAME = 'test/t1';
# Cleanup
SET DEBUG_SYNC = 'RESET';
DROP TABLE t1;
--disable_query_log
SET GLOBAL innodb_limit_optimistic_insert_debug = @old_innodb_limit_optimistic_insert_debug;
SET GLOBAL innodb_adaptive_hash_index = @old_innodb_adaptive_hash_index;
SET GLOBAL innodb_stats_persistent = @old_innodb_stats_persistent;
--enable_query_log
--source include/wait_until_count_sessions.inc
...@@ -373,23 +373,23 @@ SELECT CLUST_INDEX_SIZE FROM information_schema.INNODB_SYS_TABLESTATS WHERE NAME ...@@ -373,23 +373,23 @@ SELECT CLUST_INDEX_SIZE FROM information_schema.INNODB_SYS_TABLESTATS WHERE NAME
# (2) Insert records to leaf page (cv..) and cause modify_page # (2) Insert records to leaf page (co..) and cause modify_page
# - root page is X latched, because node_ptr for 'cv' # - root page is X latched, because node_ptr for 'co'
# is 2nd record for (co,cv,dc,dj,dq,dx,ee) # is 1st record for (co,cv,dc,dj,dq,dx,ee)
# #
# * ordinary pessimitic insert might be done by pessistic update # * ordinary pessimitic insert might be done by pessistic update
# and we should consider possibility node_ptr to be deleted. # and we should consider possibility node_ptr to be deleted.
SET DEBUG_SYNC = 'RESET'; SET DEBUG_SYNC = 'RESET';
# Filling leaf page (cv..) # Filling leaf page (co..)
INSERT INTO t1 (a00) VALUES ('cva'); INSERT INTO t1 (a00) VALUES ('coa');
--connection con1 --connection con1
SET DEBUG_SYNC = 'before_insert_pessimitic_row_ins_clust SIGNAL reached WAIT_FOR continue'; SET DEBUG_SYNC = 'before_insert_pessimitic_row_ins_clust SIGNAL reached WAIT_FOR continue';
# Cause modify_tree # Cause modify_tree
--send --send
INSERT INTO t1 (a00) VALUES ('cvb'); INSERT INTO t1 (a00) VALUES ('cob');
--connection con2 --connection con2
SET DEBUG_SYNC = 'now WAIT_FOR reached'; SET DEBUG_SYNC = 'now WAIT_FOR reached';
......
...@@ -583,46 +583,66 @@ btr_cur_will_modify_tree( ...@@ -583,46 +583,66 @@ btr_cur_will_modify_tree(
const ulint n_recs = page_get_n_recs(page); const ulint n_recs = page_get_n_recs(page);
if (lock_intention <= BTR_INTENTION_BOTH) { 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);
if (!page_has_siblings(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 /* check delete will cause. (BTR_INTENTION_BOTH
or BTR_INTENTION_DELETE) */ or BTR_INTENTION_DELETE) */
/* first, 2nd, 2nd-last and last records are 4 records */ if (n_recs <= max_nodes_deleted * 2
if (n_recs < 5) { || page_rec_is_first(rec, page)) {
return(true); /* The cursor record can be the left most record
in this page. */
return true;
} }
/* is first, 2nd or last record */ if (page_has_prev(page)
if (page_rec_is_first(rec, page) && page_rec_distance_is_at_most(
|| (page_has_next(page) page_get_infimum_rec(page), rec,
&& (page_rec_is_last(rec, page) max_nodes_deleted)) {
|| page_rec_is_second_last(rec, page))) return true;
|| (page_has_prev(page) }
&& page_rec_is_second(rec, page))) {
return(true); if (page_has_next(page)
&& page_rec_distance_is_at_most(
rec, page_get_supremum_rec(page),
max_nodes_deleted)) {
return true;
} }
if (lock_intention == BTR_INTENTION_BOTH) {
/* Delete at leftmost record in a page causes delete /* Delete at leftmost record in a page causes delete
& insert at its parent page. After that, the delete & insert at its parent page. After that, the delete
might cause btr_compress() and delete record at its might cause btr_compress() and delete record at its
parent page. Thus we should consider max 2 deletes. */ parent page. Thus we should consider max deletes. */
margin *= max_nodes_deleted;
margin = rec_size * 2;
} else {
ut_ad(lock_intention == BTR_INTENTION_DELETE);
margin = rec_size;
} }
/* NOTE: call mach_read_from_4() directly to avoid assertion
failure. It is safe because we already have SX latch of the /* Safe because we already have SX latch of the index tree */
index tree */
if (page_get_data_size(page) if (page_get_data_size(page)
< margin + BTR_CUR_PAGE_COMPRESS_LIMIT(index) < 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)) {
return(true); return(true);
} }
} }
......
/***************************************************************************** /*****************************************************************************
Copyright (c) 1994, 2016, Oracle and/or its affiliates. All Rights Reserved. Copyright (c) 1994, 2019, Oracle and/or its affiliates. All Rights Reserved.
Copyright (c) 2013, 2019, MariaDB Corporation. Copyright (c) 2013, 2020, MariaDB Corporation.
This program is free software; you can redistribute it and/or modify it under 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 the terms of the GNU General Public License as published by the Free Software
...@@ -813,6 +813,22 @@ page_rec_is_last( ...@@ -813,6 +813,22 @@ page_rec_is_last(
const page_t* page) /*!< in: page */ const page_t* page) /*!< in: page */
MY_ATTRIBUTE((warn_unused_result)); 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. true if the record is the second last user record on a page.
@return true if the second last user record */ @return true if the second last user record */
......
/***************************************************************************** /*****************************************************************************
Copyright (c) 1994, 2015, Oracle and/or its affiliates. All Rights Reserved. Copyright (c) 1994, 2019, Oracle and/or its affiliates. All Rights Reserved.
Copyright (c) 2016, 2019, MariaDB Corporation. Copyright (c) 2016, 2020, MariaDB Corporation.
This program is free software; you can redistribute it and/or modify it under 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 the terms of the GNU General Public License as published by the Free Software
...@@ -358,6 +358,26 @@ page_rec_is_last( ...@@ -358,6 +358,26 @@ page_rec_is_last(
return(page_rec_get_next_const(rec) == page_get_supremum_rec(page)); return(page_rec_get_next_const(rec) == page_get_supremum_rec(page));
} }
/************************************************************//**
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. true if the record is the second last user record on a page.
@return true if the second last user record */ @return true if the second last user record */
......
/***************************************************************************** /*****************************************************************************
Copyright (c) 1997, 2017, Oracle and/or its affiliates. All Rights Reserved. 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 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 the terms of the GNU General Public License as published by the Free Software
...@@ -169,12 +169,15 @@ row_undo_mod_remove_clust_low( ...@@ -169,12 +169,15 @@ row_undo_mod_remove_clust_low(
/* Find out if the record has been purged already /* Find out if the record has been purged already
or if we can remove it. */ or if we can remove it. */
if (!btr_pcur_restore_position(mode, &node->pcur, mtr) if (!btr_pcur_restore_position(mode, &node->pcur, mtr)) {
|| row_vers_must_preserve_del_marked(node->new_trx_id, return DB_SUCCESS;
node->table->name, }
mtr)) {
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); btr_cur = btr_pcur_get_btr_cur(&node->pcur);
...@@ -361,6 +364,7 @@ row_undo_mod_clust( ...@@ -361,6 +364,7 @@ row_undo_mod_clust(
== node->new_trx_id); == node->new_trx_id);
btr_pcur_commit_specify_mtr(pcur, &mtr); 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) { if (err == DB_SUCCESS && node->rec_type == TRX_UNDO_UPD_DEL_REC) {
......
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