mirror of
https://github.com/MariaDB/server.git
synced 2026-05-06 15:15:34 +02:00
merged mysql-5.1 into mysql-5.1-bugteam
This commit is contained in:
commit
292a72a043
48 changed files with 813 additions and 133 deletions
|
|
@ -864,16 +864,27 @@ err_exit:
|
|||
|
||||
err = dict_load_indexes(table, heap);
|
||||
|
||||
/* Initialize table foreign_child value. Its value could be
|
||||
changed when dict_load_foreigns() is called below */
|
||||
table->fk_max_recusive_level = 0;
|
||||
|
||||
/* If the force recovery flag is set, we open the table irrespective
|
||||
of the error condition, since the user may want to dump data from the
|
||||
clustered index. However we load the foreign key information only if
|
||||
all indexes were loaded. */
|
||||
if (err == DB_SUCCESS) {
|
||||
err = dict_load_foreigns(table->name, TRUE);
|
||||
err = dict_load_foreigns(table->name, TRUE, TRUE);
|
||||
|
||||
if (err != DB_SUCCESS) {
|
||||
dict_table_remove_from_cache(table);
|
||||
table = NULL;
|
||||
}
|
||||
} else if (!srv_force_recovery) {
|
||||
dict_table_remove_from_cache(table);
|
||||
table = NULL;
|
||||
}
|
||||
|
||||
table->fk_max_recusive_level = 0;
|
||||
#if 0
|
||||
if (err != DB_SUCCESS && table != NULL) {
|
||||
|
||||
|
|
@ -1095,8 +1106,12 @@ dict_load_foreign(
|
|||
/* out: DB_SUCCESS or error code */
|
||||
const char* id, /* in: foreign constraint id as a
|
||||
null-terminated string */
|
||||
ibool check_charsets)
|
||||
ibool check_charsets,
|
||||
/* in: TRUE=check charset compatibility */
|
||||
ibool check_recursive)
|
||||
/* in: Whether to record the foreign table
|
||||
parent count to avoid unlimited recursive
|
||||
load of chained foreign tables */
|
||||
{
|
||||
dict_foreign_t* foreign;
|
||||
dict_table_t* sys_foreign;
|
||||
|
|
@ -1110,6 +1125,8 @@ dict_load_foreign(
|
|||
ulint len;
|
||||
ulint n_fields_and_type;
|
||||
mtr_t mtr;
|
||||
dict_table_t* for_table;
|
||||
dict_table_t* ref_table;
|
||||
|
||||
ut_ad(mutex_own(&(dict_sys->mutex)));
|
||||
|
||||
|
|
@ -1194,11 +1211,54 @@ dict_load_foreign(
|
|||
|
||||
dict_load_foreign_cols(id, foreign);
|
||||
|
||||
/* If the foreign table is not yet in the dictionary cache, we
|
||||
have to load it so that we are able to make type comparisons
|
||||
in the next function call. */
|
||||
ref_table = dict_table_check_if_in_cache_low(
|
||||
foreign->referenced_table_name);
|
||||
|
||||
dict_table_get_low(foreign->foreign_table_name);
|
||||
/* We could possibly wind up in a deep recursive calls if
|
||||
we call dict_table_get_low() again here if there
|
||||
is a chain of tables concatenated together with
|
||||
foreign constraints. In such case, each table is
|
||||
both a parent and child of the other tables, and
|
||||
act as a "link" in such table chains.
|
||||
To avoid such scenario, we would need to check the
|
||||
number of ancesters the current table has. If that
|
||||
exceeds DICT_FK_MAX_CHAIN_LEN, we will stop loading
|
||||
the child table.
|
||||
Foreign constraints are loaded in a Breath First fashion,
|
||||
that is, the index on FOR_NAME is scanned first, and then
|
||||
index on REF_NAME. So foreign constrains in which
|
||||
current table is a child (foreign table) are loaded first,
|
||||
and then those constraints where current table is a
|
||||
parent (referenced) table.
|
||||
Thus we could check the parent (ref_table) table's
|
||||
reference count (fk_max_recusive_level) to know how deep the
|
||||
recursive call is. If the parent table (ref_table) is already
|
||||
loaded, and its fk_max_recusive_level is larger than
|
||||
DICT_FK_MAX_CHAIN_LEN, we will stop the recursive loading
|
||||
by skipping loading the child table. It will not affect foreign
|
||||
constraint check for DMLs since child table will be loaded
|
||||
at that time for the constraint check. */
|
||||
if (!ref_table
|
||||
|| ref_table->fk_max_recusive_level < DICT_FK_MAX_RECURSIVE_LOAD) {
|
||||
|
||||
/* If the foreign table is not yet in the dictionary cache, we
|
||||
have to load it so that we are able to make type comparisons
|
||||
in the next function call. */
|
||||
|
||||
for_table = dict_table_get_low(foreign->foreign_table_name);
|
||||
|
||||
if (for_table && ref_table && check_recursive) {
|
||||
/* This is to record the longest chain of ancesters
|
||||
this table has, if the parent has more ancesters
|
||||
than this table has, record it after add 1 (for this
|
||||
parent */
|
||||
if (ref_table->fk_max_recusive_level
|
||||
>= for_table->fk_max_recusive_level) {
|
||||
for_table->fk_max_recusive_level =
|
||||
ref_table->fk_max_recusive_level + 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/* Note that there may already be a foreign constraint object in
|
||||
the dictionary cache for this constraint: then the following
|
||||
|
|
@ -1223,6 +1283,8 @@ dict_load_foreigns(
|
|||
/*===============*/
|
||||
/* out: DB_SUCCESS or error code */
|
||||
const char* table_name, /* in: table name */
|
||||
ibool check_recursive,/* in: Whether to check recursive
|
||||
load of tables chained by FK */
|
||||
ibool check_charsets) /* in: TRUE=check charset
|
||||
compatibility */
|
||||
{
|
||||
|
|
@ -1324,7 +1386,7 @@ loop:
|
|||
|
||||
/* Load the foreign constraint definition to the dictionary cache */
|
||||
|
||||
err = dict_load_foreign(id, check_charsets);
|
||||
err = dict_load_foreign(id, check_charsets, check_recursive);
|
||||
|
||||
if (err != DB_SUCCESS) {
|
||||
btr_pcur_close(&pcur);
|
||||
|
|
@ -1352,6 +1414,11 @@ load_next_index:
|
|||
|
||||
mtr_start(&mtr);
|
||||
|
||||
/* Switch to scan index on REF_NAME, fk_max_recusive_level
|
||||
already been updated when scanning FOR_NAME index, no need to
|
||||
update again */
|
||||
check_recursive = FALSE;
|
||||
|
||||
goto start_load;
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -765,6 +765,16 @@ convert_error_code_to_mysql(
|
|||
|
||||
my_error(ER_QUERY_INTERRUPTED, MYF(0));
|
||||
return(-1);
|
||||
} else if (error == DB_FOREIGN_EXCEED_MAX_CASCADE) {
|
||||
push_warning_printf(thd, MYSQL_ERROR::WARN_LEVEL_WARN,
|
||||
HA_ERR_ROW_IS_REFERENCED,
|
||||
"InnoDB: Cannot delete/update "
|
||||
"rows with cascading foreign key "
|
||||
"constraints that exceed max "
|
||||
"depth of %d. Please "
|
||||
"drop extra constraints and try "
|
||||
"again", DICT_FK_MAX_RECURSIVE_LOAD);
|
||||
return(-1);
|
||||
} else {
|
||||
return(-1); // Unknown error
|
||||
}
|
||||
|
|
@ -2712,12 +2722,19 @@ ha_innobase::innobase_initialize_autoinc()
|
|||
err = row_search_max_autoinc(index, col_name, &read_auto_inc);
|
||||
|
||||
switch (err) {
|
||||
case DB_SUCCESS:
|
||||
/* At the this stage we do not know the increment
|
||||
or the offset, so use a default increment of 1. */
|
||||
auto_inc = read_auto_inc + 1;
|
||||
break;
|
||||
case DB_SUCCESS: {
|
||||
ulonglong col_max_value;
|
||||
|
||||
col_max_value = innobase_get_int_col_max_value(field);
|
||||
|
||||
/* At the this stage we do not know the increment
|
||||
nor the offset, so use a default increment of 1. */
|
||||
|
||||
auto_inc = innobase_next_autoinc(
|
||||
read_auto_inc, 1, 1, col_max_value);
|
||||
|
||||
break;
|
||||
}
|
||||
case DB_RECORD_NOT_FOUND:
|
||||
ut_print_timestamp(stderr);
|
||||
fprintf(stderr, " InnoDB: MySQL and InnoDB data "
|
||||
|
|
@ -2943,8 +2960,6 @@ retry:
|
|||
/* Init table lock structure */
|
||||
thr_lock_data_init(&share->lock,&lock,(void*) 0);
|
||||
|
||||
info(HA_STATUS_NO_LOCK | HA_STATUS_VARIABLE | HA_STATUS_CONST);
|
||||
|
||||
/* Only if the table has an AUTOINC column. */
|
||||
if (prebuilt->table != NULL && table->found_next_number_field != NULL) {
|
||||
dict_table_autoinc_lock(prebuilt->table);
|
||||
|
|
@ -2961,6 +2976,8 @@ retry:
|
|||
dict_table_autoinc_unlock(prebuilt->table);
|
||||
}
|
||||
|
||||
info(HA_STATUS_NO_LOCK | HA_STATUS_VARIABLE | HA_STATUS_CONST);
|
||||
|
||||
DBUG_RETURN(0);
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -73,10 +73,13 @@ Created 5/24/1996 Heikki Tuuri
|
|||
a later version of the engine. */
|
||||
#define DB_INTERRUPTED 49 /* the query has been interrupted with
|
||||
"KILL QUERY N;" */
|
||||
#define DB_FOREIGN_NO_INDEX 50 /* the child (foreign) table does not
|
||||
#define DB_FOREIGN_EXCEED_MAX_CASCADE 50/* Foreign key constraint related
|
||||
cascading delete/update exceeds
|
||||
maximum allowed depth */
|
||||
#define DB_FOREIGN_NO_INDEX 51 /* the child (foreign) table does not
|
||||
have an index that contains the
|
||||
foreign keys as its prefix columns */
|
||||
#define DB_REFERENCING_NO_INDEX 51 /* the parent (referencing) table does
|
||||
#define DB_REFERENCING_NO_INDEX 52 /* the parent (referencing) table does
|
||||
not have an index that contains the
|
||||
foreign keys as its prefix columns */
|
||||
|
||||
|
|
|
|||
|
|
@ -588,6 +588,22 @@ dict_table_is_comp_noninline(
|
|||
/* out: TRUE if table uses the
|
||||
compact page format */
|
||||
const dict_table_t* table); /* in: table */
|
||||
/*********************************************************************//**
|
||||
Obtain exclusive locks on all index trees of the table. This is to prevent
|
||||
accessing index trees while InnoDB is updating internal metadata for
|
||||
operations such as truncate tables. */
|
||||
UNIV_INLINE
|
||||
void
|
||||
dict_table_x_lock_indexes(
|
||||
/*======================*/
|
||||
dict_table_t* table); /* in: table */
|
||||
/*********************************************************************//**
|
||||
Release the exclusive locks on all index tree. */
|
||||
UNIV_INLINE
|
||||
void
|
||||
dict_table_x_unlock_indexes(
|
||||
/*========================*/
|
||||
dict_table_t* table); /* in: table */
|
||||
/************************************************************************
|
||||
Checks if a column is in the ordering columns of the clustered index of a
|
||||
table. Column prefixes are treated like whole columns. */
|
||||
|
|
|
|||
|
|
@ -298,6 +298,48 @@ dict_table_is_comp(
|
|||
return(UNIV_LIKELY(table->flags & DICT_TF_COMPACT));
|
||||
}
|
||||
|
||||
/*********************************************************************//**
|
||||
Obtain exclusive locks on all index trees of the table. This is to prevent
|
||||
accessing index trees while InnoDB is updating internal metadata for
|
||||
operations such as truncate tables. */
|
||||
UNIV_INLINE
|
||||
void
|
||||
dict_table_x_lock_indexes(
|
||||
/*======================*/
|
||||
dict_table_t* table) /* in: table */
|
||||
{
|
||||
dict_index_t* index;
|
||||
|
||||
ut_a(table);
|
||||
ut_ad(mutex_own(&(dict_sys->mutex)));
|
||||
|
||||
/* Loop through each index of the table and lock them */
|
||||
for (index = dict_table_get_first_index(table);
|
||||
index != NULL;
|
||||
index = dict_table_get_next_index(index)) {
|
||||
rw_lock_x_lock(dict_index_get_lock(index));
|
||||
}
|
||||
}
|
||||
|
||||
/*********************************************************************//**
|
||||
Release the exclusive locks on all index tree. */
|
||||
UNIV_INLINE
|
||||
void
|
||||
dict_table_x_unlock_indexes(
|
||||
/*========================*/
|
||||
dict_table_t* table) /* in: table */
|
||||
{
|
||||
dict_index_t* index;
|
||||
|
||||
ut_a(table);
|
||||
ut_ad(mutex_own(&(dict_sys->mutex)));
|
||||
|
||||
for (index = dict_table_get_first_index(table);
|
||||
index != NULL;
|
||||
index = dict_table_get_next_index(index)) {
|
||||
rw_lock_x_unlock(dict_index_get_lock(index));
|
||||
}
|
||||
}
|
||||
/************************************************************************
|
||||
Gets the number of fields in the internal representation of an index,
|
||||
including fields added by the dictionary system. */
|
||||
|
|
|
|||
|
|
@ -82,6 +82,8 @@ dict_load_foreigns(
|
|||
/*===============*/
|
||||
/* out: DB_SUCCESS or error code */
|
||||
const char* table_name, /* in: table name */
|
||||
ibool check_recursive,/* in: Whether to check recursive
|
||||
load of tables chained by FK */
|
||||
ibool check_charsets);/* in: TRUE=check charsets
|
||||
compatibility */
|
||||
/************************************************************************
|
||||
|
|
|
|||
|
|
@ -283,6 +283,21 @@ a foreign key constraint is enforced, therefore RESTRICT just means no flag */
|
|||
#define DICT_FOREIGN_ON_DELETE_NO_ACTION 16
|
||||
#define DICT_FOREIGN_ON_UPDATE_NO_ACTION 32
|
||||
|
||||
/** Tables could be chained together with Foreign key constraint. When
|
||||
first load the parent table, we would load all of its descedents.
|
||||
This could result in rescursive calls and out of stack error eventually.
|
||||
DICT_FK_MAX_RECURSIVE_LOAD defines the maximum number of recursive loads,
|
||||
when exceeded, the child table will not be loaded. It will be loaded when
|
||||
the foreign constraint check needs to be run. */
|
||||
#define DICT_FK_MAX_RECURSIVE_LOAD 250
|
||||
|
||||
/** Similarly, when tables are chained together with foreign key constraints
|
||||
with on cascading delete/update clause, delete from parent table could
|
||||
result in recursive cascading calls. This defines the maximum number of
|
||||
such cascading deletes/updates allowed. When exceeded, the delete from
|
||||
parent table will fail, and user has to drop excessive foreign constraint
|
||||
before proceeds. */
|
||||
#define FK_MAX_CASCADE_DEL 300
|
||||
|
||||
/* Data structure for a database table */
|
||||
struct dict_table_struct{
|
||||
|
|
@ -339,6 +354,12 @@ struct dict_table_struct{
|
|||
NOT allowed until this count gets to zero;
|
||||
MySQL does NOT itself check the number of
|
||||
open handles at drop */
|
||||
unsigned fk_max_recusive_level:8;
|
||||
/*!< maximum recursive level we support when
|
||||
loading tables chained together with FK
|
||||
constraints. If exceeds this level, we will
|
||||
stop loading child table into memory along with
|
||||
its parent table */
|
||||
ulint n_foreign_key_checks_running;
|
||||
/* count of how many foreign key check
|
||||
operations are currently being performed
|
||||
|
|
|
|||
|
|
@ -367,6 +367,9 @@ struct que_thr_struct{
|
|||
thus far */
|
||||
ulint lock_state; /* lock state of thread (table or
|
||||
row) */
|
||||
ulint fk_cascade_depth; /*!< maximum cascading call depth
|
||||
supported for foreign key constraint
|
||||
related delete/updates */
|
||||
};
|
||||
|
||||
#define QUE_THR_MAGIC_N 8476583
|
||||
|
|
|
|||
|
|
@ -555,6 +555,12 @@ handle_new_error:
|
|||
"forcing-recovery.html"
|
||||
" for help.\n", stderr);
|
||||
|
||||
} else if (err == DB_FOREIGN_EXCEED_MAX_CASCADE) {
|
||||
fprintf(stderr, "InnoDB: Cannot delete/update rows with"
|
||||
" cascading foreign key constraints that exceed max"
|
||||
" depth of %lu\n"
|
||||
"Please drop excessive foreign constraints"
|
||||
" and try again\n", (ulong) DICT_FK_MAX_RECURSIVE_LOAD);
|
||||
} else {
|
||||
fprintf(stderr, "InnoDB: unknown error code %lu\n",
|
||||
(ulong) err);
|
||||
|
|
@ -1406,11 +1412,15 @@ row_update_for_mysql(
|
|||
run_again:
|
||||
thr->run_node = node;
|
||||
thr->prev_node = node;
|
||||
thr->fk_cascade_depth = 0;
|
||||
|
||||
row_upd_step(thr);
|
||||
|
||||
err = trx->error_state;
|
||||
|
||||
/* Reset fk_cascade_depth back to 0 */
|
||||
thr->fk_cascade_depth = 0;
|
||||
|
||||
if (err != DB_SUCCESS) {
|
||||
que_thr_stop_for_mysql(thr);
|
||||
|
||||
|
|
@ -1602,6 +1612,12 @@ row_update_cascade_for_mysql(
|
|||
trx_t* trx;
|
||||
|
||||
trx = thr_get_trx(thr);
|
||||
|
||||
thr->fk_cascade_depth++;
|
||||
|
||||
if (thr->fk_cascade_depth > FK_MAX_CASCADE_DEL) {
|
||||
return (DB_FOREIGN_EXCEED_MAX_CASCADE);
|
||||
}
|
||||
run_again:
|
||||
thr->run_node = node;
|
||||
thr->prev_node = node;
|
||||
|
|
@ -2134,7 +2150,7 @@ row_table_add_foreign_constraints(
|
|||
|
||||
if (err == DB_SUCCESS) {
|
||||
/* Check that also referencing constraints are ok */
|
||||
err = dict_load_foreigns(name, TRUE);
|
||||
err = dict_load_foreigns(name, FALSE, TRUE);
|
||||
}
|
||||
|
||||
if (err != DB_SUCCESS) {
|
||||
|
|
@ -2819,6 +2835,15 @@ row_truncate_table_for_mysql(
|
|||
|
||||
trx->table_id = table->id;
|
||||
|
||||
/* Lock all index trees for this table, as we will
|
||||
truncate the table/index and possibly change their metadata.
|
||||
All DML/DDL are blocked by table level lock, with
|
||||
a few exceptions such as queries into information schema
|
||||
about the table, MySQL could try to access index stats
|
||||
for this kind of query, we need to use index locks to
|
||||
sync up */
|
||||
dict_table_x_lock_indexes(table);
|
||||
|
||||
/* scan SYS_INDEXES for all indexes of the table */
|
||||
heap = mem_heap_create(800);
|
||||
|
||||
|
|
@ -2891,6 +2916,10 @@ next_rec:
|
|||
|
||||
mem_heap_free(heap);
|
||||
|
||||
/* Done with index truncation, release index tree locks,
|
||||
subsequent work relates to table level metadata change */
|
||||
dict_table_x_unlock_indexes(table);
|
||||
|
||||
new_id = dict_hdr_get_new_id(DICT_HDR_TABLE_ID);
|
||||
|
||||
info = pars_info_create();
|
||||
|
|
@ -3883,7 +3912,8 @@ end:
|
|||
an ALTER, not in a RENAME. */
|
||||
|
||||
err = dict_load_foreigns(
|
||||
new_name, old_is_tmp ? trx->check_foreigns : TRUE);
|
||||
new_name, FALSE,
|
||||
old_is_tmp ? trx->check_foreigns : TRUE);
|
||||
|
||||
if (err != DB_SUCCESS) {
|
||||
ut_print_timestamp(stderr);
|
||||
|
|
|
|||
|
|
@ -12,6 +12,19 @@
|
|||
* handler/ha_innodb.c, dict/dict0dict.c:
|
||||
Fix Bug #55832 selects crash too easily when innodb_force_recovery>3
|
||||
|
||||
2010-08-03 The InnoDB Team
|
||||
|
||||
* include/dict0dict.h, include/dict0dict.ic, row/row0mysql.c:
|
||||
Fix bug #54678, InnoDB, TRUNCATE, ALTER, I_S SELECT, crash or deadlock
|
||||
|
||||
2010-08-03 The InnoDB Team
|
||||
|
||||
* dict/dict0load.c, handler/ha_innodb.cc, include/db0err.h,
|
||||
include/dict0load.h, include/dict0mem.h, include/que0que.h,
|
||||
row/row0merge.c, row/row0mysql.c:
|
||||
Fix Bug#54582 stack overflow when opening many tables linked
|
||||
with foreign keys at once
|
||||
|
||||
2010-08-03 The InnoDB Team
|
||||
|
||||
* include/ut0mem.h, ut/ut0mem.c:
|
||||
|
|
@ -23,6 +36,7 @@
|
|||
* handler/ha_innodb.cc
|
||||
Fix Bug #55382 Assignment with SELECT expressions takes unexpected
|
||||
S locks in READ COMMITTED
|
||||
>>>>>>> MERGE-SOURCE
|
||||
|
||||
2010-07-27 The InnoDB Team
|
||||
|
||||
|
|
|
|||
|
|
@ -3483,9 +3483,10 @@ btr_cur_set_ownership_of_extern_field(
|
|||
Marks not updated extern fields as not-owned by this record. The ownership
|
||||
is transferred to the updated record which is inserted elsewhere in the
|
||||
index tree. In purge only the owner of externally stored field is allowed
|
||||
to free the field. */
|
||||
to free the field.
|
||||
@return TRUE if BLOB ownership was transferred */
|
||||
UNIV_INTERN
|
||||
void
|
||||
ibool
|
||||
btr_cur_mark_extern_inherited_fields(
|
||||
/*=================================*/
|
||||
page_zip_des_t* page_zip,/*!< in/out: compressed page whose uncompressed
|
||||
|
|
@ -3499,13 +3500,14 @@ btr_cur_mark_extern_inherited_fields(
|
|||
ulint n;
|
||||
ulint j;
|
||||
ulint i;
|
||||
ibool change_ownership = FALSE;
|
||||
|
||||
ut_ad(rec_offs_validate(rec, NULL, offsets));
|
||||
ut_ad(!rec_offs_comp(offsets) || !rec_get_node_ptr_flag(rec));
|
||||
|
||||
if (!rec_offs_any_extern(offsets)) {
|
||||
|
||||
return;
|
||||
return(FALSE);
|
||||
}
|
||||
|
||||
n = rec_offs_n_fields(offsets);
|
||||
|
|
@ -3528,10 +3530,14 @@ btr_cur_mark_extern_inherited_fields(
|
|||
|
||||
btr_cur_set_ownership_of_extern_field(
|
||||
page_zip, rec, index, offsets, i, FALSE, mtr);
|
||||
|
||||
change_ownership = TRUE;
|
||||
updated:
|
||||
;
|
||||
}
|
||||
}
|
||||
|
||||
return(change_ownership);
|
||||
}
|
||||
|
||||
/*******************************************************************//**
|
||||
|
|
|
|||
|
|
@ -1009,16 +1009,27 @@ err_exit:
|
|||
|
||||
err = dict_load_indexes(table, heap);
|
||||
|
||||
/* Initialize table foreign_child value. Its value could be
|
||||
changed when dict_load_foreigns() is called below */
|
||||
table->fk_max_recusive_level = 0;
|
||||
|
||||
/* If the force recovery flag is set, we open the table irrespective
|
||||
of the error condition, since the user may want to dump data from the
|
||||
clustered index. However we load the foreign key information only if
|
||||
all indexes were loaded. */
|
||||
if (err == DB_SUCCESS) {
|
||||
err = dict_load_foreigns(table->name, TRUE);
|
||||
err = dict_load_foreigns(table->name, TRUE, TRUE);
|
||||
|
||||
if (err != DB_SUCCESS) {
|
||||
dict_table_remove_from_cache(table);
|
||||
table = NULL;
|
||||
}
|
||||
} else if (!srv_force_recovery) {
|
||||
dict_table_remove_from_cache(table);
|
||||
table = NULL;
|
||||
}
|
||||
|
||||
table->fk_max_recusive_level = 0;
|
||||
#if 0
|
||||
if (err != DB_SUCCESS && table != NULL) {
|
||||
|
||||
|
|
@ -1240,8 +1251,12 @@ dict_load_foreign(
|
|||
/*==============*/
|
||||
const char* id, /*!< in: foreign constraint id as a
|
||||
null-terminated string */
|
||||
ibool check_charsets)
|
||||
ibool check_charsets,
|
||||
/*!< in: TRUE=check charset compatibility */
|
||||
ibool check_recursive)
|
||||
/*!< in: Whether to record the foreign table
|
||||
parent count to avoid unlimited recursive
|
||||
load of chained foreign tables */
|
||||
{
|
||||
dict_foreign_t* foreign;
|
||||
dict_table_t* sys_foreign;
|
||||
|
|
@ -1255,6 +1270,8 @@ dict_load_foreign(
|
|||
ulint len;
|
||||
ulint n_fields_and_type;
|
||||
mtr_t mtr;
|
||||
dict_table_t* for_table;
|
||||
dict_table_t* ref_table;
|
||||
|
||||
ut_ad(mutex_own(&(dict_sys->mutex)));
|
||||
|
||||
|
|
@ -1339,11 +1356,54 @@ dict_load_foreign(
|
|||
|
||||
dict_load_foreign_cols(id, foreign);
|
||||
|
||||
/* If the foreign table is not yet in the dictionary cache, we
|
||||
have to load it so that we are able to make type comparisons
|
||||
in the next function call. */
|
||||
ref_table = dict_table_check_if_in_cache_low(
|
||||
foreign->referenced_table_name);
|
||||
|
||||
dict_table_get_low(foreign->foreign_table_name);
|
||||
/* We could possibly wind up in a deep recursive calls if
|
||||
we call dict_table_get_low() again here if there
|
||||
is a chain of tables concatenated together with
|
||||
foreign constraints. In such case, each table is
|
||||
both a parent and child of the other tables, and
|
||||
act as a "link" in such table chains.
|
||||
To avoid such scenario, we would need to check the
|
||||
number of ancesters the current table has. If that
|
||||
exceeds DICT_FK_MAX_CHAIN_LEN, we will stop loading
|
||||
the child table.
|
||||
Foreign constraints are loaded in a Breath First fashion,
|
||||
that is, the index on FOR_NAME is scanned first, and then
|
||||
index on REF_NAME. So foreign constrains in which
|
||||
current table is a child (foreign table) are loaded first,
|
||||
and then those constraints where current table is a
|
||||
parent (referenced) table.
|
||||
Thus we could check the parent (ref_table) table's
|
||||
reference count (fk_max_recusive_level) to know how deep the
|
||||
recursive call is. If the parent table (ref_table) is already
|
||||
loaded, and its fk_max_recusive_level is larger than
|
||||
DICT_FK_MAX_CHAIN_LEN, we will stop the recursive loading
|
||||
by skipping loading the child table. It will not affect foreign
|
||||
constraint check for DMLs since child table will be loaded
|
||||
at that time for the constraint check. */
|
||||
if (!ref_table
|
||||
|| ref_table->fk_max_recusive_level < DICT_FK_MAX_RECURSIVE_LOAD) {
|
||||
|
||||
/* If the foreign table is not yet in the dictionary cache, we
|
||||
have to load it so that we are able to make type comparisons
|
||||
in the next function call. */
|
||||
|
||||
for_table = dict_table_get_low(foreign->foreign_table_name);
|
||||
|
||||
if (for_table && ref_table && check_recursive) {
|
||||
/* This is to record the longest chain of ancesters
|
||||
this table has, if the parent has more ancesters
|
||||
than this table has, record it after add 1 (for this
|
||||
parent */
|
||||
if (ref_table->fk_max_recusive_level
|
||||
>= for_table->fk_max_recusive_level) {
|
||||
for_table->fk_max_recusive_level =
|
||||
ref_table->fk_max_recusive_level + 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/* Note that there may already be a foreign constraint object in
|
||||
the dictionary cache for this constraint: then the following
|
||||
|
|
@ -1368,6 +1428,8 @@ ulint
|
|||
dict_load_foreigns(
|
||||
/*===============*/
|
||||
const char* table_name, /*!< in: table name */
|
||||
ibool check_recursive,/*!< in: Whether to check recursive
|
||||
load of tables chained by FK */
|
||||
ibool check_charsets) /*!< in: TRUE=check charset
|
||||
compatibility */
|
||||
{
|
||||
|
|
@ -1469,7 +1531,7 @@ loop:
|
|||
|
||||
/* Load the foreign constraint definition to the dictionary cache */
|
||||
|
||||
err = dict_load_foreign(id, check_charsets);
|
||||
err = dict_load_foreign(id, check_charsets, check_recursive);
|
||||
|
||||
if (err != DB_SUCCESS) {
|
||||
btr_pcur_close(&pcur);
|
||||
|
|
@ -1497,6 +1559,11 @@ load_next_index:
|
|||
|
||||
mtr_start(&mtr);
|
||||
|
||||
/* Switch to scan index on REF_NAME, fk_max_recusive_level
|
||||
already been updated when scanning FOR_NAME index, no need to
|
||||
update again */
|
||||
check_recursive = FALSE;
|
||||
|
||||
goto start_load;
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -767,6 +767,19 @@ convert_error_code_to_mysql(
|
|||
case DB_INTERRUPTED:
|
||||
my_error(ER_QUERY_INTERRUPTED, MYF(0));
|
||||
/* fall through */
|
||||
|
||||
case DB_FOREIGN_EXCEED_MAX_CASCADE:
|
||||
push_warning_printf(thd, MYSQL_ERROR::WARN_LEVEL_WARN,
|
||||
HA_ERR_ROW_IS_REFERENCED,
|
||||
"InnoDB: Cannot delete/update "
|
||||
"rows with cascading foreign key "
|
||||
"constraints that exceed max "
|
||||
"depth of %d. Please "
|
||||
"drop extra constraints and try "
|
||||
"again", DICT_FK_MAX_RECURSIVE_LOAD);
|
||||
|
||||
/* fall through */
|
||||
|
||||
case DB_ERROR:
|
||||
default:
|
||||
return(-1); /* unspecified error */
|
||||
|
|
@ -3348,12 +3361,19 @@ ha_innobase::innobase_initialize_autoinc()
|
|||
err = row_search_max_autoinc(index, col_name, &read_auto_inc);
|
||||
|
||||
switch (err) {
|
||||
case DB_SUCCESS:
|
||||
/* At the this stage we do not know the increment
|
||||
or the offset, so use a default increment of 1. */
|
||||
auto_inc = read_auto_inc + 1;
|
||||
break;
|
||||
case DB_SUCCESS: {
|
||||
ulonglong col_max_value;
|
||||
|
||||
col_max_value = innobase_get_int_col_max_value(field);
|
||||
|
||||
/* At the this stage we do not know the increment
|
||||
nor the offset, so use a default increment of 1. */
|
||||
|
||||
auto_inc = innobase_next_autoinc(
|
||||
read_auto_inc, 1, 1, col_max_value);
|
||||
|
||||
break;
|
||||
}
|
||||
case DB_RECORD_NOT_FOUND:
|
||||
ut_print_timestamp(stderr);
|
||||
fprintf(stderr, " InnoDB: MySQL and InnoDB data "
|
||||
|
|
@ -3648,8 +3668,6 @@ retry:
|
|||
dict_table_get_format(prebuilt->table));
|
||||
}
|
||||
|
||||
info(HA_STATUS_NO_LOCK | HA_STATUS_VARIABLE | HA_STATUS_CONST);
|
||||
|
||||
/* Only if the table has an AUTOINC column. */
|
||||
if (prebuilt->table != NULL && table->found_next_number_field != NULL) {
|
||||
dict_table_autoinc_lock(prebuilt->table);
|
||||
|
|
@ -3666,6 +3684,8 @@ retry:
|
|||
dict_table_autoinc_unlock(prebuilt->table);
|
||||
}
|
||||
|
||||
info(HA_STATUS_NO_LOCK | HA_STATUS_VARIABLE | HA_STATUS_CONST);
|
||||
|
||||
DBUG_RETURN(0);
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -468,9 +468,10 @@ btr_estimate_number_of_different_key_vals(
|
|||
Marks not updated extern fields as not-owned by this record. The ownership
|
||||
is transferred to the updated record which is inserted elsewhere in the
|
||||
index tree. In purge only the owner of externally stored field is allowed
|
||||
to free the field. */
|
||||
to free the field.
|
||||
@return TRUE if BLOB ownership was transferred */
|
||||
UNIV_INTERN
|
||||
void
|
||||
ibool
|
||||
btr_cur_mark_extern_inherited_fields(
|
||||
/*=================================*/
|
||||
page_zip_des_t* page_zip,/*!< in/out: compressed page whose uncompressed
|
||||
|
|
|
|||
|
|
@ -94,6 +94,9 @@ enum db_err {
|
|||
|
||||
DB_PRIMARY_KEY_IS_NULL, /* a column in the PRIMARY KEY
|
||||
was found to be NULL */
|
||||
DB_FOREIGN_EXCEED_MAX_CASCADE, /* Foreign key constraint related
|
||||
cascading delete/update exceeds
|
||||
maximum allowed depth */
|
||||
|
||||
/* The following are partial failure codes */
|
||||
DB_FAIL = 1000,
|
||||
|
|
|
|||
|
|
@ -680,6 +680,22 @@ ulint
|
|||
dict_table_zip_size(
|
||||
/*================*/
|
||||
const dict_table_t* table); /*!< in: table */
|
||||
/*********************************************************************//**
|
||||
Obtain exclusive locks on all index trees of the table. This is to prevent
|
||||
accessing index trees while InnoDB is updating internal metadata for
|
||||
operations such as truncate tables. */
|
||||
UNIV_INLINE
|
||||
void
|
||||
dict_table_x_lock_indexes(
|
||||
/*======================*/
|
||||
dict_table_t* table); /*!< in: table */
|
||||
/*********************************************************************//**
|
||||
Release the exclusive locks on all index tree. */
|
||||
UNIV_INLINE
|
||||
void
|
||||
dict_table_x_unlock_indexes(
|
||||
/*========================*/
|
||||
dict_table_t* table); /*!< in: table */
|
||||
/********************************************************************//**
|
||||
Checks if a column is in the ordering columns of the clustered index of a
|
||||
table. Column prefixes are treated like whole columns.
|
||||
|
|
|
|||
|
|
@ -452,6 +452,48 @@ dict_table_zip_size(
|
|||
return(dict_table_flags_to_zip_size(table->flags));
|
||||
}
|
||||
|
||||
/*********************************************************************//**
|
||||
Obtain exclusive locks on all index trees of the table. This is to prevent
|
||||
accessing index trees while InnoDB is updating internal metadata for
|
||||
operations such as truncate tables. */
|
||||
UNIV_INLINE
|
||||
void
|
||||
dict_table_x_lock_indexes(
|
||||
/*======================*/
|
||||
dict_table_t* table) /*!< in: table */
|
||||
{
|
||||
dict_index_t* index;
|
||||
|
||||
ut_a(table);
|
||||
ut_ad(mutex_own(&(dict_sys->mutex)));
|
||||
|
||||
/* Loop through each index of the table and lock them */
|
||||
for (index = dict_table_get_first_index(table);
|
||||
index != NULL;
|
||||
index = dict_table_get_next_index(index)) {
|
||||
rw_lock_x_lock(dict_index_get_lock(index));
|
||||
}
|
||||
}
|
||||
|
||||
/*********************************************************************//**
|
||||
Release the exclusive locks on all index tree. */
|
||||
UNIV_INLINE
|
||||
void
|
||||
dict_table_x_unlock_indexes(
|
||||
/*========================*/
|
||||
dict_table_t* table) /*!< in: table */
|
||||
{
|
||||
dict_index_t* index;
|
||||
|
||||
ut_a(table);
|
||||
ut_ad(mutex_own(&(dict_sys->mutex)));
|
||||
|
||||
for (index = dict_table_get_first_index(table);
|
||||
index != NULL;
|
||||
index = dict_table_get_next_index(index)) {
|
||||
rw_lock_x_unlock(dict_index_get_lock(index));
|
||||
}
|
||||
}
|
||||
/********************************************************************//**
|
||||
Gets the number of fields in the internal representation of an index,
|
||||
including fields added by the dictionary system.
|
||||
|
|
|
|||
|
|
@ -97,6 +97,8 @@ ulint
|
|||
dict_load_foreigns(
|
||||
/*===============*/
|
||||
const char* table_name, /*!< in: table name */
|
||||
ibool check_recursive,/*!< in: Whether to check recursive
|
||||
load of tables chained by FK */
|
||||
ibool check_charsets);/*!< in: TRUE=check charsets
|
||||
compatibility */
|
||||
/********************************************************************//**
|
||||
|
|
|
|||
|
|
@ -112,6 +112,21 @@ ROW_FORMAT=REDUNDANT. */
|
|||
in table->flags. */
|
||||
/* @} */
|
||||
|
||||
/** Tables could be chained together with Foreign key constraint. When
|
||||
first load the parent table, we would load all of its descedents.
|
||||
This could result in rescursive calls and out of stack error eventually.
|
||||
DICT_FK_MAX_RECURSIVE_LOAD defines the maximum number of recursive loads,
|
||||
when exceeded, the child table will not be loaded. It will be loaded when
|
||||
the foreign constraint check needs to be run. */
|
||||
#define DICT_FK_MAX_RECURSIVE_LOAD 250
|
||||
|
||||
/** Similarly, when tables are chained together with foreign key constraints
|
||||
with on cascading delete/update clause, delete from parent table could
|
||||
result in recursive cascading calls. This defines the maximum number of
|
||||
such cascading deletes/updates allowed. When exceeded, the delete from
|
||||
parent table will fail, and user has to drop excessive foreign constraint
|
||||
before proceeds. */
|
||||
#define FK_MAX_CASCADE_DEL 300
|
||||
|
||||
/**********************************************************************//**
|
||||
Creates a table memory object.
|
||||
|
|
@ -434,6 +449,12 @@ struct dict_table_struct{
|
|||
NOT allowed until this count gets to zero;
|
||||
MySQL does NOT itself check the number of
|
||||
open handles at drop */
|
||||
unsigned fk_max_recusive_level:8;
|
||||
/*!< maximum recursive level we support when
|
||||
loading tables chained together with FK
|
||||
constraints. If exceeds this level, we will
|
||||
stop loading child table into memory along with
|
||||
its parent table */
|
||||
ulint n_foreign_key_checks_running;
|
||||
/*!< count of how many foreign key check
|
||||
operations are currently being performed
|
||||
|
|
|
|||
|
|
@ -381,6 +381,9 @@ struct que_thr_struct{
|
|||
thus far */
|
||||
ulint lock_state; /*!< lock state of thread (table or
|
||||
row) */
|
||||
ulint fk_cascade_depth; /*!< maximum cascading call depth
|
||||
supported for foreign key constraint
|
||||
related delete/updates */
|
||||
};
|
||||
|
||||
#define QUE_THR_MAGIC_N 8476583
|
||||
|
|
|
|||
|
|
@ -2395,7 +2395,7 @@ row_merge_rename_tables(
|
|||
goto err_exit;
|
||||
}
|
||||
|
||||
err = dict_load_foreigns(old_name, TRUE);
|
||||
err = dict_load_foreigns(old_name, FALSE, TRUE);
|
||||
|
||||
if (err != DB_SUCCESS) {
|
||||
err_exit:
|
||||
|
|
|
|||
|
|
@ -576,6 +576,13 @@ handle_new_error:
|
|||
"InnoDB: " REFMAN "forcing-recovery.html"
|
||||
" for help.\n", stderr);
|
||||
break;
|
||||
case DB_FOREIGN_EXCEED_MAX_CASCADE:
|
||||
fprintf(stderr, "InnoDB: Cannot delete/update rows with"
|
||||
" cascading foreign key constraints that exceed max"
|
||||
" depth of %lu\n"
|
||||
"Please drop excessive foreign constraints"
|
||||
" and try again\n", (ulong) DICT_FK_MAX_RECURSIVE_LOAD);
|
||||
break;
|
||||
default:
|
||||
fprintf(stderr, "InnoDB: unknown error code %lu\n",
|
||||
(ulong) err);
|
||||
|
|
@ -1381,11 +1388,15 @@ row_update_for_mysql(
|
|||
run_again:
|
||||
thr->run_node = node;
|
||||
thr->prev_node = node;
|
||||
thr->fk_cascade_depth = 0;
|
||||
|
||||
row_upd_step(thr);
|
||||
|
||||
err = trx->error_state;
|
||||
|
||||
/* Reset fk_cascade_depth back to 0 */
|
||||
thr->fk_cascade_depth = 0;
|
||||
|
||||
if (err != DB_SUCCESS) {
|
||||
que_thr_stop_for_mysql(thr);
|
||||
|
||||
|
|
@ -1581,6 +1592,12 @@ row_update_cascade_for_mysql(
|
|||
trx_t* trx;
|
||||
|
||||
trx = thr_get_trx(thr);
|
||||
|
||||
thr->fk_cascade_depth++;
|
||||
|
||||
if (thr->fk_cascade_depth > FK_MAX_CASCADE_DEL) {
|
||||
return (DB_FOREIGN_EXCEED_MAX_CASCADE);
|
||||
}
|
||||
run_again:
|
||||
thr->run_node = node;
|
||||
thr->prev_node = node;
|
||||
|
|
@ -2061,7 +2078,7 @@ row_table_add_foreign_constraints(
|
|||
name, reject_fks);
|
||||
if (err == DB_SUCCESS) {
|
||||
/* Check that also referencing constraints are ok */
|
||||
err = dict_load_foreigns(name, TRUE);
|
||||
err = dict_load_foreigns(name, FALSE, TRUE);
|
||||
}
|
||||
|
||||
if (err != DB_SUCCESS) {
|
||||
|
|
@ -2754,6 +2771,15 @@ row_truncate_table_for_mysql(
|
|||
|
||||
trx->table_id = table->id;
|
||||
|
||||
/* Lock all index trees for this table, as we will
|
||||
truncate the table/index and possibly change their metadata.
|
||||
All DML/DDL are blocked by table level lock, with
|
||||
a few exceptions such as queries into information schema
|
||||
about the table, MySQL could try to access index stats
|
||||
for this kind of query, we need to use index locks to
|
||||
sync up */
|
||||
dict_table_x_lock_indexes(table);
|
||||
|
||||
if (table->space && !table->dir_path_of_temp_table) {
|
||||
/* Discard and create the single-table tablespace. */
|
||||
ulint space = table->space;
|
||||
|
|
@ -2770,6 +2796,7 @@ row_truncate_table_for_mysql(
|
|||
|| fil_create_new_single_table_tablespace(
|
||||
space, table->name, FALSE, flags,
|
||||
FIL_IBD_FILE_INITIAL_SIZE) != DB_SUCCESS) {
|
||||
dict_table_x_unlock_indexes(table);
|
||||
ut_print_timestamp(stderr);
|
||||
fprintf(stderr,
|
||||
" InnoDB: TRUNCATE TABLE %s failed to"
|
||||
|
|
@ -2873,6 +2900,10 @@ next_rec:
|
|||
|
||||
mem_heap_free(heap);
|
||||
|
||||
/* Done with index truncation, release index tree locks,
|
||||
subsequent work relates to table level metadata change */
|
||||
dict_table_x_unlock_indexes(table);
|
||||
|
||||
dict_hdr_get_new_id(&new_id, NULL, NULL);
|
||||
|
||||
info = pars_info_create();
|
||||
|
|
@ -3920,7 +3951,7 @@ end:
|
|||
an ALTER, not in a RENAME. */
|
||||
|
||||
err = dict_load_foreigns(
|
||||
new_name, !old_is_tmp || trx->check_foreigns);
|
||||
new_name, FALSE, !old_is_tmp || trx->check_foreigns);
|
||||
|
||||
if (err != DB_SUCCESS) {
|
||||
ut_print_timestamp(stderr);
|
||||
|
|
|
|||
|
|
@ -1598,6 +1598,7 @@ row_upd_clust_rec_by_insert(
|
|||
dict_table_t* table;
|
||||
dtuple_t* entry;
|
||||
ulint err;
|
||||
ibool change_ownership = FALSE;
|
||||
|
||||
ut_ad(node);
|
||||
ut_ad(dict_index_is_clust(index));
|
||||
|
|
@ -1630,9 +1631,9 @@ row_upd_clust_rec_by_insert(
|
|||
index = dict_table_get_first_index(table);
|
||||
offsets = rec_get_offsets(rec, index, offsets_,
|
||||
ULINT_UNDEFINED, &heap);
|
||||
btr_cur_mark_extern_inherited_fields(
|
||||
btr_cur_get_page_zip(btr_cur),
|
||||
rec, index, offsets, node->update, mtr);
|
||||
change_ownership = btr_cur_mark_extern_inherited_fields(
|
||||
btr_cur_get_page_zip(btr_cur), rec, index, offsets,
|
||||
node->update, mtr);
|
||||
if (check_ref) {
|
||||
/* NOTE that the following call loses
|
||||
the position of pcur ! */
|
||||
|
|
@ -1661,10 +1662,11 @@ row_upd_clust_rec_by_insert(
|
|||
|
||||
row_upd_index_entry_sys_field(entry, index, DATA_TRX_ID, trx->id);
|
||||
|
||||
if (node->upd_ext) {
|
||||
if (change_ownership) {
|
||||
/* If we return from a lock wait, for example, we may have
|
||||
extern fields marked as not-owned in entry (marked in the
|
||||
if-branch above). We must unmark them. */
|
||||
if-branch above). We must unmark them, take the ownership
|
||||
back. */
|
||||
|
||||
btr_cur_unmark_dtuple_extern_fields(entry);
|
||||
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue