mirror of
https://github.com/MariaDB/server.git
synced 2025-04-28 06:45:23 +03:00
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@c7d93c274f
This commit is contained in:
parent
b208030ef5
commit
0806592ac8
27
mysql-test/suite/innodb/r/gap_lock_split.result
Normal file
27
mysql-test/suite/innodb/r/gap_lock_split.result
Normal file
@ -0,0 +1,27 @@
|
|||||||
|
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;
|
39
mysql-test/suite/innodb/t/gap_lock_split.test
Normal file
39
mysql-test/suite/innodb/t/gap_lock_split.test
Normal file
@ -0,0 +1,39 @@
|
|||||||
|
--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) 1994, 2016, Oracle and/or its affiliates. All Rights Reserved.
|
||||||
Copyright (c) 2012, Facebook Inc.
|
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
|
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
|
||||||
@ -2688,8 +2688,8 @@ btr_insert_into_right_sibling(
|
|||||||
max_size = page_get_max_insert_size_after_reorganize(next_page, 1);
|
max_size = page_get_max_insert_size_after_reorganize(next_page, 1);
|
||||||
|
|
||||||
/* Extends gap lock for the next page */
|
/* Extends gap lock for the next page */
|
||||||
if (!dict_table_is_locking_disabled(cursor->index->table)) {
|
if (is_leaf && !dict_table_is_locking_disabled(cursor->index->table)) {
|
||||||
lock_update_split_left(next_block, block);
|
lock_update_node_pointer(block, next_block);
|
||||||
}
|
}
|
||||||
|
|
||||||
rec = page_cur_tuple_insert(
|
rec = page_cur_tuple_insert(
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
/*****************************************************************************
|
/*****************************************************************************
|
||||||
|
|
||||||
Copyright (c) 1996, 2016, Oracle and/or its affiliates. All Rights Reserved.
|
Copyright (c) 1996, 2022, Oracle and/or its affiliates.
|
||||||
Copyright (c) 2017, 2020, MariaDB Corporation.
|
Copyright (c) 2017, 2022, 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
|
||||||
@ -151,6 +151,40 @@ lock_update_copy_and_discard(
|
|||||||
which copied */
|
which copied */
|
||||||
const buf_block_t* block); /*!< in: index page;
|
const buf_block_t* block); /*!< in: index page;
|
||||||
NOT the root! */
|
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. */
|
Updates the lock table when a page is split to the left. */
|
||||||
void
|
void
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
/*****************************************************************************
|
/*****************************************************************************
|
||||||
|
|
||||||
Copyright (c) 1996, 2017, Oracle and/or its affiliates. All Rights Reserved.
|
Copyright (c) 1996, 2022, Oracle and/or its affiliates.
|
||||||
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
|
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
|
||||||
@ -3044,6 +3044,17 @@ lock_update_split_right(
|
|||||||
lock_mutex_exit();
|
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. */
|
Updates the lock table when a page is merged to the right. */
|
||||||
void
|
void
|
||||||
|
Loading…
x
Reference in New Issue
Block a user