summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorNikita Malyavin <nikitamalyavin@gmail.com>2019-08-19 23:57:42 +1000
committerNikita Malyavin <nikitamalyavin@gmail.com>2020-01-21 20:35:06 +1000
commitd823652daa3bc37835ba7ebabf9c08dc740c5bd2 (patch)
tree8ccb388248b0fdd7f87022eb41225f152c6656fc
parent0e86b4a473d3dc0aeb9567f61f21208b010a7107 (diff)
downloadmariadb-git-d823652daa3bc37835ba7ebabf9c08dc740c5bd2.tar.gz
Prelock child tables in addition to parent ones
-rw-r--r--mysql-test/suite/innodb/r/truncate_foreign.result7
-rw-r--r--mysql-test/suite/innodb/t/truncate_foreign.test4
-rw-r--r--sql/sql_base.cc76
-rw-r--r--sql/table.h4
4 files changed, 54 insertions, 37 deletions
diff --git a/mysql-test/suite/innodb/r/truncate_foreign.result b/mysql-test/suite/innodb/r/truncate_foreign.result
index bcf5b16aa83..55e8b6b44be 100644
--- a/mysql-test/suite/innodb/r/truncate_foreign.result
+++ b/mysql-test/suite/innodb/r/truncate_foreign.result
@@ -33,6 +33,7 @@ TRUNCATE TABLE child;
ERROR HY000: Lock wait timeout exceeded; try restarting transaction
SET DEBUG_SYNC='now SIGNAL go';
connection dml;
+# Truncate will not start until delete will release prelocked child
ERROR 23000: Cannot delete or update a parent row: a foreign key constraint fails (`test`.`child`, CONSTRAINT `child_ibfk_1` FOREIGN KEY (`a`) REFERENCES `parent` (`a`) ON UPDATE CASCADE)
SELECT * FROM child;
a
@@ -44,14 +45,18 @@ connection default;
SET DEBUG_SYNC='now WAIT_FOR fk';
SET foreign_key_checks=0;
TRUNCATE TABLE parent;
+ERROR HY000: Lock wait timeout exceeded; try restarting transaction
SET DEBUG_SYNC='now SIGNAL go';
connection dml;
-ERROR 23000: Cannot add or update a child row: a foreign key constraint fails (`test`.`child`, CONSTRAINT `child_ibfk_1` FOREIGN KEY (`a`) REFERENCES `parent` (`a`) ON UPDATE CASCADE)
+# The row will be successfully inserted before truncation during prelocking
SELECT * FROM parent;
a
+3
+5
SELECT * FROM child;
a
3
+5
disconnect dml;
connection default;
SET DEBUG_SYNC = RESET;
diff --git a/mysql-test/suite/innodb/t/truncate_foreign.test b/mysql-test/suite/innodb/t/truncate_foreign.test
index 2c00c0641e9..b185d836b3a 100644
--- a/mysql-test/suite/innodb/t/truncate_foreign.test
+++ b/mysql-test/suite/innodb/t/truncate_foreign.test
@@ -42,6 +42,7 @@ TRUNCATE TABLE child;
SET DEBUG_SYNC='now SIGNAL go';
connection dml;
+--echo # Truncate will not start until delete will release prelocked child
--error ER_ROW_IS_REFERENCED_2
reap;
SELECT * FROM child;
@@ -52,11 +53,12 @@ send INSERT INTO child SET a=5;
connection default;
SET DEBUG_SYNC='now WAIT_FOR fk';
SET foreign_key_checks=0;
+--error ER_LOCK_WAIT_TIMEOUT
TRUNCATE TABLE parent;
SET DEBUG_SYNC='now SIGNAL go';
connection dml;
---error ER_NO_REFERENCED_ROW_2
+--echo # The row will be successfully inserted before truncation during prelocking
reap;
SELECT * FROM parent;
SELECT * FROM child;
diff --git a/sql/sql_base.cc b/sql/sql_base.cc
index 766c5ec0d04..64434acd9e7 100644
--- a/sql/sql_base.cc
+++ b/sql/sql_base.cc
@@ -4625,6 +4625,41 @@ add_internal_tables(THD *thd, Query_tables_list *prelocking_ctx,
DBUG_RETURN(FALSE);
}
+static
+void prelock_fk_tables(THD *thd, TABLE_LIST *table_list,
+ Query_tables_list *prelocking_ctx,
+ List<FOREIGN_KEY_INFO> &fk_list,
+ bool is_foreign)
+{
+ for (auto fk: fk_list)
+ {
+ uint8 op= table_list->trg_event_map;
+ bool del_modifies_child= fk_modifies_child(fk.delete_method);
+ bool upd_modifiels_child= fk_modifies_child(fk.update_method);
+
+ thr_lock_type lock_type= TL_READ;
+ if (!is_foreign
+ && ((op & (1u << TRG_EVENT_DELETE) && del_modifies_child)
+ || (op & (1u << TRG_EVENT_UPDATE) && upd_modifiels_child)))
+ lock_type= TL_WRITE_ALLOW_WRITE;
+
+ auto *db= is_foreign ? fk.referenced_db : fk.foreign_db;
+ auto *table_name= is_foreign ? fk.referenced_table : fk.foreign_table;
+
+ if (table_already_fk_prelocked(prelocking_ctx->query_tables,
+ db, table_name, lock_type))
+ continue;
+
+ TABLE_LIST *tl= (TABLE_LIST *) thd->alloc(sizeof(TABLE_LIST));
+ tl->init_one_table_for_prelocking(db, table_name,
+ NULL, lock_type,
+ TABLE_LIST::PRELOCK_FK,
+ table_list->belong_to_view,
+ table_list->trg_event_map,
+ &prelocking_ctx->query_tables_last,
+ table_list->for_insert_data);
+ }
+}
/**
@@ -4670,43 +4705,14 @@ handle_table(THD *thd, Query_tables_list *prelocking_ctx,
return TRUE;
}
- if (table->s->referenced_by_foreign_key())
+ if (table->s->referenced_by_foreign_key() || table->s->has_foreign_keys())
{
- List_iterator<FOREIGN_KEY_INFO> fk_list_it(table->s->referenced_keys);
- FOREIGN_KEY_INFO *fk;
- Query_arena *arena, backup;
-
- arena= thd->activate_stmt_arena_if_needed(&backup);
*need_prelocking= TRUE;
-
- while ((fk= fk_list_it++))
- {
- // FK_OPTION_RESTRICT and FK_OPTION_NO_ACTION only need read access
- uint8 op= table_list->trg_event_map;
- thr_lock_type lock_type;
-
- if ((op & (1 << TRG_EVENT_DELETE) && fk_modifies_child(fk->delete_method))
- || (op & (1 << TRG_EVENT_UPDATE) && fk_modifies_child(fk->update_method)))
- lock_type= TL_WRITE_ALLOW_WRITE;
- else
- lock_type= TL_READ;
-
- if (table_already_fk_prelocked(prelocking_ctx->query_tables,
- fk->foreign_db, fk->foreign_table,
- lock_type))
- continue;
-
- TABLE_LIST *tl= (TABLE_LIST *) thd->alloc(sizeof(TABLE_LIST));
- tl->init_one_table_for_prelocking(fk->foreign_db,
- fk->foreign_table,
- NULL, lock_type,
- TABLE_LIST::PRELOCK_FK,
- table_list->belong_to_view, op,
- &prelocking_ctx->query_tables_last,
- table_list->for_insert_data);
- }
- if (arena)
- thd->restore_active_arena(arena, &backup);
+ Query_arena_stmt stmt(thd);
+ prelock_fk_tables(thd, table_list, prelocking_ctx,
+ table->s->foreign_keys, true);
+ prelock_fk_tables(thd, table_list, prelocking_ctx,
+ table->s->referenced_keys, false);
}
}
diff --git a/sql/table.h b/sql/table.h
index c674209c4d2..01c06e10405 100644
--- a/sql/table.h
+++ b/sql/table.h
@@ -653,6 +653,10 @@ struct TABLE_SHARE
{
return !referenced_keys.is_empty();
}
+ bool has_foreign_keys() const
+ {
+ return !foreign_keys.is_empty();
+ }
Virtual_column_info **check_constraints;
uint *blob_field; /* Index to blobs in Field arrray*/