Commit a49f5cae authored by unknown's avatar unknown

Bug#4118: multi-table UPDATE takes WRITE lock on read table

  Ensures that WRITE lock is not obtained on all tables referenced.


mysql-test/r/lock_multi.result:
  Bug#4118
    New test for multi-update locking
mysql-test/r/multi_update.result:
  Bug#4118
    Fix test
mysql-test/t/lock_multi.test:
  Bug#4118
    New test for multi-update locking
mysql-test/t/multi_update.test:
  Bug#4118
    Fix test
sql/sql_parse.cc:
  Bug#4118
    Split multi-update to its own case statement in sql_parse.cc
sql/sql_update.cc:
  Bug#4118
    Overview of locking checking:    
      1. Open and acquire READ lock
      2. Check to see which tables need WRITE lock
      3. Unlock tables and relock
sql/sql_yacc.yy:
  Bug#4118
    Split multi-update to its own case statement in sql_parse.cc
parent 79950521
...@@ -17,6 +17,18 @@ unlock tables; ...@@ -17,6 +17,18 @@ unlock tables;
n n
1 1
drop table t1; drop table t1;
create table t1 (a int, b int);
create table t2 (c int, d int);
insert into t1 values(1,1);
insert into t1 values(2,2);
insert into t2 values(1,2);
lock table t1 read;
update t1,t2 set c=a where b=d;
select c from t2;
c
2
drop table t1;
drop table t2;
create table t1 (a int); create table t1 (a int);
create table t2 (a int); create table t2 (a int);
lock table t1 write, t2 write; lock table t1 write, t2 write;
......
...@@ -151,7 +151,6 @@ Table 't2' was locked with a READ lock and can't be updated ...@@ -151,7 +151,6 @@ Table 't2' was locked with a READ lock and can't be updated
UPDATE t1,t2 SET t1.d=t2.d,t2.d=30 WHERE t1.n=t2.n; UPDATE t1,t2 SET t1.d=t2.d,t2.d=30 WHERE t1.n=t2.n;
Table 't2' was locked with a READ lock and can't be updated Table 't2' was locked with a READ lock and can't be updated
UPDATE t1,t2 SET t1.d=t2.d WHERE t1.n=t2.n; UPDATE t1,t2 SET t1.d=t2.d WHERE t1.n=t2.n;
Table 't2' was locked with a READ lock and can't be updated
unlock tables; unlock tables;
LOCK TABLES t1 write, t2 write; LOCK TABLES t1 write, t2 write;
UPDATE t1,t2 SET t1.d=t2.d WHERE t1.n=t2.n; UPDATE t1,t2 SET t1.d=t2.d WHERE t1.n=t2.n;
......
...@@ -50,6 +50,30 @@ connection reader; ...@@ -50,6 +50,30 @@ connection reader;
reap; reap;
drop table t1; drop table t1;
#
# Test problem when using locks with multi-updates
# It should not block when multi-update is reading on a read-locked table
#
connection locker;
create table t1 (a int, b int);
create table t2 (c int, d int);
insert into t1 values(1,1);
insert into t1 values(2,2);
insert into t2 values(1,2);
lock table t1 read;
connection writer;
--sleep 2
send update t1,t2 set c=a where b=d;
connection reader;
--sleep 2
select c from t2;
connection writer;
reap;
connection locker;
drop table t1;
drop table t2;
# #
# Test problem when using locks on many tables and droping a table that # Test problem when using locks on many tables and droping a table that
# is to-be-locked by another thread # is to-be-locked by another thread
......
...@@ -151,8 +151,6 @@ LOCK TABLES t1 write, t2 read; ...@@ -151,8 +151,6 @@ LOCK TABLES t1 write, t2 read;
DELETE t1.*, t2.* FROM t1,t2 where t1.n=t2.n; DELETE t1.*, t2.* FROM t1,t2 where t1.n=t2.n;
--error 1099 --error 1099
UPDATE t1,t2 SET t1.d=t2.d,t2.d=30 WHERE t1.n=t2.n; UPDATE t1,t2 SET t1.d=t2.d,t2.d=30 WHERE t1.n=t2.n;
# The following should be fixed to not give an error
--error 1099
UPDATE t1,t2 SET t1.d=t2.d WHERE t1.n=t2.n; UPDATE t1,t2 SET t1.d=t2.d WHERE t1.n=t2.n;
unlock tables; unlock tables;
LOCK TABLES t1 write, t2 write; LOCK TABLES t1 write, t2 write;
......
...@@ -1927,8 +1927,6 @@ mysql_execute_command(void) ...@@ -1927,8 +1927,6 @@ mysql_execute_command(void)
send_error(&thd->net,ER_WRONG_VALUE_COUNT); send_error(&thd->net,ER_WRONG_VALUE_COUNT);
DBUG_VOID_RETURN; DBUG_VOID_RETURN;
} }
if (select_lex->table_list.elements == 1)
{
if (check_one_table_access(thd, UPDATE_ACL, tables, 0)) if (check_one_table_access(thd, UPDATE_ACL, tables, 0))
goto error; /* purecov: inspected */ goto error; /* purecov: inspected */
...@@ -1940,8 +1938,15 @@ mysql_execute_command(void) ...@@ -1940,8 +1938,15 @@ mysql_execute_command(void)
(ORDER *) select_lex->order_list.first, (ORDER *) select_lex->order_list.first,
select_lex->select_limit, select_lex->select_limit,
lex->duplicates); lex->duplicates);
break;
case SQLCOM_MULTI_UPDATE:
if (check_db_used(thd,tables))
goto error;
if (select_lex->item_list.elements != lex->value_list.elements)
{
send_error(&thd->net,ER_WRONG_VALUE_COUNT);
DBUG_VOID_RETURN;
} }
else
{ {
const char *msg= 0; const char *msg= 0;
TABLE_LIST *table; TABLE_LIST *table;
......
...@@ -401,9 +401,21 @@ int mysql_multi_update(THD *thd, ...@@ -401,9 +401,21 @@ int mysql_multi_update(THD *thd,
int res; int res;
multi_update *result; multi_update *result;
TABLE_LIST *tl; TABLE_LIST *tl;
const bool locked= !(thd->locked_tables);
DBUG_ENTER("mysql_multi_update"); DBUG_ENTER("mysql_multi_update");
if ((res=open_and_lock_tables(thd,table_list))) for (;;)
{
table_map update_map= 0;
int tnr= 0;
if ((res= open_tables(thd, table_list)))
DBUG_RETURN(res);
/*
Only need to call lock_tables if (thd->locked_tables == NULL)
*/
if (locked && ((res= lock_tables(thd, table_list))))
DBUG_RETURN(res); DBUG_RETURN(res);
thd->select_limit=HA_POS_ERROR; thd->select_limit=HA_POS_ERROR;
...@@ -411,16 +423,80 @@ int mysql_multi_update(THD *thd, ...@@ -411,16 +423,80 @@ int mysql_multi_update(THD *thd,
/* /*
Ensure that we have update privilege for all tables and columns in the Ensure that we have update privilege for all tables and columns in the
SET part SET part
While we are here, initialize the table->map field.
*/ */
for (tl= table_list ; tl ; tl=tl->next) for (tl= table_list ; tl ; tl=tl->next)
{ {
TABLE *table= tl->table; TABLE *table= tl->table;
table->grant.want_privilege= (UPDATE_ACL & ~table->grant.privilege); table->grant.want_privilege= (UPDATE_ACL & ~table->grant.privilege);
table->map= (table_map) 1 << (tnr++);
} }
if (setup_fields(thd, table_list, *fields, 1, 0, 0)) if (!setup_fields(thd, table_list, *fields, 1, 0, 0))
{
List_iterator_fast<Item> field_it(*fields);
Item_field *item;
while ((item= (Item_field *) field_it++))
update_map|= item->used_tables();
DBUG_PRINT("info",("update_map=0x%08x", update_map));
}
else
DBUG_RETURN(-1); DBUG_RETURN(-1);
/*
Unlock the tables in preparation for relocking
*/
if (locked)
{
pthread_mutex_lock(&LOCK_open);
mysql_unlock_tables(thd, thd->lock);
thd->lock= 0;
pthread_mutex_unlock(&LOCK_open);
}
/*
Set the table locking strategy according to the update map
*/
for (tl= table_list ; tl ; tl=tl->next)
{
TABLE *table= tl->table;
if (update_map & table->map)
{
DBUG_PRINT("info",("setting table `%s` for update", tl->alias));
tl->lock_type= thd->lex.lock_option;
tl->updating= 1;
}
else
{
DBUG_PRINT("info",("setting table `%s` for read-only", tl->alias));
tl->lock_type= TL_READ;
tl->updating= 0;
}
if (locked)
tl->table->reginfo.lock_type= tl->lock_type;
}
/*
Relock the tables
*/
if (!(res=lock_tables(thd,table_list)))
break;
if (!locked)
DBUG_RETURN(res);
List_iterator_fast<Item> field_it(*fields);
Item_field *item;
while ((item= (Item_field *) field_it++))
/* item->cleanup(); XXX Use this instead in MySQL 4.1+ */
item->field= item->result_field= 0;
close_thread_tables(thd);
}
/* /*
Count tables and setup timestamp handling Count tables and setup timestamp handling
*/ */
......
...@@ -2751,10 +2751,18 @@ update: ...@@ -2751,10 +2751,18 @@ update:
lex->select->order_list.next= (byte**) &lex->select->order_list.first; lex->select->order_list.next= (byte**) &lex->select->order_list.first;
} }
opt_low_priority opt_ignore join_table_list opt_low_priority opt_ignore join_table_list
SET update_list where_clause opt_order_clause delete_limit_clause SET update_list
{ {
if (Lex->select->table_list.elements > 1)
{
LEX *lex=Lex;
lex->sql_command= SQLCOM_MULTI_UPDATE;
lex->lock_option= $3;
}
else
set_lock_for_tables($3); set_lock_for_tables($3);
} }
where_clause opt_order_clause delete_limit_clause {}
; ;
update_list: update_list:
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment