mirror of
https://github.com/MariaDB/server.git
synced 2025-08-01 03:47:19 +03:00
Better approach for prelocking of tables for stored routines execution
and some SP-related cleanups. - We don't have separate stage for calculation of list of tables to be prelocked and doing implicit LOCK/UNLOCK any more. Instead we calculate this list at open_tables() and do implicit LOCK in lock_tables() (and UNLOCK in close_thread_tables()). Also now we support cases when same table (with same alias) is used several times in the same query in SP. - Cleaned up execution of SP. Moved all common code which handles LEX and does preparations before statement execution or complex expression evaluation to auxilary sp_lex_keeper class. Now all statements in SP (and corresponding instructions) that evaluate expression which can contain subquery have their own LEX. mysql-test/r/lock.result: Replaced wrong error code with the correct one after fixing bug in SP-locking. mysql-test/r/mysqldump.result: Added dropping of view which is used in test to its beginning. mysql-test/r/sp.result: Added tests for improved SP-locking. Temporarily disabled tests for SHOW PROCEDURE STATUS and alike (Until Monty will allow to open mysql.proc under LOCK TABLES without mentioning it in lock list). Replaced wrong results of test for bug #5240 with correct results after fixing bug in handling of cursors. mysql-test/t/lock.test: Replaced wrong error code with the correct one after fixing bug in SP-locking. mysql-test/t/mysqldump.test: Added dropping of view which is used in test to its beginning. mysql-test/t/sp.test: Added tests for improved SP-locking. Temporarily disabled tests for SHOW PROCEDURE STATUS and alike (Until Monty will allow to open mysql.proc under LOCK TABLES without mentioning it in lock list). Removed test for bug #1654 since we already test exactly this function in one of SP-locking tests. Removed comment about cursor's wrong behavior in test for bug #5240 after fixing bug which was its cause. sql/item_func.cc: Removed comment which is no longer true. sql/mysql_priv.h: Changed open_tables() signature. Now its 2nd parameter is in/out since it can add elements to table list. sql/sp.cc: sp_find_procedure(): Added one more parameter which enforces cache only lookup. sp_merge_hash(): Now uses its return value to indicate that first of two hashes changed as result of merge. sp_cache_routines(): This function caches all stored routines used in query now. sql/sp.h: - sp_find_procedure() now has one more parameter which enforces cache only lookup. - sp_merge_hash() now uses its return value to indicate that first of two hashes changed as result of merge. - sp_cache_routines() caches all stored routines now. So it does not need third argument any more. sql/sp_head.cc: sp_head::sp_head(): Added initialization of new m_spfuns and m_spprocs members. sp_head::execute(): Let us save/restore part of thread context which can be damaged by execution of instructions. sp_head::execute_function()/execute_procedure(): Now it is responsibility of caller to close tables used in subqueries which are passed as routine parameters. sp_head::restore_lex(): Let us accumulate information about routines used by this one in new m_spfuns, m_spprocs hashes. sp_lex_keeper::reset_lex_and_exec_core() Main method of new auxilary sp_lex_keeper class to which instructions delegate responsibility for handling LEX and preparations before executing statement or calculating complex expression. Since all instructions which calculate complex expression or execute command now use sp_lex_keeper they have to implement sp_instr::exec_core() method. Most of instruction specific logic has moved from sp_instr::execute() to this new method. Removed sp_instr_set_user_var class which is no longer used, because nowdays we allow execution of statements in stored functions and triggers. sp_merge_table_list() became sp_head::merge_table_list() method. It also treats sp_head::m_sptabs as multi-set of tables now. sp_hash_to_table_list() became sp_head::add_used_tables_to_table_list(). It takes into account that sp_head::m_sptabs is multi-set and allocates object into persistent arena of PS. Removed sp_merge_table_hash(), sp_open_and_lock_tables(), sp_unlock_tables(), sp_merge_routine_tables() methods since they are not used by new prelocking mechanism. Added sp_add_sp_tables_to_table_list() which serves for adding tables needed by routines used in query to the query table list for prelocking. sql/sp_head.h: class sp_head: - Added m_spfuns, m_spprocs members for storing names of routines used by this routine. - Added add_used_tables_to_table_list() method which allows to add tables needed by this routine to query's table list. - Converted sp_merge_table_list() to sp_head::merge_table_list() method. - Changed semantics of THD::m_sptabs. Now it is multi-set which contains only tables which are used by this routine and not routines that are called from this one. Removed sp_merge_routine_tables(), sp_merge_table_hash(), sp_open_and_lock_tables(), sp_unlock_tables() calls since they are not used for our prelocking list calculation. Added auxilary sp_lex_keeper class to which instructions delegate responsibility for handling LEX and preparations before executing statement or calculating complex expression. This class uses new sp_instr::exec_core() method which is responsible for executing instruction's core function after all preparations were made. All instructions which hold and calculate complex expression now have their own LEX (by aggregating sp_lex_keeper instance). sp_instr_stmt now uses sp_lex_keeper too. Removed sp_instr_set_user_var class which is no longer used, because nowdays we allow execution of statements in stored functions and triggers. sql/sp_rcontext.cc: Now sp_cursor holds pointer to sp_lex_keeper instead of LEX. sql/sp_rcontext.h: Now sp_cursor holds pointer to sp_lex_keeper instead of LEX. sql/sql_acl.cc: acl_init(), grant_init(): Now we use simple_open_n_lock_tables() instead of explicit calls to open_tables() and mysql_lock_tables(). sql/sql_base.cc: Implemented support for execution of statements in "prelocked" mode. When we have statement which uses stored routines explicitly or implicitly (via views or triggers) we have to open and lock all tables for these routines at the same time as tables for the main statement. In fact we have to do implicit LOCK TABLES at the begining of such statement and implict UNLOCK TABLES at its end. We call such mode "prelocked". When open_tables() is called for the statement tables which are needed for execution of routines used by it are added to its tables list (this process also caches all routines used). Implicit use of routines is discovered when we open view or table with trigger and apropriate tables are added to the table list at this moment. Statement which has such extra tables in its list (well actually any that uses functions) is marked as requiring prelocked mode for its execution. When lock_tables() sees such statement it will issue implicit LOCK TABLES for this extended table list instead of doing usual locking, it will also set THD::prelocked_mode to indicate that we are in prelocked mode. When open_tables()/lock_tables() are called for statement of stored routine (substatement), they notice that we are running in prelocked mode and use one of prelocked tables from those that are not used by upper levels of execution. close_thread_tables() for substatement won't really close tables used but will mark them as free for reuse instead. Finally when close_thread_tables() is called for the main statement it really unlocks and closes all tables used. Everything will work even if one uses such statement under real LOCK TABLES (we are simply not doing implicit LOCK/UNLOCK in this case). sql/sql_class.cc: Added initialization of THD::prelocked_mode member. sql/sql_class.h: - Added prelocked_mode_type enum and THD::prelocked_mode member which are used for indication whenever "prelocked mode" is on (i.e. that statement uses stored routines and is executed under implicit LOCK TABLES). - Removed THD::shortcut_make_view which is no longer needed. We use TABLE_LIST::prelocking_placeholder for the same purprose now. sql/sql_handler.cc: Changed open_tables() invocation. Now its 2nd parameter is in/out since it can add elements to table list. sql/sql_lex.cc: lex_start(): Added initialization of LEX::query_tables_own_last. Unused LEX::sptabs member was removed. st_lex::unlink_first_table()/link_first_table_back(): We should update LEX::query_tables_last properly if table list contains(ed) only one element. sql/sql_lex.h: LEX: - Removed sptabs member since it is no longer used. - Added query_tables_own_last member, which if non-0 indicates that statement requires prelocking (implicit LOCK TABLES) for its execution and points to last own element in query table list. If it is zero then this query does not need prelocking. - Added requires_prelocking(), mark_as_requiring_prelocking(), first_not_own_table() inline methods to incapsulate and simplify usage of this new member. sql/sql_parse.cc: dispatch_command(): To properly leave prelocked mode when needed we should call close_thread_tables() even if there are no open tables. mysql_execute_command(): - Removed part of function which were responsible for doing implicit LOCK TABLES before statement execution if statement used stored routines (and doing UNLOCK TABLES at the end). Now we do all this in open_tables()/lock_tables()/close_thread_tables() instead. - It is also sensible to reset errors before execution of statement which uses routines. - SQLCOM_DO, SQLCOM_SET_OPTION, SQLCOM_CALL We should always try to open tables because even if statement has empty table list, it can call routines using tables, which should be preopened before statement execution. - SQLCOM_CALL We should not look up routine called in mysql.proc, since it should be already cached by this moment by open_tables() call. - SQLCOM_LOCK_TABLES it is better to use simple_open_n_lock_tables() since we want to avoid materialization of derived tables for this command. sql/sql_prepare.cc: mysql_test_update(): Changed open_tables() invocations. Now its 2nd parameter is in/out since it can add elements to table list. check_prepared_statement(): Since now we cache all routines used by statement in open_tables() we don't need to do it explicitly. mysql_stmt_prepare(): Now we should call close_thread_tables() when THD::lex points to the LEX of statement which opened tables. reset_stmt_for_execute(): Commented why we are resetting all tables in table list. sql/sql_trigger.h: Table_triggers_list::process_triggers(): We should surpress sending of ok packet when we are calling trigger's routine, since now we allow statements in them. sql/sql_update.cc: Changed open_tables() invocations. Now its 2nd parameter is in/out since it can add elements to table list. sql/sql_view.cc: mysql_make_view(): - Removed handling of routines used in view. Instead we add tables which are needed for their execution to statement's table list in open_tables(). - Now we use TABLE_LIST::prelocking_placeholder instead of THD::shortcut_make_view for indicating that view is opened only to discover which tables and routines it uses (this happens when we build extended table list for prelocking). Also now we try to avoid to modify main LEX in this case (except of its table list). - Corrected small error we added tables to the table list of the main LEX without updating its query_tables_last member properly. sql/sql_yacc.yy: Now each expression which is used in SP statements and can contain subquery has its own LEX. This LEX is stored in corresponding sp_instr object and used along with Item tree for expression calculation. We don't need sp_instr_set_user_var() anymore since now we allow execution of statements in stored functions and triggers. sql/table.h: Added TABLE_LIST::prelocking_placeholder member for distinguishing elements of table list which does not belong to the statement itself and added there only for prelocking (as they are to be used by routines called by this statement). sql/tztime.cc: my_tz_init(): Now we use more simplier simple_open_n_lock_tables() call instead of open_tables()/lock_tables() pair.
This commit is contained in:
373
sql/sql_base.cc
373
sql/sql_base.cc
@ -20,6 +20,7 @@
|
||||
#include "mysql_priv.h"
|
||||
#include "sql_select.h"
|
||||
#include "sp_head.h"
|
||||
#include "sp.h"
|
||||
#include "sql_trigger.h"
|
||||
#include <m_ctype.h>
|
||||
#include <my_dir.h>
|
||||
@ -359,7 +360,30 @@ bool close_cached_tables(THD *thd, bool if_wait_for_refresh,
|
||||
|
||||
|
||||
/*
|
||||
Close all tables used by thread
|
||||
Mark all tables in the list which were used by current substatement
|
||||
as free for reuse.
|
||||
|
||||
SYNOPSIS
|
||||
mark_used_tables_as_free_for_reuse()
|
||||
thd - thread context
|
||||
table - head of the list of tables
|
||||
|
||||
DESCRIPTION
|
||||
Marks all tables in the list which were used by current substatement
|
||||
(they are marked by its query_id) as free for reuse.
|
||||
*/
|
||||
|
||||
static void mark_used_tables_as_free_for_reuse(THD *thd, TABLE *table)
|
||||
{
|
||||
for (; table ; table= table->next)
|
||||
if (table->query_id == thd->query_id)
|
||||
table->query_id= 0;
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
Close all tables used by the current substatement, or all tables
|
||||
used by this thread if we are on the upper level.
|
||||
|
||||
SYNOPSIS
|
||||
close_thread_tables()
|
||||
@ -372,14 +396,31 @@ bool close_cached_tables(THD *thd, bool if_wait_for_refresh,
|
||||
IMPLEMENTATION
|
||||
Unlocks tables and frees derived tables.
|
||||
Put all normal tables used by thread in free list.
|
||||
|
||||
When in prelocked mode it will only close/mark as free for reuse
|
||||
tables opened by this substatement, it will also check if we are
|
||||
closing tables after execution of complete query (i.e. we are on
|
||||
upper level) and will leave prelocked mode if needed.
|
||||
*/
|
||||
|
||||
void close_thread_tables(THD *thd, bool lock_in_use, bool skip_derived,
|
||||
TABLE *stopper)
|
||||
{
|
||||
bool found_old_table;
|
||||
prelocked_mode_type prelocked_mode= thd->prelocked_mode;
|
||||
DBUG_ENTER("close_thread_tables");
|
||||
|
||||
/*
|
||||
We are assuming here that thd->derived_tables contains ONLY derived
|
||||
tables for this substatement. i.e. instead of approach which uses
|
||||
query_id matching for determining which of the derived tables belong
|
||||
to this substatement we rely on the ability of substatements to
|
||||
save/restore thd->derived_tables during their execution.
|
||||
|
||||
TODO: Probably even better approach is to simply associate list of
|
||||
derived tables with (sub-)statement instead of thread and destroy
|
||||
them at the end of its execution.
|
||||
*/
|
||||
if (thd->derived_tables && !skip_derived)
|
||||
{
|
||||
TABLE *table, *next;
|
||||
@ -394,10 +435,50 @@ void close_thread_tables(THD *thd, bool lock_in_use, bool skip_derived,
|
||||
}
|
||||
thd->derived_tables= 0;
|
||||
}
|
||||
if (thd->locked_tables)
|
||||
|
||||
if (prelocked_mode)
|
||||
{
|
||||
ha_commit_stmt(thd); // If select statement
|
||||
DBUG_VOID_RETURN; // LOCK TABLES in use
|
||||
/*
|
||||
Mark all temporary tables used by this substatement as free for reuse.
|
||||
*/
|
||||
mark_used_tables_as_free_for_reuse(thd, thd->temporary_tables);
|
||||
}
|
||||
|
||||
if (thd->locked_tables || prelocked_mode)
|
||||
{
|
||||
/*
|
||||
TODO: It is not 100% clear whenever we should do ha_commit_stmt() for
|
||||
sub-statements. This issue needs additional investigation.
|
||||
*/
|
||||
ha_commit_stmt(thd);
|
||||
|
||||
/* We are under simple LOCK TABLES so should not do anything else. */
|
||||
if (!prelocked_mode)
|
||||
DBUG_VOID_RETURN;
|
||||
|
||||
if (!thd->lex->requires_prelocking())
|
||||
{
|
||||
/*
|
||||
If we are executing one of substatements we have to mark
|
||||
all tables which it used as free for reuse.
|
||||
*/
|
||||
mark_used_tables_as_free_for_reuse(thd, thd->open_tables);
|
||||
DBUG_VOID_RETURN;
|
||||
}
|
||||
|
||||
DBUG_ASSERT(prelocked_mode);
|
||||
/*
|
||||
We are in prelocked mode, so we have to leave it now with doing
|
||||
implicit UNLOCK TABLES if need.
|
||||
*/
|
||||
thd->prelocked_mode= NON_PRELOCKED;
|
||||
|
||||
if (prelocked_mode == PRELOCKED_UNDER_LOCK_TABLES)
|
||||
DBUG_VOID_RETURN;
|
||||
|
||||
thd->lock= thd->locked_tables;
|
||||
thd->locked_tables= 0;
|
||||
/* Fallthrough */
|
||||
}
|
||||
|
||||
if (thd->lock)
|
||||
@ -429,6 +510,17 @@ void close_thread_tables(THD *thd, bool lock_in_use, bool skip_derived,
|
||||
if (!lock_in_use)
|
||||
VOID(pthread_mutex_unlock(&LOCK_open));
|
||||
/* VOID(pthread_sigmask(SIG_SETMASK,&thd->signals,NULL)); */
|
||||
|
||||
if (prelocked_mode == PRELOCKED)
|
||||
{
|
||||
/*
|
||||
If we are here then we are leaving normal prelocked mode, so it is
|
||||
good idea to turn off OPTION_TABLE_LOCK flag.
|
||||
*/
|
||||
DBUG_ASSERT(thd->lex->requires_prelocking());
|
||||
thd->options&= ~(ulong) (OPTION_TABLE_LOCK);
|
||||
}
|
||||
|
||||
DBUG_VOID_RETURN;
|
||||
}
|
||||
|
||||
@ -898,7 +990,8 @@ TABLE *open_table(THD *thd, TABLE_LIST *table_list, MEM_ROOT *mem_root,
|
||||
!memcmp(table->s->table_cache_key, key,
|
||||
key_length + TMP_TABLE_KEY_EXTRA))
|
||||
{
|
||||
if (table->query_id == thd->query_id)
|
||||
if (table->query_id == thd->query_id ||
|
||||
thd->prelocked_mode && table->query_id)
|
||||
{
|
||||
my_error(ER_CANT_REOPEN_TABLE, MYF(0), table->alias);
|
||||
DBUG_RETURN(0);
|
||||
@ -912,16 +1005,17 @@ TABLE *open_table(THD *thd, TABLE_LIST *table_list, MEM_ROOT *mem_root,
|
||||
}
|
||||
}
|
||||
|
||||
if (thd->locked_tables)
|
||||
if (thd->locked_tables || thd->prelocked_mode)
|
||||
{ // Using table locks
|
||||
for (table=thd->open_tables; table ; table=table->next)
|
||||
{
|
||||
if (table->s->key_length == key_length &&
|
||||
!memcmp(table->s->table_cache_key,key,key_length) &&
|
||||
!my_strcasecmp(system_charset_info, table->alias, alias))
|
||||
!memcmp(table->s->table_cache_key, key, key_length) &&
|
||||
!my_strcasecmp(system_charset_info, table->alias, alias) &&
|
||||
table->query_id != thd->query_id && /* skip tables already used by this query */
|
||||
!(thd->prelocked_mode && table->query_id))
|
||||
{
|
||||
if (table->query_id != thd->query_id)
|
||||
table->query_id=thd->query_id;
|
||||
table->query_id= thd->query_id;
|
||||
DBUG_PRINT("info",("Using locked table"));
|
||||
goto reset;
|
||||
}
|
||||
@ -1613,21 +1707,34 @@ err:
|
||||
SYNOPSIS
|
||||
open_tables()
|
||||
thd - thread handler
|
||||
start - list of tables
|
||||
start - list of tables in/out
|
||||
counter - number of opened tables will be return using this parameter
|
||||
|
||||
NOTE
|
||||
Unless we are already in prelocked mode, this function will also precache
|
||||
all SP/SFs explicitly or implicitly (via views and triggers) used by the
|
||||
query and add tables needed for their execution to table list. If resulting
|
||||
tables list will be non empty it will mark query as requiring precaching.
|
||||
Prelocked mode will be enabled for such query during lock_tables() call.
|
||||
|
||||
If query for which we are opening tables is already marked as requiring
|
||||
prelocking it won't do such precaching and will simply reuse table list
|
||||
which is already built.
|
||||
|
||||
RETURN
|
||||
0 - OK
|
||||
-1 - error
|
||||
*/
|
||||
|
||||
int open_tables(THD *thd, TABLE_LIST *start, uint *counter)
|
||||
int open_tables(THD *thd, TABLE_LIST **start, uint *counter)
|
||||
{
|
||||
TABLE_LIST *tables;
|
||||
bool refresh;
|
||||
int result=0;
|
||||
DBUG_ENTER("open_tables");
|
||||
MEM_ROOT new_frm_mem;
|
||||
/* Also used for indicating that prelocking is need */
|
||||
TABLE_LIST **query_tables_last_own;
|
||||
DBUG_ENTER("open_tables");
|
||||
/*
|
||||
temporary mem_root for new .frm parsing.
|
||||
TODO: variables for size
|
||||
@ -1637,8 +1744,51 @@ int open_tables(THD *thd, TABLE_LIST *start, uint *counter)
|
||||
thd->current_tablenr= 0;
|
||||
restart:
|
||||
*counter= 0;
|
||||
query_tables_last_own= 0;
|
||||
thd->proc_info="Opening tables";
|
||||
for (tables= start; tables ;tables= tables->next_global)
|
||||
|
||||
/*
|
||||
If we are not already executing prelocked statement and don't have
|
||||
statement for which table list for prelocking is already built, let
|
||||
us cache routines and try to build such table list.
|
||||
|
||||
NOTE: If we want queries with functions to work under explicit
|
||||
LOCK TABLES we have to additionaly lock mysql.proc table in it.
|
||||
At least until Monty will fix SP loading :)
|
||||
|
||||
NOTE: We can't delay prelocking until we will met some sub-statement
|
||||
which really uses tables, since this will imply that we have to restore
|
||||
its table list to be able execute it in some other context.
|
||||
And current views implementation assumes that view tables are added to
|
||||
global table list only once during PS preparing/first SP execution.
|
||||
Also locking at earlier stage is probably faster altough may decrease
|
||||
concurrency a bit.
|
||||
|
||||
NOTE: We will mark statement as requiring prelocking only if we will
|
||||
have non empty table list. But this does not guarantee that in prelocked
|
||||
mode we will have some locked tables, because queries which use only
|
||||
derived/information schema tables and views possible. Thus "counter"
|
||||
may be still zero for prelocked statement...
|
||||
*/
|
||||
if (!thd->prelocked_mode && !thd->lex->requires_prelocking() &&
|
||||
(thd->lex->spfuns.records || thd->lex->spprocs.records))
|
||||
{
|
||||
TABLE_LIST **save_query_tables_last;
|
||||
|
||||
sp_cache_routines(thd, thd->lex);
|
||||
save_query_tables_last= thd->lex->query_tables_last;
|
||||
|
||||
DBUG_ASSERT(thd->lex->query_tables == *start);
|
||||
|
||||
if (sp_add_sp_tables_to_table_list(thd, thd->lex, thd->lex) ||
|
||||
*start)
|
||||
{
|
||||
query_tables_last_own= save_query_tables_last;
|
||||
*start= thd->lex->query_tables;
|
||||
}
|
||||
}
|
||||
|
||||
for (tables= *start; tables ;tables= tables->next_global)
|
||||
{
|
||||
/*
|
||||
Ignore placeholders for derived tables. After derived tables
|
||||
@ -1659,8 +1809,27 @@ int open_tables(THD *thd, TABLE_LIST *start, uint *counter)
|
||||
free_root(&new_frm_mem, MYF(MY_KEEP_PREALLOC));
|
||||
if (tables->view)
|
||||
{
|
||||
/* VIEW placeholder */
|
||||
(*counter)--;
|
||||
continue; //VIEW placeholder
|
||||
/*
|
||||
Again if needed we have to get cache all routines used by this view
|
||||
and add tables used by them to table list.
|
||||
*/
|
||||
if (!thd->prelocked_mode && !thd->lex->requires_prelocking() &&
|
||||
(tables->view->spfuns.records || tables->view->spprocs.records))
|
||||
{
|
||||
// FIXME We should catch recursion for both views and funcs here
|
||||
sp_cache_routines(thd, tables->view);
|
||||
|
||||
/* We have at least one table in TL here */
|
||||
if (!query_tables_last_own)
|
||||
query_tables_last_own= thd->lex->query_tables_last;
|
||||
sp_add_sp_tables_to_table_list(thd, thd->lex, tables->view);
|
||||
}
|
||||
/* Cleanup hashes because destructo for this LEX is never called */
|
||||
hash_free(&tables->view->spfuns);
|
||||
hash_free(&tables->view->spprocs);
|
||||
continue;
|
||||
}
|
||||
|
||||
if (refresh) // Refresh in progress
|
||||
@ -1672,7 +1841,12 @@ int open_tables(THD *thd, TABLE_LIST *start, uint *counter)
|
||||
thd->version=refresh_version;
|
||||
TABLE **prev_table= &thd->open_tables;
|
||||
bool found=0;
|
||||
for (TABLE_LIST *tmp= start; tmp; tmp= tmp->next_global)
|
||||
/*
|
||||
QQ: What we should do if we have started building of table list
|
||||
for prelocking ??? Probably throw it away ? But before we should
|
||||
mark all temporary tables as free? How about locked ?
|
||||
*/
|
||||
for (TABLE_LIST *tmp= *start; tmp; tmp= tmp->next_global)
|
||||
{
|
||||
/* Close normal (not temporary) changed tables */
|
||||
if (tmp->table && ! tmp->table->s->tmp_table)
|
||||
@ -1701,7 +1875,27 @@ int open_tables(THD *thd, TABLE_LIST *start, uint *counter)
|
||||
break;
|
||||
}
|
||||
else
|
||||
{
|
||||
/*
|
||||
If we are not already in prelocked mode and extended table list is not
|
||||
yet built and we have trigger for table being opened then we should
|
||||
cache all routines used by its triggers and add their tables to
|
||||
prelocking list.
|
||||
If we lock table for reading we won't update it so there is no need to
|
||||
process its triggers since they never will be activated.
|
||||
|
||||
FIXME Now we are simply turning on prelocking. Proper integration
|
||||
and testing is to be done later.
|
||||
*/
|
||||
if (!thd->prelocked_mode && !thd->lex->requires_prelocking() &&
|
||||
tables->table->triggers &&
|
||||
tables->lock_type >= TL_WRITE_ALLOW_WRITE)
|
||||
{
|
||||
if (!query_tables_last_own)
|
||||
query_tables_last_own= thd->lex->query_tables_last;
|
||||
}
|
||||
free_root(&new_frm_mem, MYF(MY_KEEP_PREALLOC));
|
||||
}
|
||||
|
||||
if (tables->lock_type != TL_UNLOCK && ! thd->locked_tables)
|
||||
tables->table->reginfo.lock_type=tables->lock_type;
|
||||
@ -1709,6 +1903,10 @@ int open_tables(THD *thd, TABLE_LIST *start, uint *counter)
|
||||
}
|
||||
thd->proc_info=0;
|
||||
free_root(&new_frm_mem, MYF(0)); // Free pre-alloced block
|
||||
|
||||
if (query_tables_last_own)
|
||||
thd->lex->mark_as_requiring_prelocking(query_tables_last_own);
|
||||
|
||||
DBUG_RETURN(result);
|
||||
}
|
||||
|
||||
@ -1757,6 +1955,11 @@ static bool check_lock_and_start_stmt(THD *thd, TABLE *table,
|
||||
table_list Table to open is first table in this list
|
||||
lock_type Lock to use for open
|
||||
|
||||
NOTE
|
||||
This function don't do anything like SP/SF/views/triggers analysis done
|
||||
in open_tables(). It is intended for opening of only one concrete table.
|
||||
And used only in special contexts.
|
||||
|
||||
RETURN VALUES
|
||||
table Opened table
|
||||
0 Error
|
||||
@ -1831,7 +2034,7 @@ int simple_open_n_lock_tables(THD *thd, TABLE_LIST *tables)
|
||||
{
|
||||
DBUG_ENTER("simple_open_n_lock_tables");
|
||||
uint counter;
|
||||
if (open_tables(thd, tables, &counter) || lock_tables(thd, tables, counter))
|
||||
if (open_tables(thd, &tables, &counter) || lock_tables(thd, tables, counter))
|
||||
DBUG_RETURN(-1); /* purecov: inspected */
|
||||
DBUG_RETURN(0);
|
||||
}
|
||||
@ -1858,7 +2061,7 @@ bool open_and_lock_tables(THD *thd, TABLE_LIST *tables)
|
||||
{
|
||||
uint counter;
|
||||
DBUG_ENTER("open_and_lock_tables");
|
||||
if (open_tables(thd, tables, &counter) ||
|
||||
if (open_tables(thd, &tables, &counter) ||
|
||||
lock_tables(thd, tables, counter) ||
|
||||
mysql_handle_derived(thd->lex, &mysql_derived_prepare) ||
|
||||
(thd->fill_derived_tables() &&
|
||||
@ -1891,7 +2094,7 @@ bool open_normal_and_derived_tables(THD *thd, TABLE_LIST *tables)
|
||||
uint counter;
|
||||
DBUG_ENTER("open_normal_and_derived_tables");
|
||||
DBUG_ASSERT(!thd->fill_derived_tables());
|
||||
if (open_tables(thd, tables, &counter) ||
|
||||
if (open_tables(thd, &tables, &counter) ||
|
||||
mysql_handle_derived(thd->lex, &mysql_derived_prepare))
|
||||
DBUG_RETURN(TRUE); /* purecov: inspected */
|
||||
relink_tables_for_multidelete(thd); // Not really needed, but
|
||||
@ -1924,6 +2127,27 @@ static void relink_tables_for_multidelete(THD *thd)
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
Mark all real tables in the list as free for reuse.
|
||||
|
||||
SYNOPSIS
|
||||
mark_real_tables_as_free_for_reuse()
|
||||
thd - thread context
|
||||
table - head of the list of tables
|
||||
|
||||
DESCRIPTION
|
||||
Marks all real tables in the list (i.e. not views, derived
|
||||
or schema tables) as free for reuse.
|
||||
*/
|
||||
|
||||
static void mark_real_tables_as_free_for_reuse(TABLE_LIST *table)
|
||||
{
|
||||
for (; table; table= table->next_global)
|
||||
if (!table->placeholder() && !table->schema_table)
|
||||
table->table->query_id= 0;
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
Lock all tables in list
|
||||
|
||||
@ -1938,6 +2162,10 @@ static void relink_tables_for_multidelete(THD *thd)
|
||||
handling thr_lock gives us. You most always get all needed locks at
|
||||
once.
|
||||
|
||||
If query for which we are calling this function marked as requring
|
||||
prelocking, this function will do implicit LOCK TABLES and change
|
||||
thd::prelocked_mode accordingly.
|
||||
|
||||
RETURN VALUES
|
||||
0 ok
|
||||
-1 Error
|
||||
@ -1946,36 +2174,125 @@ static void relink_tables_for_multidelete(THD *thd)
|
||||
int lock_tables(THD *thd, TABLE_LIST *tables, uint count)
|
||||
{
|
||||
TABLE_LIST *table;
|
||||
if (!tables)
|
||||
return 0;
|
||||
|
||||
if (!thd->locked_tables)
|
||||
DBUG_ENTER("lock_tables");
|
||||
/*
|
||||
We can't meet statement requiring prelocking if we already
|
||||
in prelocked mode.
|
||||
*/
|
||||
DBUG_ASSERT(!thd->prelocked_mode || !thd->lex->requires_prelocking());
|
||||
/*
|
||||
If statement requires prelocking then it has non-empty table list.
|
||||
So it is safe to shortcut.
|
||||
*/
|
||||
DBUG_ASSERT(!thd->lex->requires_prelocking() || tables);
|
||||
|
||||
if (!tables)
|
||||
DBUG_RETURN(0);
|
||||
|
||||
/*
|
||||
We need this extra check for thd->prelocked_mode because we want to avoid
|
||||
attempts to lock tables in substatements. Checking for thd->locked_tables
|
||||
is not enough in some situations. For example for SP containing
|
||||
"drop table t3; create temporary t3 ..; insert into t3 ...;"
|
||||
thd->locked_tables may be 0 after drop tables, and without this extra
|
||||
check insert will try to lock temporary table t3, that will lead
|
||||
to memory leak...
|
||||
*/
|
||||
if (!thd->locked_tables && !thd->prelocked_mode)
|
||||
{
|
||||
DBUG_ASSERT(thd->lock == 0); // You must lock everything at once
|
||||
TABLE **start,**ptr;
|
||||
|
||||
if (!(ptr=start=(TABLE**) thd->alloc(sizeof(TABLE*)*count)))
|
||||
return -1;
|
||||
DBUG_RETURN(-1);
|
||||
for (table= tables; table; table= table->next_global)
|
||||
{
|
||||
if (!table->placeholder() && !table->schema_table)
|
||||
*(ptr++)= table->table;
|
||||
}
|
||||
|
||||
/* We have to emulate LOCK TABLES if we are statement needs prelocking. */
|
||||
if (thd->lex->requires_prelocking())
|
||||
{
|
||||
thd->in_lock_tables=1;
|
||||
thd->options|= OPTION_TABLE_LOCK;
|
||||
}
|
||||
|
||||
if (!(thd->lock=mysql_lock_tables(thd,start, (uint) (ptr - start))))
|
||||
return -1; /* purecov: inspected */
|
||||
{
|
||||
if (thd->lex->requires_prelocking())
|
||||
{
|
||||
thd->options&= ~(ulong) (OPTION_TABLE_LOCK);
|
||||
thd->in_lock_tables=0;
|
||||
}
|
||||
DBUG_RETURN(-1);
|
||||
}
|
||||
if (thd->lex->requires_prelocking() &&
|
||||
thd->lex->sql_command != SQLCOM_LOCK_TABLES)
|
||||
{
|
||||
TABLE_LIST *first_not_own= thd->lex->first_not_own_table();
|
||||
/*
|
||||
We just have done implicit LOCK TABLES, and now we have
|
||||
to emulate first open_and_lock_tables() after it.
|
||||
|
||||
Note that "LOCK TABLES" can also be marked as requiring prelocking
|
||||
(e.g. if one locks view which uses functions). We should not emulate
|
||||
such open_and_lock_tables() in this case. We also should not set
|
||||
THD::prelocked_mode or first close_thread_tables() call will do
|
||||
"UNLOCK TABLES".
|
||||
*/
|
||||
thd->locked_tables= thd->lock;
|
||||
thd->lock= 0;
|
||||
thd->in_lock_tables=0;
|
||||
|
||||
for (table= tables; table != first_not_own; table= table->next_global)
|
||||
{
|
||||
if (!table->placeholder() && !table->schema_table)
|
||||
{
|
||||
table->table->query_id= thd->query_id;
|
||||
if (check_lock_and_start_stmt(thd, table->table, table->lock_type))
|
||||
{
|
||||
ha_rollback_stmt(thd);
|
||||
mysql_unlock_tables(thd, thd->locked_tables);
|
||||
thd->locked_tables= 0;
|
||||
thd->options&= ~(ulong) (OPTION_TABLE_LOCK);
|
||||
DBUG_RETURN(-1);
|
||||
}
|
||||
}
|
||||
}
|
||||
/*
|
||||
Let us mark all tables which don't belong to the statement itself,
|
||||
and was marked as occupied during open_tables() as free for reuse.
|
||||
*/
|
||||
mark_real_tables_as_free_for_reuse(first_not_own);
|
||||
thd->prelocked_mode= PRELOCKED;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
for (table= tables; table; table= table->next_global)
|
||||
TABLE_LIST *first_not_own= thd->lex->first_not_own_table();
|
||||
for (table= tables; table != first_not_own; table= table->next_global)
|
||||
{
|
||||
if (!table->placeholder() &&
|
||||
if (!table->placeholder() && !table->schema_table &&
|
||||
check_lock_and_start_stmt(thd, table->table, table->lock_type))
|
||||
{
|
||||
ha_rollback_stmt(thd);
|
||||
return -1;
|
||||
DBUG_RETURN(-1);
|
||||
}
|
||||
}
|
||||
/*
|
||||
If we are under explicit LOCK TABLES and our statement requires
|
||||
prelocking, we should mark all "additional" tables as free for use
|
||||
and enter prelocked mode.
|
||||
*/
|
||||
if (thd->lex->requires_prelocking())
|
||||
{
|
||||
mark_real_tables_as_free_for_reuse(first_not_own);
|
||||
thd->prelocked_mode= PRELOCKED_UNDER_LOCK_TABLES;
|
||||
}
|
||||
}
|
||||
return 0;
|
||||
DBUG_RETURN(0);
|
||||
}
|
||||
|
||||
|
||||
|
Reference in New Issue
Block a user