mirror of
https://github.com/MariaDB/server.git
synced 2025-08-09 22:24:09 +03:00
MDEV-18507 can't update temporary table when joined with table with triggers on read-only
triggers are opened and tables used in triggers are prelocked in open_tables(). But multi-update can detect what tables will actually be updated only later, after all main tables are opened. Meaning, if a table is used in multi-update, but is not actually updated, its on-update treggers will be opened and tables will be prelocked, even if it's unnecessary. This can cause more tables to be write-locked than needed, causing read_only errors, privilege errors and lock waits. Fix: don't open/prelock triggers unless table->updating is true. In multi-update after setting table->updating=true, do a second open_tables() for newly added tables, if any.
This commit is contained in:
@@ -1017,4 +1017,25 @@ execute stmt1;
|
||||
deallocate prepare stmt1;
|
||||
drop view v3,v2,v1;
|
||||
drop table t1,t2,t3;
|
||||
create table t1 (id int not null, v1 varchar(10) not null);
|
||||
insert into t1 values (1,1),(2,2);
|
||||
create table t2 (log varchar(10) not null);
|
||||
create trigger t1_after_update after update on t1
|
||||
for each row insert into t2 values ('triggered');
|
||||
create user foo;
|
||||
grant select, insert, update, delete, create, drop, reload, index, alter, show databases, create temporary tables, lock tables, execute, create view, show view, create routine, alter routine, trigger on *.* to 'foo'@'%';
|
||||
set global read_only=1;
|
||||
create temporary table temp_t1 (id int not null, update_me varchar(10));
|
||||
insert into temp_t1 values (1,1),(2,2),(3,3);
|
||||
update temp_t1 left join t1 on temp_t1.id = t1.id set temp_t1.update_me = 'hello';
|
||||
set global read_only = 0;
|
||||
create table t3 (id int not null);
|
||||
insert t3 values (2);
|
||||
update t1 left join t3 on t1.id = t3.id set t1.v1 = 'hello';
|
||||
select * from t2;
|
||||
log
|
||||
triggered
|
||||
triggered
|
||||
drop table t1,t2, t3;
|
||||
drop user foo;
|
||||
end of 5.5 tests
|
||||
|
@@ -1055,5 +1055,36 @@ deallocate prepare stmt1;
|
||||
|
||||
drop view v3,v2,v1;
|
||||
drop table t1,t2,t3;
|
||||
|
||||
#
|
||||
# MDEV-18507 can't update temporary table when joined with table with triggers on read-only
|
||||
#
|
||||
create table t1 (id int not null, v1 varchar(10) not null);
|
||||
insert into t1 values (1,1),(2,2);
|
||||
create table t2 (log varchar(10) not null);
|
||||
create trigger t1_after_update after update on t1
|
||||
for each row insert into t2 values ('triggered');
|
||||
|
||||
create user foo;
|
||||
grant select, insert, update, delete, create, drop, reload, index, alter, show databases, create temporary tables, lock tables, execute, create view, show view, create routine, alter routine, trigger on *.* to 'foo'@'%';
|
||||
|
||||
set global read_only=1;
|
||||
connect a, localhost, foo;
|
||||
|
||||
create temporary table temp_t1 (id int not null, update_me varchar(10));
|
||||
insert into temp_t1 values (1,1),(2,2),(3,3);
|
||||
update temp_t1 left join t1 on temp_t1.id = t1.id set temp_t1.update_me = 'hello';
|
||||
|
||||
connection default;
|
||||
set global read_only = 0;
|
||||
|
||||
create table t3 (id int not null);
|
||||
insert t3 values (2);
|
||||
update t1 left join t3 on t1.id = t3.id set t1.v1 = 'hello';
|
||||
select * from t2;
|
||||
|
||||
drop table t1,t2, t3;
|
||||
drop user foo;
|
||||
|
||||
--echo end of 5.5 tests
|
||||
|
||||
|
@@ -4250,6 +4250,7 @@ sp_head::add_used_tables_to_table_list(THD *thd,
|
||||
table->table_name_length= stab->table_name_length;
|
||||
table->alias= table->table_name + table->table_name_length + 1;
|
||||
table->lock_type= stab->lock_type;
|
||||
table->updating= stab->lock_type >= TL_WRITE_ALLOW_WRITE;
|
||||
table->cacheable_table= 1;
|
||||
table->prelocking_placeholder= 1;
|
||||
table->belong_to_view= belong_to_view;
|
||||
|
@@ -4484,7 +4484,7 @@ bool extend_table_list(THD *thd, TABLE_LIST *tables,
|
||||
LEX *lex= thd->lex;
|
||||
|
||||
if (thd->locked_tables_mode <= LTM_LOCK_TABLES &&
|
||||
! has_prelocking_list &&
|
||||
! has_prelocking_list && tables->updating &&
|
||||
tables->lock_type >= TL_WRITE_ALLOW_WRITE)
|
||||
{
|
||||
bool need_prelocking= FALSE;
|
||||
|
@@ -1208,6 +1208,9 @@ int mysql_multi_update_prepare(THD *thd)
|
||||
List<Item> *fields= &lex->select_lex.item_list;
|
||||
table_map tables_for_update;
|
||||
bool update_view= 0;
|
||||
DML_prelocking_strategy prelocking_strategy;
|
||||
bool has_prelocking_list= thd->lex->requires_prelocking();
|
||||
|
||||
/*
|
||||
if this multi-update was converted from usual update, here is table
|
||||
counter else junk will be assigned here, but then replaced with real
|
||||
@@ -1228,10 +1231,10 @@ int mysql_multi_update_prepare(THD *thd)
|
||||
keep prepare of multi-UPDATE compatible with concurrent LOCK TABLES WRITE
|
||||
and global read lock.
|
||||
*/
|
||||
if ((original_multiupdate &&
|
||||
open_tables(thd, &table_list, &table_count,
|
||||
(thd->stmt_arena->is_stmt_prepare() ?
|
||||
MYSQL_OPEN_FORCE_SHARED_MDL : 0))) ||
|
||||
if ((original_multiupdate && open_tables(thd, &table_list, &table_count,
|
||||
thd->stmt_arena->is_stmt_prepare()
|
||||
? MYSQL_OPEN_FORCE_SHARED_MDL : 0,
|
||||
&prelocking_strategy)) ||
|
||||
mysql_handle_derived(lex, DT_INIT))
|
||||
DBUG_RETURN(TRUE);
|
||||
/*
|
||||
@@ -1278,6 +1281,9 @@ int mysql_multi_update_prepare(THD *thd)
|
||||
if (unsafe_key_update(lex->select_lex.leaf_tables, tables_for_update))
|
||||
DBUG_RETURN(true);
|
||||
|
||||
TABLE_LIST **new_tables= lex->query_tables_last;
|
||||
DBUG_ASSERT(*new_tables== NULL);
|
||||
|
||||
/*
|
||||
Setup timestamp handling and locking mode
|
||||
*/
|
||||
@@ -1308,6 +1314,8 @@ int mysql_multi_update_prepare(THD *thd)
|
||||
tl->updating= 1;
|
||||
if (tl->belong_to_view)
|
||||
tl->belong_to_view->updating= 1;
|
||||
if (extend_table_list(thd, tl, &prelocking_strategy, has_prelocking_list))
|
||||
DBUG_RETURN(TRUE);
|
||||
}
|
||||
else
|
||||
{
|
||||
@@ -1328,6 +1336,19 @@ int mysql_multi_update_prepare(THD *thd)
|
||||
tl->set_lock_type(thd, read_lock_type_for_table(thd, lex, tl));
|
||||
}
|
||||
}
|
||||
|
||||
uint addon_table_count= 0;
|
||||
if (*new_tables)
|
||||
{
|
||||
Sroutine_hash_entry **new_routines= thd->lex->sroutines_list.next;
|
||||
DBUG_ASSERT(*new_routines == NULL);
|
||||
if (open_tables(thd, new_tables, &addon_table_count, new_routines,
|
||||
thd->stmt_arena->is_stmt_prepare()
|
||||
? MYSQL_OPEN_FORCE_SHARED_MDL : 0,
|
||||
&prelocking_strategy))
|
||||
DBUG_RETURN(TRUE);
|
||||
}
|
||||
|
||||
for (tl= table_list; tl; tl= tl->next_local)
|
||||
{
|
||||
/* Check access privileges for table */
|
||||
@@ -1360,7 +1381,7 @@ int mysql_multi_update_prepare(THD *thd)
|
||||
|
||||
/* now lock and fill tables */
|
||||
if (!thd->stmt_arena->is_stmt_prepare() &&
|
||||
lock_tables(thd, table_list, table_count, 0))
|
||||
lock_tables(thd, table_list, table_count + addon_table_count, 0))
|
||||
{
|
||||
DBUG_RETURN(TRUE);
|
||||
}
|
||||
|
@@ -1511,6 +1511,7 @@ bool mysql_make_view(THD *thd, File_parser *parser, TABLE_LIST *table,
|
||||
tbl->lock_type= table->lock_type;
|
||||
tbl->mdl_request.set_type((tbl->lock_type >= TL_WRITE_ALLOW_WRITE) ?
|
||||
MDL_SHARED_WRITE : MDL_SHARED_READ);
|
||||
tbl->updating= table->updating;
|
||||
}
|
||||
/*
|
||||
If the view is mergeable, we might want to
|
||||
|
@@ -13835,7 +13835,7 @@ table_lock:
|
||||
{
|
||||
thr_lock_type lock_type= (thr_lock_type) $3;
|
||||
bool lock_for_write= lock_type >= TL_WRITE_ALLOW_WRITE;
|
||||
ulong table_options= 0;
|
||||
ulong table_options= lock_for_write ? TL_OPTION_UPDATING : 0;
|
||||
enum_mdl_type mdl_type= !lock_for_write
|
||||
? MDL_SHARED_READ
|
||||
: lock_type == TL_WRITE_CONCURRENT_INSERT
|
||||
|
Reference in New Issue
Block a user