Commit 0806592a authored by Marko Mäkelä's avatar Marko Mäkelä

MDEV-28422 Page split breaks a gap lock

btr_insert_into_right_sibling(): Inherit any gap lock from the
left sibling to the right sibling before inserting the record
to the right sibling and updating the node pointer(s).

lock_update_node_pointer(): Update locks in case a node pointer
will move.

Based on mysql/mysql-server@c7d93c274fdc5c56e36458fa4000fa3a483ffffd
parent b208030e
SET @save_frequency=@@GLOBAL.innodb_purge_rseg_truncate_frequency;
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,,;
START TRANSACTION WITH CONSISTENT SNAPSHOT;
connection default;
DELETE FROM t1 WHERE id=1788;
BEGIN;
SELECT * FROM t1 WHERE id=1788 FOR UPDATE;
id val
connection con1;
COMMIT;
InnoDB 0 transactions not purged
connection default;
INSERT INTO t1 (id,val) VALUES (1787, REPEAT('x',2000));
connection con1;
SET innodb_lock_wait_timeout=0;
INSERT INTO t1 (id,val) VALUES (1788, 'x');
ERROR HY000: Lock wait timeout exceeded; try restarting transaction
SELECT * FROM t1 WHERE id=1788 FOR UPDATE;
id val
disconnect con1;
connection default;
COMMIT;
DROP TABLE t1;
SET GLOBAL innodb_purge_rseg_truncate_frequency=@save_frequency;
--source include/have_innodb.inc
--source include/have_sequence.inc
--source include/have_debug.inc
SET @save_frequency=@@GLOBAL.innodb_purge_rseg_truncate_frequency;
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,,);
# Prevent purge.
START TRANSACTION WITH CONSISTENT SNAPSHOT;
connection default;
DELETE FROM t1 WHERE id=1788;
BEGIN;
# This will return no result, but should acquire a gap lock.
SELECT * FROM t1 WHERE id=1788 FOR UPDATE;
connection con1;
COMMIT;
source include/wait_all_purged.inc;
connection default;
INSERT INTO t1 (id,val) VALUES (1787, REPEAT('x',2000));
connection con1;
SET innodb_lock_wait_timeout=0;
--error ER_LOCK_WAIT_TIMEOUT
INSERT INTO t1 (id,val) VALUES (1788, 'x');
SELECT * FROM t1 WHERE id=1788 FOR UPDATE;
disconnect con1;
connection default;
COMMIT;
DROP TABLE t1;
SET GLOBAL innodb_purge_rseg_truncate_frequency=@save_frequency;
......@@ -2,7 +2,7 @@
Copyright (c) 1994, 2016, Oracle and/or its affiliates. All Rights Reserved.
Copyright (c) 2012, Facebook Inc.
Copyright (c) 2014, 2021, MariaDB Corporation.
Copyright (c) 2014, 2022, MariaDB Corporation.
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
......@@ -2688,8 +2688,8 @@ btr_insert_into_right_sibling(
max_size = page_get_max_insert_size_after_reorganize(next_page, 1);
/* Extends gap lock for the next page */
if (!dict_table_is_locking_disabled(cursor->index->table)) {
lock_update_split_left(next_block, block);
if (is_leaf && !dict_table_is_locking_disabled(cursor->index->table)) {
lock_update_node_pointer(block, next_block);
}
rec = page_cur_tuple_insert(
......
/*****************************************************************************
Copyright (c) 1996, 2016, Oracle and/or its affiliates. All Rights Reserved.
Copyright (c) 2017, 2020, MariaDB Corporation.
Copyright (c) 1996, 2022, Oracle and/or its affiliates.
Copyright (c) 2017, 2022, MariaDB Corporation.
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
......@@ -151,6 +151,40 @@ lock_update_copy_and_discard(
which copied */
const buf_block_t* block); /*!< in: index page;
NOT the root! */
/** Update gap locks between the last record of the left_block and the
first record of the right_block when a record is about to be inserted
at the start of the right_block, even though it should "naturally" be
inserted as the last record of the left_block according to the
current node pointer in the parent page.
That is, we assume that the lowest common ancestor of the left_block
and right_block routes the key of the new record to the left_block,
but a heuristic which tries to avoid overflowing left_block has chosen
to insert the record into right_block instead. Said ancestor performs
this routing by comparing the key of the record to a "split point" -
all records greater or equal to than the split point (node pointer)
are in right_block, and smaller ones in left_block.
The split point may be smaller than the smallest key in right_block.
The gap between the last record on the left_block and the first record
on the right_block is represented as a gap lock attached to the supremum
pseudo-record of left_block, and a gap lock attached to the new first
record of right_block.
Thus, inserting the new record, and subsequently adjusting the node
pointers in parent pages to values smaller or equal to the new
records' key, will mean that gap will be sliced at a different place
("moved to the left"): fragment of the 1st gap will now become treated
as 2nd. Therefore, we must copy any GRANTED locks from 1st gap to the
2nd gap. Any WAITING locks must be of INSERT_INTENTION type (as no
other GAP locks ever wait for anything) and can stay at 1st gap, as
their only purpose is to notify the requester they can retry
insertion, and there's no correctness requirement to avoid waking them
up too soon.
@param left_block left page
@param right_block right page */
void lock_update_node_pointer(const buf_block_t *left_block,
const buf_block_t *right_block);
/*************************************************************//**
Updates the lock table when a page is split to the left. */
void
......
/*****************************************************************************
Copyright (c) 1996, 2017, Oracle and/or its affiliates. All Rights Reserved.
Copyright (c) 2014, 2021, MariaDB Corporation.
Copyright (c) 1996, 2022, Oracle and/or its affiliates.
Copyright (c) 2014, 2022, MariaDB Corporation.
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
......@@ -3044,6 +3044,17 @@ lock_update_split_right(
lock_mutex_exit();
}
void lock_update_node_pointer(const buf_block_t *left_block,
const buf_block_t *right_block)
{
const ulint h= lock_get_min_heap_no(right_block);
lock_mutex_enter();
lock_rec_inherit_to_gap(right_block, left_block,
h, PAGE_HEAP_NO_SUPREMUM);
lock_mutex_exit();
}
/*************************************************************//**
Updates the lock table when a page is merged to the right. */
void
......
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