mirror of
https://github.com/MariaDB/server.git
synced 2025-02-04 21:02:17 +01:00
3cef4f8f0f
We implement an idea that was suggested by Michael 'Monty' Widenius in October 2017: When InnoDB is inserting into an empty table or partition, we can write a single undo log record TRX_UNDO_EMPTY, which will cause ROLLBACK to clear the table. For this to work, the insert into an empty table or partition must be covered by an exclusive table lock that will be held until the transaction has been committed or rolled back, or the INSERT operation has been rolled back (and the table is empty again), in lock_table_x_unlock(). Clustered index records that are covered by the TRX_UNDO_EMPTY record will carry DB_TRX_ID=0 and DB_ROLL_PTR=1<<55, and thus they cannot be distinguished from what MDEV-12288 leaves behind after purging the history of row-logged operations. Concurrent non-locking reads must be adjusted: If the read view was created before the INSERT into an empty table, then we must continue to imagine that the table is empty, and not try to read any records. If the read view was created after the INSERT was committed, then all records must be visible normally. To implement this, we introduce the field dict_table_t::bulk_trx_id. This special handling only applies to the very first INSERT statement of a transaction for the empty table or partition. If a subsequent statement in the transaction is modifying the initially empty table again, we must enable row-level undo logging, so that we will be able to roll back to the start of the statement in case of an error (such as duplicate key). INSERT IGNORE will continue to use row-level logging and locking, because implementing it would require the ability to roll back the latest row. Since the undo log that we write only allows us to roll back the entire statement, we cannot support INSERT IGNORE. We will introduce a handler::extra() parameter HA_EXTRA_IGNORE_INSERT to indicate to storage engines that INSERT IGNORE is being executed. In many test cases, we add an extra record to the table, so that during the 'interesting' part of the test, row-level locking and logging will be used. Replicas will continue to use row-level logging and locking until MDEV-24622 has been addressed. Likewise, this optimization will be disabled in Galera cluster until MDEV-24623 enables it. dict_table_t::bulk_trx_id: The latest active or committed transaction that initiated an insert into an empty table or partition. Protected by exclusive table lock and a clustered index leaf page latch. ins_node_t::bulk_insert: Whether bulk insert was initiated. trx_t::mod_tables: Use C++11 style accessors (emplace instead of insert). Unlike earlier, this collection will cover also temporary tables. trx_mod_table_time_t: Add start_bulk_insert(), end_bulk_insert(), is_bulk_insert(), was_bulk_insert(). trx_undo_report_row_operation(): Before accessing any undo log pages, invoke trx->mod_tables.emplace() in order to determine whether undo logging was disabled, or whether this is the first INSERT and we are supposed to write a TRX_UNDO_EMPTY record. row_ins_clust_index_entry_low(): If we are inserting into an empty clustered index leaf page, set the ins_node_t::bulk_insert flag for the subsequent trx_undo_report_row_operation() call. lock_rec_insert_check_and_lock(), lock_prdt_insert_check_and_lock(): Remove the redundant parameter 'flags' that can be checked in the caller. btr_cur_ins_lock_and_undo(): Simplify the logic. Correctly write DB_TRX_ID,DB_ROLL_PTR after invoking trx_undo_report_row_operation(). trx_mark_sql_stat_end(), ha_innobase::extra(HA_EXTRA_IGNORE_INSERT), ha_innobase::external_lock(): Invoke trx_t::end_bulk_insert() so that the next statement will not be covered by table-level undo logging. ReadView::changes_visible(trx_id_t) const: New accessor for the case where the trx_id_t is not read from a potentially corrupted index page but directly from the memory. In this case, we can skip a sanity check. row_sel(), row_sel_try_search_shortcut(), row_search_mvcc(): row_sel_try_search_shortcut_for_mysql(), row_merge_read_clustered_index(): Check dict_table_t::bulk_trx_id. row_sel_clust_sees(): Replaces lock_clust_rec_cons_read_sees(). lock_sec_rec_cons_read_sees(): Replaced with lower-level code. btr_root_page_init(): Refactored from btr_create(). dict_index_t::clear(), dict_table_t::clear(): Empty an index or table, for the ROLLBACK of an INSERT operation. ROW_T_EMPTY, ROW_OP_EMPTY: Note a concurrent ROLLBACK of an INSERT into an empty table. This is joint work with Thirunarayanan Balathandayuthapani, who created a working prototype. Thanks to Matthias Leich for extensive testing.
430 lines
13 KiB
Text
430 lines
13 KiB
Text
# WL#6745 InnoDB R-tree support
|
|
# This test case will test R-tree split.
|
|
|
|
--source include/have_innodb.inc
|
|
--source include/have_debug.inc
|
|
--source include/have_debug_sync.inc
|
|
--source include/big_test.inc
|
|
|
|
# Create table with R-tree index.
|
|
create table t1 (c1 int, c2 geometry not null, spatial index (c2))engine=innodb;
|
|
|
|
# Insert enough values to let R-tree split.
|
|
insert into t1 values(1, Point(1,1));
|
|
insert into t1 values(2, Point(2,2));
|
|
insert into t1 values(3, Point(3,3));
|
|
insert into t1 values(4, Point(4,4));
|
|
insert into t1 values(5, Point(5,5));
|
|
insert into t1 values(6, Point(6,6));
|
|
insert into t1 values(7, Point(7,7));
|
|
insert into t1 values(8, Point(8,8));
|
|
insert into t1 values(9, Point(9,9));
|
|
|
|
insert into t1 select * from t1;
|
|
insert into t1 select * from t1;
|
|
insert into t1 select * from t1;
|
|
insert into t1 select * from t1;
|
|
|
|
insert into t1 select * from t1;
|
|
insert into t1 select * from t1;
|
|
|
|
connect (a,localhost,root,,);
|
|
SET debug_dbug='+d,rtr_pcur_move_to_next_return';
|
|
|
|
set session transaction isolation level serializable;
|
|
set @g1 = ST_GeomFromText('Polygon((0 0,0 100,100 100,100 0,0 0))');
|
|
SET DEBUG_SYNC = 'RESET';
|
|
SET DEBUG_SYNC = 'row_search_for_mysql_before_return SIGNAL started WAIT_FOR go_ahead';
|
|
--send select count(*) from t1 where MBRWithin(t1.c2, @g1);
|
|
|
|
connect (con1,localhost,root,,);
|
|
set session transaction isolation level serializable;
|
|
|
|
SET DEBUG_SYNC = 'now WAIT_FOR started';
|
|
insert into t1 select * from t1;
|
|
SET DEBUG_SYNC = 'now SIGNAL go_ahead';
|
|
|
|
connection a;
|
|
reap;
|
|
select count(*) from t1 where MBRWithin(t1.c2, @g1);
|
|
|
|
connection default;
|
|
insert into t1 select * from t1;
|
|
insert into t1 select * from t1;
|
|
|
|
set @g1 = ST_GeomFromText('Polygon((0 0,0 100,100 100,100 0,0 0))');
|
|
select count(*) from t1 where MBRWithin(t1.c2, @g1);
|
|
|
|
set @g1 = ST_GeomFromText('Polygon((10 10,10 800,800 800,800 10,10 10))');
|
|
select count(*) from t1 where MBRWithin(t1.c2, @g1);
|
|
|
|
set session transaction isolation level serializable;
|
|
|
|
truncate t1;
|
|
|
|
# Test on predicate locking
|
|
INSERT INTO t1 VALUES (1, ST_GeomFromText('LineString(2 2, 150 150)'));
|
|
INSERT INTO t1 VALUES (1, ST_GeomFromText('LineString(3 3, 160 160)'));
|
|
INSERT INTO t1 VALUES (1, ST_GeomFromText('LineString(4 4, 170 170)'));
|
|
INSERT INTO t1 VALUES (1, ST_GeomFromText('LineString(5 5, 180 180)'));
|
|
INSERT INTO t1 VALUES (1, ST_GeomFromText('LineString(6 6, 190 190)'));
|
|
INSERT INTO t1 VALUES (1, ST_GeomFromText('LineString(7 7, 200 200)'));
|
|
INSERT INTO t1 VALUES (1, ST_GeomFromText('LineString(8 8, 210 210)'));
|
|
|
|
insert into t1 select * from t1;
|
|
insert into t1 select * from t1;
|
|
insert into t1 select * from t1;
|
|
insert into t1 select * from t1;
|
|
|
|
# Connection 'a' will place predicate lock on almost all pages
|
|
connection a;
|
|
set session transaction isolation level serializable;
|
|
select @@tx_isolation;
|
|
start transaction;
|
|
set @g1 = ST_GeomFromText('Polygon((100 100, 100 110, 110 110, 110 100, 100 100))');
|
|
select count(*) from t1 where MBRwithin(t1.c2, @g1);
|
|
|
|
# The split will replicate locks across pages
|
|
connect (b,localhost,root,,);
|
|
set session transaction isolation level serializable;
|
|
set session innodb_lock_wait_timeout = 1;
|
|
|
|
select @@tx_isolation;
|
|
|
|
insert into t1 select * from t1;
|
|
insert into t1 select * from t1;
|
|
insert into t1 select * from t1;
|
|
|
|
# FIXME: Put this back once we sort out the shrink business
|
|
#insert into t1 select * from t1;
|
|
|
|
connection a;
|
|
commit;
|
|
|
|
connection default;
|
|
select count(*) from t1;
|
|
|
|
# Insert a record that would be in the search range
|
|
insert into t1 values (105, Point(105, 105));
|
|
|
|
# Connection 'a' will place predicate lock on almost all pages
|
|
connection a;
|
|
start transaction;
|
|
set @g1 = ST_GeomFromText('Polygon((100 100, 100 110, 110 110, 110 100, 100 100))');
|
|
select count(*) from t1 where MBRwithin(t1.c2, @g1);
|
|
|
|
connection b;
|
|
select @@innodb_lock_wait_timeout;
|
|
select @@tx_isolation;
|
|
|
|
--error ER_LOCK_WAIT_TIMEOUT
|
|
insert into t1 select * from t1;
|
|
select count(*) from t1;
|
|
|
|
connection a;
|
|
select sleep(2);
|
|
commit;
|
|
|
|
#==================Test predicates on "MBRIntersects"==========================
|
|
connection default;
|
|
truncate t1;
|
|
|
|
# Test on predicate locking
|
|
INSERT INTO t1 VALUES (1, ST_GeomFromText('LineString(2 2, 150 150)'));
|
|
INSERT INTO t1 VALUES (1, ST_GeomFromText('LineString(3 3, 160 160)'));
|
|
INSERT INTO t1 VALUES (1, ST_GeomFromText('LineString(4 4, 170 170)'));
|
|
INSERT INTO t1 VALUES (1, ST_GeomFromText('LineString(5 5, 180 180)'));
|
|
INSERT INTO t1 VALUES (1, ST_GeomFromText('LineString(6 6, 190 190)'));
|
|
INSERT INTO t1 VALUES (1, ST_GeomFromText('LineString(7 7, 200 200)'));
|
|
INSERT INTO t1 VALUES (1, ST_GeomFromText('LineString(8 8, 210 210)'));
|
|
|
|
insert into t1 select * from t1;
|
|
insert into t1 select * from t1;
|
|
insert into t1 select * from t1;
|
|
insert into t1 select * from t1;
|
|
|
|
# Connection 'a' will place predicate lock on almost all pages
|
|
connection a;
|
|
set session transaction isolation level serializable;
|
|
select @@tx_isolation;
|
|
start transaction;
|
|
set @g1 = ST_GeomFromText('Polygon((100 100, 100 110, 110 110, 110 100, 100 100))');
|
|
select count(*) from t1 where MBRwithin(t1.c2, @g1);
|
|
|
|
# The split will replicate locks across pages
|
|
connection b;
|
|
set session transaction isolation level serializable;
|
|
set session innodb_lock_wait_timeout = 1;
|
|
|
|
select @@tx_isolation;
|
|
|
|
insert into t1 select * from t1;
|
|
insert into t1 select * from t1;
|
|
insert into t1 select * from t1;
|
|
insert into t1 select * from t1;
|
|
|
|
connection a;
|
|
commit;
|
|
|
|
connection default;
|
|
select count(*) from t1;
|
|
|
|
# Insert a record that would be in the search range
|
|
insert into t1 values (105, Point(105, 105));
|
|
|
|
# Connection 'a' will place predicate lock on almost all pages
|
|
connection a;
|
|
start transaction;
|
|
set @g1 = ST_GeomFromText('Polygon((100 100, 100 110, 110 110, 110 100, 100 100))');
|
|
select count(*) from t1 where MBRwithin(t1.c2, @g1);
|
|
select count(*) from t1 where MBRIntersects(t1.c2, @g1);
|
|
|
|
connection b;
|
|
select @@innodb_lock_wait_timeout;
|
|
select @@tx_isolation;
|
|
|
|
# this should conflict with the "MBRIntersects" predicate lock in session "a"
|
|
--error ER_LOCK_WAIT_TIMEOUT
|
|
INSERT INTO t1 VALUES (1, ST_GeomFromText('LineString(8 105, 200 105)'));
|
|
select count(*) from t1;
|
|
|
|
connection a;
|
|
select sleep(2);
|
|
commit;
|
|
|
|
#==================Test predicate lock on "delete"==========================
|
|
connection default;
|
|
truncate t1;
|
|
|
|
# Test on predicate locking
|
|
INSERT INTO t1 VALUES (1, ST_GeomFromText('LineString(2 2, 150 150)'));
|
|
INSERT INTO t1 VALUES (1, ST_GeomFromText('LineString(3 3, 160 160)'));
|
|
INSERT INTO t1 VALUES (1, ST_GeomFromText('LineString(5 5, 180 180)'));
|
|
INSERT INTO t1 VALUES (1, ST_GeomFromText('LineString(6 6, 190 190)'));
|
|
INSERT INTO t1 VALUES (1, ST_GeomFromText('LineString(7 7, 200 200)'));
|
|
INSERT INTO t1 VALUES (1, ST_GeomFromText('LineString(8 8, 210 210)'));
|
|
|
|
# Connection default will place predicate lock on follow range
|
|
set @g1 = ST_GeomFromText('Polygon((3 3, 3 5, 5 5, 5 3, 3 3))');
|
|
start transaction;
|
|
delete from t1 where MBRWithin(t1.c2, @g1);
|
|
|
|
connection a;
|
|
set session innodb_lock_wait_timeout = 1;
|
|
select @@innodb_lock_wait_timeout;
|
|
--error ER_LOCK_WAIT_TIMEOUT
|
|
insert into t1 values(4, Point(4,4));
|
|
|
|
connection default;
|
|
rollback;
|
|
|
|
#==================Test predicate lock on "select for update"==================
|
|
connection default;
|
|
truncate t1;
|
|
|
|
# Test on predicate locking
|
|
INSERT INTO t1 VALUES (1, ST_GeomFromText('LineString(2 2, 150 150)'));
|
|
INSERT INTO t1 VALUES (1, ST_GeomFromText('LineString(3 3, 160 160)'));
|
|
INSERT INTO t1 VALUES (1, ST_GeomFromText('LineString(5 5, 180 180)'));
|
|
INSERT INTO t1 VALUES (1, ST_GeomFromText('LineString(6 6, 190 190)'));
|
|
INSERT INTO t1 VALUES (1, ST_GeomFromText('LineString(7 7, 200 200)'));
|
|
INSERT INTO t1 VALUES (1, ST_GeomFromText('LineString(8 8, 210 210)'));
|
|
|
|
# Connection default will place predicate lock on follow range
|
|
set @g1 = ST_GeomFromText('Polygon((3 3, 3 5, 5 5, 5 3, 3 3))');
|
|
start transaction;
|
|
select count(*) from t1 where MBRWithin(t1.c2, @g1) for update;
|
|
|
|
connection a;
|
|
set session innodb_lock_wait_timeout = 1;
|
|
select @@innodb_lock_wait_timeout;
|
|
--error ER_LOCK_WAIT_TIMEOUT
|
|
insert into t1 values(4, Point(4,4));
|
|
|
|
connection default;
|
|
rollback;
|
|
#==================Test predicates replicate through split =================
|
|
connection default;
|
|
truncate t1;
|
|
|
|
delimiter |;
|
|
create procedure insert_t1(IN start int, IN total int)
|
|
begin
|
|
declare i int default 1;
|
|
set i = start;
|
|
while (i <= total) DO
|
|
insert into t1 values (i, Point(i, i));
|
|
set i = i + 1;
|
|
end while;
|
|
end|
|
|
delimiter ;|
|
|
|
|
CALL insert_t1(0, 1000);
|
|
|
|
# Connection 'a' will place predicate lock on root and last leaf page
|
|
connection a;
|
|
set session transaction isolation level serializable;
|
|
select @@tx_isolation;
|
|
start transaction;
|
|
set @g1 = ST_GeomFromText('Polygon((800 800, 800 1000, 1000 1000, 1000 800, 800 800))');
|
|
select count(*) from t1 where MBRwithin(t1.c2, @g1);
|
|
|
|
# Connection 'b' will split the last leaf page, so the predicate
|
|
# lock should replicate
|
|
connection b;
|
|
|
|
CALL insert_t1(1001, 2000);
|
|
|
|
# This insert goes to the new page after split, it should be blocked
|
|
set session transaction isolation level serializable;
|
|
set session innodb_lock_wait_timeout = 1;
|
|
# Insert a record that would be in the search range
|
|
--error ER_LOCK_WAIT_TIMEOUT
|
|
insert into t1 values (1200, Point(950, 950));
|
|
|
|
connection a;
|
|
select sleep(2);
|
|
commit;
|
|
disconnect a;
|
|
disconnect b;
|
|
|
|
# Clean up.
|
|
connection default;
|
|
drop table t1;
|
|
|
|
drop procedure insert_t1;
|
|
|
|
#============ Test row locks =======================
|
|
create table t1 (c1 int, c2 geometry not null, spatial index (c2))engine=innodb;
|
|
|
|
|
|
INSERT INTO t1 VALUES (1, ST_GeomFromText('LineString(2 2, 150 150)'));
|
|
INSERT INTO t1 VALUES (2, ST_GeomFromText('LineString(3 3, 160 160)'));
|
|
INSERT INTO t1 VALUES (2, ST_GeomFromText('LineString(4 4, 170 170)'));
|
|
INSERT INTO t1 VALUES (1, ST_GeomFromText('LineString(5 5, 180 180)'));
|
|
INSERT INTO t1 VALUES (1, ST_GeomFromText('LineString(6 6, 190 190)'));
|
|
INSERT INTO t1 VALUES (1, ST_GeomFromText('LineString(7 7, 200 200)'));
|
|
INSERT INTO t1 VALUES (1, ST_GeomFromText('LineString(8 8, 210 210)'));
|
|
|
|
connect (a,localhost,root,,);
|
|
SET SESSION debug_dbug='+d,rtr_pcur_move_to_next_return';
|
|
|
|
set transaction isolation level serializable;
|
|
start transaction;
|
|
set @g1 = ST_GeomFromText('Polygon((100 100, 100 110, 110 110, 110 100, 100 100))');
|
|
select count(*) from t1 where MBRwithin(t1.c2, @g1);
|
|
|
|
connect (b,localhost,root,,);
|
|
|
|
# This should be successful
|
|
delete from t1 where c1 = 1;
|
|
|
|
connection a;
|
|
commit;
|
|
set transaction isolation level serializable;
|
|
start transaction;
|
|
set @g1 = ST_GeomFromText('Polygon((0 0, 0 300, 300 300, 300 0, 0 0))');
|
|
select count(*) from t1 where MBRwithin(t1.c2, @g1);
|
|
|
|
connection b;
|
|
|
|
set session innodb_lock_wait_timeout = 1;
|
|
--error ER_LOCK_WAIT_TIMEOUT
|
|
delete from t1 where c1 = 2;
|
|
|
|
# Clean up.
|
|
connection a;
|
|
commit;
|
|
|
|
connection default;
|
|
drop table t1;
|
|
SET DEBUG_SYNC= 'RESET';
|
|
|
|
# Test btr_discard_page adjust concurrent search path
|
|
create table t1 (c1 int, c2 geometry not null, spatial index (c2))engine=innodb;
|
|
|
|
start transaction;
|
|
insert into t1 values(1, Point(1,1));
|
|
insert into t1 values(2, Point(2,2));
|
|
insert into t1 values(3, Point(3,3));
|
|
insert into t1 values(4, Point(4,4));
|
|
insert into t1 values(5, Point(5,5));
|
|
insert into t1 values(6, Point(6,6));
|
|
insert into t1 values(7, Point(7,7));
|
|
insert into t1 values(8, Point(8,8));
|
|
insert into t1 values(9, Point(9,9));
|
|
|
|
insert into t1 select * from t1;
|
|
insert into t1 select * from t1;
|
|
insert into t1 select * from t1;
|
|
insert into t1 select * from t1;
|
|
|
|
insert into t1 select * from t1;
|
|
insert into t1 select * from t1;
|
|
insert into t1 select * from t1;
|
|
insert into t1 select * from t1;
|
|
|
|
insert into t1 select * from t1;
|
|
|
|
select count(*) from t1;
|
|
|
|
connection b;
|
|
set @g1 = ST_GeomFromText('Polygon((0 0,0 100,100 100,100 0,0 0))');
|
|
set transaction isolation level read uncommitted;
|
|
SET DEBUG_SYNC= 'row_search_for_mysql_before_return SIGNAL siga WAIT_FOR sigb';
|
|
send select count(*) from t1 where MBRWithin(t1.c2, @g1);
|
|
|
|
connection default;
|
|
SET DEBUG_SYNC= 'now WAIT_FOR siga';
|
|
rollback;
|
|
SET DEBUG_SYNC= 'now SIGNAL sigb';
|
|
|
|
connection b;
|
|
--reap
|
|
select count(*) from t1 where MBRWithin(t1.c2, @g1);
|
|
|
|
connection default;
|
|
DROP TABLE t1;
|
|
SET DEBUG_SYNC = 'RESET';
|
|
|
|
create table t1 (c1 int, c2 geometry not null, spatial index (c2))engine=innodb;
|
|
insert into t1 values(100, point(100, 100));
|
|
|
|
delimiter |;
|
|
create procedure insert_t1(IN total int)
|
|
begin
|
|
declare i int default 1;
|
|
while (i <= total) DO
|
|
insert into t1 values (i, Point(i, i));
|
|
set i = i + 1;
|
|
end while;
|
|
end|
|
|
delimiter ;|
|
|
|
|
start transaction;
|
|
|
|
CALL insert_t1(100);
|
|
|
|
connection a;
|
|
set @g1 = ST_GeomFromText('Polygon((0 0,0 1000,1000 1000,1000 0,0 0))');
|
|
SET DEBUG_SYNC= 'rtr_pcur_move_to_next_return SIGNAL siga WAIT_FOR sigb';
|
|
--send select count(*) from t1 where MBRWithin(t1.c2, @g1);
|
|
|
|
connection default;
|
|
SET DEBUG_SYNC= 'now WAIT_FOR siga';
|
|
rollback;
|
|
|
|
SET DEBUG_SYNC= 'now SIGNAL sigb';
|
|
|
|
connection a;
|
|
reap;
|
|
|
|
connection default;
|
|
drop procedure insert_t1;
|
|
DROP TABLE t1;
|
|
|
|
disconnect a;
|
|
disconnect b;
|
|
|
|
connection default;
|
|
SET DEBUG_SYNC = 'RESET';
|