MDEV-34309 [1/2] prelock referentially related tables for CHECK TABLE

This commit is contained in:
Nikita Malyavin 2024-11-21 18:32:07 +01:00
commit 1115f790dd
5 changed files with 164 additions and 61 deletions

View file

@ -3682,6 +3682,7 @@ sp_head::add_used_tables_to_table_list(THD *thd,
belong_to_view,
stab->trg_event_map,
query_tables_last_ptr,
TABLE_LIST::OPEN_NORMAL,
stab->for_insert_data);
tab_buff+= ALIGN_SIZE(sizeof(TABLE_LIST));
result= TRUE;

View file

@ -342,9 +342,11 @@ static inline bool table_not_corrupt_error(uint sql_errno)
static int debug_fail_counter= 0;
#endif
static bool open_only_one_table(THD* thd, TABLE_LIST* table,
bool repair_table_use_frm,
bool is_view_operator_func)
static
bool open_only_one_table(THD* thd, TABLE_LIST* table,
bool repair_table_use_frm,
bool is_view_operator_func,
Prelocking_strategy *prelock_strategy)
{
LEX *lex= thd->lex;
SELECT_LEX *select= lex->first_select_lex();
@ -433,7 +435,7 @@ static bool open_only_one_table(THD* thd, TABLE_LIST* table,
*/
open_error= (thd->open_temporary_tables(table) ||
open_and_lock_tables(thd, table, TRUE, 0));
open_and_lock_tables(thd, table, TRUE, 0, prelock_strategy));
}
#ifndef DBUG_OFF
@ -647,9 +649,11 @@ static bool mysql_admin_table(THD* thd, TABLE_LIST* tables,
/* open only one table from local list of command */
while (1)
{
Check_table_prelocking_strategy prelocking_strategy;
open_error= open_only_one_table(thd, table,
no_errors_from_open,
(view_operator_func != NULL));
(view_operator_func != NULL),
&prelocking_strategy);
thd->open_options&= ~extra_open_options;
/*
@ -974,8 +978,10 @@ static bool mysql_admin_table(THD* thd, TABLE_LIST* tables,
table->lock_type= TL_READ;
DBUG_ASSERT(view_operator_func == NULL);
DML_prelocking_strategy prelocking_strategy;
open_error= open_only_one_table(thd, table,
no_errors_from_open, FALSE);
no_errors_from_open, FALSE,
&prelocking_strategy);
thd->open_options&= ~extra_open_options;
if (unlikely(!open_error))

View file

@ -3910,9 +3910,8 @@ bool extend_table_list(THD *thd, TABLE_LIST *tables,
{
bool error= false;
LEX *lex= thd->lex;
bool maybe_need_prelocking=
(tables->updating && tables->lock_type >= TL_FIRST_WRITE)
|| thd->lex->default_used;
bool maybe_need_prelocking= prelocking_strategy->may_need_prelocking(thd,
tables);
if (thd->locked_tables_mode <= LTM_LOCK_TABLES &&
! has_prelocking_list && maybe_need_prelocking)
@ -4946,8 +4945,8 @@ bool DML_prelocking_strategy::handle_routine(THD *thd,
@note this can be changed to use a hash, instead of scanning the linked
list, if the performance of this function will ever become an issue
*/
bool table_already_fk_prelocked(TABLE_LIST *tl, LEX_CSTRING *db,
LEX_CSTRING *table, thr_lock_type lock_type)
bool table_already_fk_prelocked(TABLE_LIST *tl, const LEX_CSTRING *db,
const LEX_CSTRING *table, thr_lock_type lock_type)
{
for (; tl; tl= tl->next_global )
{
@ -5009,6 +5008,7 @@ add_internal_tables(THD *thd, Query_tables_list *prelocking_ctx,
TABLE_LIST::PRELOCK_NONE,
0, 0,
&prelocking_ctx->query_tables_last,
TABLE_LIST::OPEN_NORMAL,
tables->for_insert_data);
/*
Store link to the new table_list that will be used by open so that
@ -5020,6 +5020,12 @@ add_internal_tables(THD *thd, Query_tables_list *prelocking_ctx,
DBUG_RETURN(FALSE);
}
static bool
prepare_fk_prelocking_list(THD *thd, Query_tables_list *prelocking_ctx,
const TABLE_LIST *table_list,
const List <FOREIGN_KEY_INFO> &fk_list,
const Prelocking_strategy *prelocking_strategy,
uint8 row_event_map, bool parent);
/**
Extend the table_list to include foreign tables for prelocking.
@ -5034,55 +5040,77 @@ add_internal_tables(THD *thd, Query_tables_list *prelocking_ctx,
@retval TRUE Failure (OOM).
*/
inline bool
prepare_fk_prelocking_list(THD *thd, Query_tables_list *prelocking_ctx,
TABLE_LIST *table_list, bool *need_prelocking,
uint8 op)
prepare_fk_parent_prelocking_list(THD *thd, Query_tables_list *prelocking_ctx,
TABLE_LIST *table_list,
const
Prelocking_strategy *prelocking_strategy,
bool *need_prelocking,
uint8 op)
{
DBUG_ENTER("prepare_fk_prelocking_list");
DBUG_ENTER("prepare_fk_parent_prelocking_list");
List <FOREIGN_KEY_INFO> fk_list;
List_iterator<FOREIGN_KEY_INFO> fk_list_it(fk_list);
FOREIGN_KEY_INFO *fk;
Query_arena *arena, backup;
TABLE *table= table_list->table;
if (!table->file->referenced_by_foreign_key())
DBUG_RETURN(FALSE);
arena= thd->activate_stmt_arena_if_needed(&backup);
Query_arena_stmt arena_ctx(thd);
table->file->get_parent_foreign_key_list(thd, &fk_list);
if (unlikely(thd->is_error()))
{
if (arena)
thd->restore_active_arena(arena, &backup);
return TRUE;
}
*need_prelocking= TRUE;
*need_prelocking= true;
DBUG_RETURN(prepare_fk_prelocking_list(thd, prelocking_ctx, table_list, fk_list,
prelocking_strategy, op, true));
}
while ((fk= fk_list_it++))
inline bool fk_has_cascade_event(const FOREIGN_KEY_INFO &fk,
uint8 row_event_map)
{
bool can_delete= row_event_map & trg2bit(TRG_EVENT_DELETE);
bool can_update= row_event_map & trg2bit(TRG_EVENT_UPDATE);
return (can_delete && fk_modifies_child(fk.delete_method))
|| (can_update && fk_modifies_child(fk.update_method));
}
inline bool
prepare_fk_prelocking_list(THD *thd, Query_tables_list *prelocking_ctx,
const TABLE_LIST *table_list,
const List <FOREIGN_KEY_INFO> &fk_list,
const Prelocking_strategy *prelocking_strategy,
uint8 row_event_map,
bool parent)
{
if (!parent)
row_event_map= 0; // Child tables are never updated.
for (const FOREIGN_KEY_INFO &fk: fk_list)
{
// FK_OPTION_RESTRICT and FK_OPTION_NO_ACTION only need read access
thr_lock_type lock_type;
const LEX_CSTRING &rel_table_name= parent ? fk.foreign_table
: fk.referenced_table;
const LEX_CSTRING &rel_table_db= parent ? fk.foreign_db : fk.referenced_db;
if ((op & trg2bit(TRG_EVENT_DELETE) && fk_modifies_child(fk->delete_method))
|| (op & trg2bit(TRG_EVENT_UPDATE) && fk_modifies_child(fk->update_method)))
lock_type= TL_FIRST_WRITE;
else
lock_type= TL_READ;
// FK_OPTION_RESTRICT and FK_OPTION_NO_ACTION only need read access
thr_lock_type lock_type= fk_has_cascade_event(fk, row_event_map)
? TL_FIRST_WRITE : TL_READ;
if (table_already_fk_prelocked(prelocking_ctx->query_tables,
&fk->foreign_db, &fk->foreign_table, lock_type))
&rel_table_db, &rel_table_name,
lock_type))
continue;
TABLE_LIST *tl= new (thd) 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);
tl->init_one_table_for_prelocking(&rel_table_db,
&rel_table_name,
NULL, lock_type,
TABLE_LIST::PRELOCK_FK,
table_list->belong_to_view, row_event_map,
&prelocking_ctx->query_tables_last,
prelocking_strategy->get_fk_open_strategy(lock_type),
table_list->for_insert_data);
}
if (arena)
thd->restore_active_arena(arena, &backup);
DBUG_RETURN(FALSE);
return FALSE;
}
/**
@ -5128,16 +5156,16 @@ bool DML_prelocking_strategy::handle_table(THD *thd,
return TRUE;
}
if (prepare_fk_prelocking_list(thd, prelocking_ctx, table_list,
need_prelocking,
table_list->trg_event_map))
if (prepare_fk_parent_prelocking_list(thd, prelocking_ctx, table_list,
this, need_prelocking,
table_list->trg_event_map))
return TRUE;
}
else if (table_list->slave_fk_event_map)
{
if (prepare_fk_prelocking_list(thd, prelocking_ctx, table_list,
need_prelocking,
table_list->slave_fk_event_map))
if (prepare_fk_parent_prelocking_list(thd, prelocking_ctx, table_list,
this, need_prelocking,
table_list->slave_fk_event_map))
return TRUE;
}
@ -5166,6 +5194,28 @@ bool DML_prelocking_strategy::handle_table(THD *thd,
}
bool
Check_table_prelocking_strategy::handle_table(THD *thd,
Query_tables_list *prelocking_ctx,
TABLE_LIST *tables,
bool *need_prelocking)
{
Query_arena_stmt stmt_ctx(thd);
List <FOREIGN_KEY_INFO> fk_child_list, fk_parent_list;
tables->table->file->get_foreign_key_list(thd, &fk_child_list);
tables->table->file->get_parent_foreign_key_list(thd, &fk_parent_list);
if (unlikely(thd->is_error()))
return TRUE;
for (const auto *fk_list: {&fk_child_list, &fk_parent_list})
{
if (prepare_fk_prelocking_list(thd, prelocking_ctx, tables, *fk_list, this,
0, fk_list == &fk_parent_list))
return TRUE;
}
return FALSE;
}
/**
Open all tables used by DEFAULT functions.

View file

@ -397,6 +397,13 @@ inline bool setup_fields_with_no_wrap(THD *thd, Ref_ptr_array ref_pointer_array,
return res;
}
inline static
bool check_table_referential_checks_needed(THD *thd)
{
return thd->lex->check_opt.flags & T_EXTEND
&& !(thd->variables.option_bits & OPTION_NO_FOREIGN_KEY_CHECKS);
}
/**
An abstract class for a strategy specifying how the prelocking
algorithm should extend the prelocking set while processing
@ -417,6 +424,19 @@ public:
virtual bool handle_view(THD *thd, Query_tables_list *prelocking_ctx,
TABLE_LIST *table_list, bool *need_prelocking)= 0;
virtual bool handle_end(THD *thd) { return 0; };
virtual bool may_need_prelocking(THD *thd, TABLE_LIST *table) const
{
return (table->updating && table->lock_type >= TL_FIRST_WRITE)
|| thd->lex->default_used;
}
virtual TABLE_LIST::enum_open_strategy
get_fk_open_strategy(thr_lock_type lock_type) const
{
DBUG_ASSERT(!current_thd->lex->query_tables->updating); // should be DML
return TABLE_LIST::OPEN_STUB;
}
};
@ -438,6 +458,13 @@ public:
TABLE_LIST *table_list, bool *need_prelocking) override;
bool handle_view(THD *thd, Query_tables_list *prelocking_ctx,
TABLE_LIST *table_list, bool *need_prelocking) override;
TABLE_LIST::enum_open_strategy
get_fk_open_strategy(thr_lock_type lock_type) const override
{
return lock_type < TL_FIRST_WRITE ? TABLE_LIST::OPEN_STUB
: TABLE_LIST::OPEN_NORMAL;
}
};
@ -485,6 +512,23 @@ public:
};
class Check_table_prelocking_strategy : public DML_prelocking_strategy
{
public:
bool handle_table(THD *thd, Query_tables_list *prelocking_ctx,
TABLE_LIST *table_list, bool *need_prelocking) override;
bool may_need_prelocking(THD *thd, TABLE_LIST *table) const override
{
return check_table_referential_checks_needed(thd);
}
TABLE_LIST::enum_open_strategy
get_fk_open_strategy(thr_lock_type lock_type) const override
{
return TABLE_LIST::OPEN_NORMAL;
}
};
inline bool
open_tables(THD *thd, const DDL_options_st &options,
TABLE_LIST **tables, uint *counter, uint flags)

View file

@ -2499,6 +2499,20 @@ struct TABLE_LIST
vers_conditions.name= table->s->vers.name;
}
/**
Indicates that if TABLE_LIST object corresponds to the table/view
which requires special handling.
*/
enum enum_open_strategy
{
/* Normal open. */
OPEN_NORMAL= 0,
/* Associate a table share only if the the table exists. */
OPEN_IF_EXISTS,
/* Don't associate a table share. */
OPEN_STUB
};
inline void init_one_table_for_prelocking(const LEX_CSTRING *db_arg,
const LEX_CSTRING *table_name_arg,
const LEX_CSTRING *alias_arg,
@ -2507,6 +2521,7 @@ struct TABLE_LIST
TABLE_LIST *belong_to_view_arg,
uint8 trg_event_map_arg,
TABLE_LIST ***last_ptr,
enum_open_strategy open_strat,
my_bool insert_data)
{
@ -2519,8 +2534,7 @@ struct TABLE_LIST
belong_to_view= belong_to_view_arg;
trg_event_map= trg_event_map_arg;
/* MDL is enough for read-only FK checks, we don't need the table */
if (prelocking_type == PRELOCK_FK && lock_type < TL_FIRST_WRITE)
open_strategy= OPEN_STUB;
open_strategy= open_strat;
**last_ptr= this;
prev_global= *last_ptr;
@ -2852,19 +2866,7 @@ struct TABLE_LIST
used for implicit LOCK TABLES only and won't be used in real statement.
*/
prelocking_types prelocking_placeholder;
/**
Indicates that if TABLE_LIST object corresponds to the table/view
which requires special handling.
*/
enum enum_open_strategy
{
/* Normal open. */
OPEN_NORMAL= 0,
/* Associate a table share only if the the table exists. */
OPEN_IF_EXISTS,
/* Don't associate a table share. */
OPEN_STUB
} open_strategy;
enum_open_strategy open_strategy;
/** TRUE if an alias for this table was specified in the SQL. */
bool is_alias;
/** TRUE if the table is referred to in the statement using a fully