Skip to content

Commit 64a23c1

Browse files
committed
extend prelocking to FK-accessed tables
Backport of f136291
1 parent 3b365fa commit 64a23c1

File tree

6 files changed

+157
-4
lines changed

6 files changed

+157
-4
lines changed

mysql-test/suite/innodb/r/foreign-keys.result

Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,3 +14,40 @@ ALTER TABLE `title` ADD FOREIGN KEY (`title_manager_fk`) REFERENCES `people`
1414
ALTER TABLE `title` ADD FOREIGN KEY (`title_reporter_fk`) REFERENCES `people`
1515
(`people_id`);
1616
drop table title, department, people;
17+
create table t1 (a int primary key, b int) engine=innodb;
18+
create table t2 (c int primary key, d int,
19+
foreign key (d) references t1 (a) on update cascade) engine=innodb;
20+
insert t1 values (1,1),(2,2),(3,3);
21+
insert t2 values (4,1),(5,2),(6,3);
22+
flush table t2 with read lock;
23+
connect con1,localhost,root;
24+
delete from t1 where a=2;
25+
ERROR 23000: Cannot delete or update a parent row: a foreign key constraint fails (`test`.`t2`, CONSTRAINT `t2_ibfk_1` FOREIGN KEY (`d`) REFERENCES `t1` (`a`) ON UPDATE CASCADE)
26+
update t1 set a=10 where a=1;
27+
connection default;
28+
unlock tables;
29+
connection con1;
30+
connection default;
31+
lock table t2 write;
32+
connection con1;
33+
delete from t1 where a=2;
34+
connection default;
35+
unlock tables;
36+
connection con1;
37+
ERROR 23000: Cannot delete or update a parent row: a foreign key constraint fails (`test`.`t2`, CONSTRAINT `t2_ibfk_1` FOREIGN KEY (`d`) REFERENCES `t1` (`a`) ON UPDATE CASCADE)
38+
connection default;
39+
unlock tables;
40+
disconnect con1;
41+
create user foo;
42+
grant select,update on test.t1 to foo;
43+
connect foo,localhost,foo;
44+
update t1 set a=30 where a=3;
45+
disconnect foo;
46+
connection default;
47+
select * from t2;
48+
c d
49+
5 2
50+
4 10
51+
6 30
52+
drop table t2, t1;
53+
drop user foo;

mysql-test/suite/innodb/t/foreign-keys.test

Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,8 @@
11
--source include/have_innodb.inc
22
--source include/have_debug.inc
33

4+
--enable_connect_log
5+
46
--echo #
57
--echo # Bug #19471516 SERVER CRASHES WHEN EXECUTING ALTER TABLE
68
--echo # ADD FOREIGN KEY
@@ -24,3 +26,49 @@ ALTER TABLE `title` ADD FOREIGN KEY (`title_reporter_fk`) REFERENCES `people`
2426
(`people_id`);
2527

2628
drop table title, department, people;
29+
30+
#
31+
# FK and prelocking:
32+
# child table accesses (reads and writes) wait for locks.
33+
#
34+
create table t1 (a int primary key, b int) engine=innodb;
35+
create table t2 (c int primary key, d int,
36+
foreign key (d) references t1 (a) on update cascade) engine=innodb;
37+
insert t1 values (1,1),(2,2),(3,3);
38+
insert t2 values (4,1),(5,2),(6,3);
39+
flush table t2 with read lock; # this takes MDL_SHARED_NO_WRITE
40+
connect (con1,localhost,root);
41+
--error ER_ROW_IS_REFERENCED_2
42+
delete from t1 where a=2;
43+
send update t1 set a=10 where a=1;
44+
connection default;
45+
let $wait_condition= select 1 from information_schema.processlist where state='Waiting for table metadata lock';
46+
source include/wait_condition.inc;
47+
unlock tables;
48+
connection con1;
49+
reap;
50+
connection default;
51+
lock table t2 write; # this takes MDL_SHARED_NO_READ_WRITE
52+
connection con1;
53+
send delete from t1 where a=2;
54+
connection default;
55+
let $wait_condition= select 1 from information_schema.processlist where state='Waiting for table metadata lock';
56+
source include/wait_condition.inc;
57+
unlock tables;
58+
connection con1;
59+
--error ER_ROW_IS_REFERENCED_2
60+
reap;
61+
connection default;
62+
unlock tables;
63+
disconnect con1;
64+
65+
# but privileges should not be checked
66+
create user foo;
67+
grant select,update on test.t1 to foo;
68+
connect(foo,localhost,foo);
69+
update t1 set a=30 where a=3;
70+
disconnect foo;
71+
connection default;
72+
select * from t2;
73+
drop table t2, t1;
74+
drop user foo;

sql/sp_head.cc

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4231,7 +4231,7 @@ sp_head::add_used_tables_to_table_list(THD *thd,
42314231
table->init_one_table_for_prelocking(key_buff, stab->db_length,
42324232
key_buff + stab->db_length + 1, stab->table_name_length,
42334233
key_buff + stab->db_length + stab->table_name_length + 2,
4234-
stab->lock_type, belong_to_view, stab->trg_event_map,
4234+
stab->lock_type, true, belong_to_view, stab->trg_event_map,
42354235
query_tables_last_ptr);
42364236

42374237
tab_buff+= ALIGN_SIZE(sizeof(TABLE_LIST));

sql/sql_base.cc

Lines changed: 65 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4802,6 +4802,25 @@ handle_routine(THD *thd, Query_tables_list *prelocking_ctx,
48024802
}
48034803

48044804

4805+
/*
4806+
@note this can be changed to use a hash, instead of scanning the linked
4807+
list, if the performance of this function will ever become an issue
4808+
*/
4809+
static bool table_already_fk_prelocked(TABLE_LIST *tl, LEX_STRING *db,
4810+
LEX_STRING *table, thr_lock_type lock_type)
4811+
{
4812+
for (; tl; tl= tl->next_global )
4813+
{
4814+
if (tl->lock_type >= lock_type &&
4815+
tl->prelocking_placeholder == TABLE_LIST::FK &&
4816+
strcmp(tl->db, db->str) == 0 &&
4817+
strcmp(tl->table_name, table->str) == 0)
4818+
return true;
4819+
}
4820+
return false;
4821+
}
4822+
4823+
48054824
/**
48064825
Defines how prelocking algorithm for DML statements should handle table list
48074826
elements:
@@ -4841,6 +4860,52 @@ handle_table(THD *thd, Query_tables_list *prelocking_ctx,
48414860
add_tables_and_routines_for_triggers(thd, prelocking_ctx, table_list))
48424861
return TRUE;
48434862
}
4863+
4864+
if (table_list->table->file->referenced_by_foreign_key())
4865+
{
4866+
List <FOREIGN_KEY_INFO> fk_list;
4867+
List_iterator<FOREIGN_KEY_INFO> fk_list_it(fk_list);
4868+
FOREIGN_KEY_INFO *fk;
4869+
Query_arena *arena, backup;
4870+
4871+
arena= thd->activate_stmt_arena_if_needed(&backup);
4872+
4873+
table_list->table->file->get_parent_foreign_key_list(thd, &fk_list);
4874+
if (thd->is_error())
4875+
{
4876+
if (arena)
4877+
thd->restore_active_arena(arena, &backup);
4878+
return TRUE;
4879+
}
4880+
4881+
*need_prelocking= TRUE;
4882+
4883+
while ((fk= fk_list_it++))
4884+
{
4885+
// FK_OPTION_RESTRICT and FK_OPTION_NO_ACTION only need read access
4886+
static bool can_write[]= { true, false, true, true, false, true };
4887+
uint8 op= table_list->trg_event_map;
4888+
thr_lock_type lock_type;
4889+
4890+
if ((op & (1 << TRG_EVENT_DELETE) && can_write[fk->delete_method])
4891+
|| (op & (1 << TRG_EVENT_UPDATE) && can_write[fk->update_method]))
4892+
lock_type= TL_WRITE_ALLOW_WRITE;
4893+
else
4894+
lock_type= TL_READ;
4895+
4896+
if (table_already_fk_prelocked(table_list, fk->foreign_db,
4897+
fk->foreign_table, lock_type))
4898+
continue;
4899+
4900+
TABLE_LIST *tl= (TABLE_LIST *) thd->alloc(sizeof(TABLE_LIST));
4901+
tl->init_one_table_for_prelocking(fk->foreign_db->str, fk->foreign_db->length,
4902+
fk->foreign_table->str, fk->foreign_table->length,
4903+
NULL, lock_type, false, table_list->belong_to_view,
4904+
op, &prelocking_ctx->query_tables_last);
4905+
}
4906+
if (arena)
4907+
thd->restore_active_arena(arena, &backup);
4908+
}
48444909
}
48454910

48464911
return FALSE;

sql/sql_class.cc

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5082,7 +5082,8 @@ has_write_table_with_auto_increment_and_select(TABLE_LIST *tables)
50825082
for(TABLE_LIST *table= tables; table; table= table->next_global)
50835083
{
50845084
if (!table->placeholder() &&
5085-
(table->lock_type <= TL_READ_NO_INSERT))
5085+
table->lock_type <= TL_READ_NO_INSERT &&
5086+
table->prelocking_placeholder != TABLE_LIST::FK)
50865087
{
50875088
has_select= true;
50885089
break;

sql/table.h

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1713,14 +1713,16 @@ struct TABLE_LIST
17131713
size_t table_name_length_arg,
17141714
const char *alias_arg,
17151715
enum thr_lock_type lock_type_arg,
1716+
bool routine,
17161717
TABLE_LIST *belong_to_view_arg,
17171718
uint8 trg_event_map_arg,
17181719
TABLE_LIST ***last_ptr)
17191720
{
17201721
init_one_table(db_name_arg, db_length_arg, table_name_arg,
17211722
table_name_length_arg, alias_arg, lock_type_arg);
17221723
cacheable_table= 1;
1723-
prelocking_placeholder= 1;
1724+
prelocking_placeholder= routine ? ROUTINE : FK;
1725+
open_type= routine ? OT_TEMPORARY_OR_BASE : OT_BASE_ONLY;
17241726
belong_to_view= belong_to_view_arg;
17251727
trg_event_map= trg_event_map_arg;
17261728

@@ -2004,7 +2006,7 @@ struct TABLE_LIST
20042006
This TABLE_LIST object is just placeholder for prelocking, it will be
20052007
used for implicit LOCK TABLES only and won't be used in real statement.
20062008
*/
2007-
bool prelocking_placeholder;
2009+
enum { USER, ROUTINE, FK } prelocking_placeholder;
20082010
/**
20092011
Indicates that if TABLE_LIST object corresponds to the table/view
20102012
which requires special handling.

0 commit comments

Comments
 (0)