summaryrefslogtreecommitdiff
path: root/innobase/row
diff options
context:
space:
mode:
Diffstat (limited to 'innobase/row')
-rw-r--r--innobase/row/row0ins.c30
-rw-r--r--innobase/row/row0mysql.c268
-rw-r--r--innobase/row/row0sel.c18
-rw-r--r--innobase/row/row0upd.c8
4 files changed, 256 insertions, 68 deletions
diff --git a/innobase/row/row0ins.c b/innobase/row/row0ins.c
index 6d1482b6720..f8a98f74c09 100644
--- a/innobase/row/row0ins.c
+++ b/innobase/row/row0ins.c
@@ -1173,7 +1173,7 @@ run_again:
check_index = foreign->foreign_index;
}
- if (check_table == NULL) {
+ if (check_table == NULL || check_table->ibd_file_missing) {
if (check_ref) {
FILE* ef = dict_foreign_err_file;
mutex_enter(&dict_foreign_err_mutex);
@@ -1192,7 +1192,7 @@ run_again:
dtuple_print(ef, entry);
fputs("\nBut the parent table ", ef);
ut_print_name(ef, trx, foreign->referenced_table_name);
- fputs(" does not currently exist!\n", ef);
+ fputs("\nor its .ibd file does not currently exist!\n", ef);
mutex_exit(&dict_foreign_err_mutex);
return(DB_NO_REFERENCED_ROW);
@@ -1415,8 +1415,34 @@ row_ins_check_foreign_constraints(
row_mysql_freeze_data_dictionary(trx);
}
+ if (foreign->referenced_table) {
+ mutex_enter(&(dict_sys->mutex));
+
+ (foreign->referenced_table
+ ->n_foreign_key_checks_running)++;
+
+ mutex_exit(&(dict_sys->mutex));
+ }
+
+ /* NOTE that if the thread ends up waiting for a lock
+ we will release dict_operation_lock temporarily!
+ But the counter on the table protects the referenced
+ table from being dropped while the check is running. */
+
err = row_ins_check_foreign_constraint(TRUE, foreign,
table, entry, thr);
+
+ if (foreign->referenced_table) {
+ mutex_enter(&(dict_sys->mutex));
+
+ ut_a(foreign->referenced_table
+ ->n_foreign_key_checks_running > 0);
+ (foreign->referenced_table
+ ->n_foreign_key_checks_running)--;
+
+ mutex_exit(&(dict_sys->mutex));
+ }
+
if (got_s_lock) {
row_mysql_unfreeze_data_dictionary(trx);
}
diff --git a/innobase/row/row0mysql.c b/innobase/row/row0mysql.c
index dec2b19559c..b7cd730828a 100644
--- a/innobase/row/row0mysql.c
+++ b/innobase/row/row0mysql.c
@@ -869,7 +869,21 @@ row_insert_for_mysql(
ut_ad(trx);
ut_ad(trx->mysql_thread_id == os_thread_get_curr_id());
-
+
+ if (prebuilt->table->ibd_file_missing) {
+ ut_print_timestamp(stderr);
+ fprintf(stderr, " InnoDB: Error:\n"
+"InnoDB: MySQL is trying to use a table handle but the .ibd file for\n"
+"InnoDB: table %s does not exist.\n"
+"InnoDB: Have you deleted the .ibd file from the database directory under\n"
+"InnoDB: the MySQL datadir, or have you used DISCARD TABLESPACE?\n"
+"InnoDB: Look from\n"
+"http://dev.mysql.com/doc/mysql/en/InnoDB_troubleshooting_datadict.html\n"
+"InnoDB: how you can resolve the problem.\n",
+ prebuilt->table->name);
+ return(DB_ERROR);
+ }
+
if (prebuilt->magic_n != ROW_PREBUILT_ALLOCATED) {
fprintf(stderr,
"InnoDB: Error: trying to free a corrupt\n"
@@ -1087,6 +1101,20 @@ row_update_for_mysql(
ut_ad(trx->mysql_thread_id == os_thread_get_curr_id());
UT_NOT_USED(mysql_rec);
+ if (prebuilt->table->ibd_file_missing) {
+ ut_print_timestamp(stderr);
+ fprintf(stderr, " InnoDB: Error:\n"
+"InnoDB: MySQL is trying to use a table handle but the .ibd file for\n"
+"InnoDB: table %s does not exist.\n"
+"InnoDB: Have you deleted the .ibd file from the database directory under\n"
+"InnoDB: the MySQL datadir, or have you used DISCARD TABLESPACE?\n"
+"InnoDB: Look from\n"
+"http://dev.mysql.com/doc/mysql/en/InnoDB_troubleshooting_datadict.html\n"
+"InnoDB: how you can resolve the problem.\n",
+ prebuilt->table->name);
+ return(DB_ERROR);
+ }
+
if (prebuilt->magic_n != ROW_PREBUILT_ALLOCATED) {
fprintf(stderr,
"InnoDB: Error: trying to free a corrupt\n"
@@ -1805,20 +1833,19 @@ row_drop_table_for_mysql_in_background(
trx = trx_allocate_for_background();
+ /* If the original transaction was dropping a table referenced by
+ foreign keys, we must set the following to be able to drop the
+ table: */
+
+ trx->check_foreigns = FALSE;
+
/* fputs("InnoDB: Error: Dropping table ", stderr);
ut_print_name(stderr, name);
fputs(" in background drop list\n", stderr); */
- /* Drop the table in InnoDB */
+ /* Try to drop the table in InnoDB */
error = row_drop_table_for_mysql(name, trx, FALSE);
-
- if (error != DB_SUCCESS) {
- ut_print_timestamp(stderr);
- fputs(" InnoDB: Error: Dropping table ", stderr);
- ut_print_name(stderr, trx, name);
- fputs(" in background drop list failed\n", stderr);
- }
/* Flush the log to reduce probability that the .frm files and
the InnoDB data dictionary get out-of-sync if the user runs
@@ -1830,7 +1857,7 @@ row_drop_table_for_mysql_in_background(
trx_free_for_background(trx);
- return(DB_SUCCESS);
+ return(error);
}
/*************************************************************************
@@ -1864,6 +1891,7 @@ loop:
mutex_exit(&kernel_mutex);
if (drop == NULL) {
+ /* All tables dropped */
return(n_tables + n_tables_dropped);
}
@@ -1878,16 +1906,16 @@ loop:
goto already_dropped;
}
-
- if (table->n_mysql_handles_opened > 0
- || table->n_foreign_key_checks_running > 0) {
+
+ if (DB_SUCCESS != row_drop_table_for_mysql_in_background(
+ drop->table_name)) {
+ /* If the DROP fails for some table, we return, and let the
+ main thread retry later */
return(n_tables + n_tables_dropped);
}
n_tables_dropped++;
-
- row_drop_table_for_mysql_in_background(drop->table_name);
already_dropped:
mutex_enter(&kernel_mutex);
@@ -1931,21 +1959,21 @@ row_get_background_drop_list_len_low(void)
}
/*************************************************************************
-Adds a table to the list of tables which the master thread drops in
-background. We need this on Unix because in ALTER TABLE MySQL may call
-drop table even if the table has running queries on it. */
+If a table is not yet in the drop list, adds the table to the list of tables
+which the master thread drops in background. We need this on Unix because in
+ALTER TABLE MySQL may call drop table even if the table has running queries on
+it. Also, if there are running foreign key checks on the table, we drop the
+table lazily. */
static
-void
+ibool
row_add_table_to_background_drop_list(
/*==================================*/
+ /* out: TRUE if the table was not yet in the
+ drop list, and was added there */
dict_table_t* table) /* in: table */
{
row_mysql_drop_t* drop;
- drop = mem_alloc(sizeof(row_mysql_drop_t));
-
- drop->table_name = mem_strdup(table->name);
-
mutex_enter(&kernel_mutex);
if (!row_mysql_drop_list_inited) {
@@ -1953,7 +1981,26 @@ row_add_table_to_background_drop_list(
UT_LIST_INIT(row_mysql_drop_list);
row_mysql_drop_list_inited = TRUE;
}
+
+ /* Look if the table already is in the drop list */
+ drop = UT_LIST_GET_FIRST(row_mysql_drop_list);
+
+ while (drop != NULL) {
+ if (strcmp(drop->table_name, table->name) == 0) {
+ /* Already in the list */
+
+ mutex_exit(&kernel_mutex);
+
+ return(FALSE);
+ }
+
+ drop = UT_LIST_GET_NEXT(row_mysql_drop_list, drop);
+ }
+ drop = mem_alloc(sizeof(row_mysql_drop_t));
+
+ drop->table_name = mem_strdup(table->name);
+
UT_LIST_ADD_LAST(row_mysql_drop_list, row_mysql_drop_list, drop);
/* fputs("InnoDB: Adding table ", stderr);
@@ -1961,14 +2008,32 @@ row_add_table_to_background_drop_list(
fputs(" to background drop list\n", stderr); */
mutex_exit(&kernel_mutex);
+
+ return(TRUE);
}
/*************************************************************************
Discards the tablespace of a table which stored in an .ibd file. Discarding
means that this function deletes the .ibd file and assigns a new table id for
-the table. Also the flag table->ibd_file_missing is set TRUE.
+the table. Also the flag table->ibd_file_missing is set TRUE. */
+
+int
+row_discard_tablespace_for_mysql(
+/*=============================*/
+ /* out: error code or DB_SUCCESS */
+ const char* name, /* in: table name */
+ trx_t* trx) /* in: transaction handle */
+{
+ dict_foreign_t* foreign;
+ dulint new_id;
+ dict_table_t* table;
+ que_thr_t* thr;
+ que_t* graph = NULL;
+ ibool success;
+ ulint err;
+ char* buf;
-How do we prevent crashes caused by ongoing operations on the table? Old
+/* How do we prevent crashes caused by ongoing operations on the table? Old
operations could try to access non-existent pages.
1) SQL queries, INSERT, SELECT, ...: we must get an exclusive MySQL table lock
@@ -1984,22 +2049,9 @@ tablespace mem object with IMPORT TABLESPACE later, then the tablespace will
have the same id, but the tablespace_version field in the mem object is
different, and ongoing old insert buffer page merges get discarded.
4) Linear readahead and random readahead: we use the same method as in 3) to
-discard ongoing operations. */
-
-int
-row_discard_tablespace_for_mysql(
-/*=============================*/
- /* out: error code or DB_SUCCESS */
- const char* name, /* in: table name */
- trx_t* trx) /* in: transaction handle */
-{
- dulint new_id;
- dict_table_t* table;
- que_thr_t* thr;
- que_t* graph = NULL;
- ibool success;
- ulint err;
- char* buf;
+discard ongoing operations.
+5) FOREIGN KEY operations: if table->n_foreign_key_checks_running > 0, we
+do not allow the discard. We also reserve the data dictionary latch. */
static const char discard_tablespace_proc1[] =
"PROCEDURE DISCARD_TABLESPACE_PROC () IS\n"
@@ -2060,6 +2112,54 @@ row_discard_tablespace_for_mysql(
goto funct_exit;
}
+ if (table->n_foreign_key_checks_running > 0) {
+
+ ut_print_timestamp(stderr);
+ fputs(" InnoDB: You are trying to DISCARD table ", stderr);
+ ut_print_name(stderr, trx, table->name);
+ fputs("\n"
+ "InnoDB: though there is a foreign key check running on it.\n"
+ "InnoDB: Cannot discard the table.\n",
+ stderr);
+
+ err = DB_ERROR;
+
+ goto funct_exit;
+ }
+
+ /* Check if the table is referenced by foreign key constraints from
+ some other table (not the table itself) */
+
+ foreign = UT_LIST_GET_FIRST(table->referenced_list);
+
+ while (foreign && foreign->foreign_table == table) {
+ foreign = UT_LIST_GET_NEXT(referenced_list, foreign);
+ }
+
+ if (foreign && trx->check_foreigns) {
+
+ FILE* ef = dict_foreign_err_file;
+
+ /* We only allow discarding a referenced table if
+ FOREIGN_KEY_CHECKS is set to 0 */
+
+ err = DB_CANNOT_DROP_CONSTRAINT;
+
+ mutex_enter(&dict_foreign_err_mutex);
+ rewind(ef);
+ ut_print_timestamp(ef);
+
+ fputs(" Cannot DISCARD table ", ef);
+ ut_print_name(ef, trx, name);
+ fputs("\n"
+ "because it is referenced by ", ef);
+ ut_print_name(ef, trx, foreign->foreign_table_name);
+ putc('\n', ef);
+ mutex_exit(&dict_foreign_err_mutex);
+
+ goto funct_exit;
+ }
+
new_id = dict_hdr_get_new_id(DICT_HDR_TABLE_ID);
buf = mem_alloc((sizeof discard_tablespace_proc1) +
@@ -2077,6 +2177,10 @@ row_discard_tablespace_for_mysql(
ut_a(graph);
+ /* Remove any locks there are on the table or its records */
+
+ lock_reset_all_on_table(table);
+
graph->trx = trx;
trx->graph = NULL;
@@ -2227,8 +2331,8 @@ row_import_tablespace_for_mysql(
ibuf_delete_for_discarded_space(table->space);
- success = fil_open_single_table_tablespace(table->space, table->name);
-
+ success = fil_open_single_table_tablespace(TRUE, table->space,
+ table->name);
if (success) {
table->ibd_file_missing = FALSE;
table->tablespace_discarded = FALSE;
@@ -2236,7 +2340,7 @@ row_import_tablespace_for_mysql(
if (table->ibd_file_missing) {
ut_print_timestamp(stderr);
fputs(
-" InnoDB: cannot find of open in the database directory the .ibd file of\n"
+" InnoDB: cannot find or open in the database directory the .ibd file of\n"
"InnoDB: table ", stderr);
ut_print_name(stderr, trx, name);
fputs("\n"
@@ -2258,7 +2362,7 @@ funct_exit:
}
/*************************************************************************
-Drops a table for MySQL. If the name of the table to be dropped is equal
+Drops a table for MySQL. If the name of the table to be dropped is equal
with one of the predefined magic table names, then this also stops printing
the corresponding monitor output by the master thread. */
@@ -2492,36 +2596,60 @@ row_drop_table_for_mysql(
}
if (table->n_mysql_handles_opened > 0) {
+ ibool added;
- ut_print_timestamp(stderr);
- fputs(" InnoDB: Warning: MySQL is trying to drop table ",
- stderr);
- ut_print_name(stderr, trx, table->name);
- fputs("\n"
- "InnoDB: though there are still open handles to it.\n"
- "InnoDB: Adding the table to the background drop queue.\n",
- stderr);
+ added = row_add_table_to_background_drop_list(table);
- row_add_table_to_background_drop_list(table);
+ if (added) {
+ ut_print_timestamp(stderr);
+fputs(" InnoDB: Warning: MySQL is trying to drop table ", stderr);
+ ut_print_name(stderr, trx, table->name);
+ fputs("\n"
+"InnoDB: though there are still open handles to it.\n"
+"InnoDB: Adding the table to the background drop queue.\n",
+ stderr);
+
+ /* We return DB_SUCCESS to MySQL though the drop will
+ happen lazily later */
- err = DB_SUCCESS;
+ err = DB_SUCCESS;
+ } else {
+ /* The table is already in the background drop list */
+ err = DB_ERROR;
+ }
goto funct_exit;
}
+ /* TODO: could we replace the counter n_foreign_key_checks_running
+ with lock checks on the table? Acquire here an exclusive lock on the
+ table, and rewrite lock0lock.c and the lock wait in srv0srv.c so that
+ they can cope with the table having been dropped here? Foreign key
+ checks take an IS or IX lock on the table. */
+
if (table->n_foreign_key_checks_running > 0) {
- ut_print_timestamp(stderr);
- fputs(" InnoDB: You are trying to drop table ", stderr);
- ut_print_name(stderr, trx, table->name);
- fputs("\n"
- "InnoDB: though there is a foreign key check running on it.\n"
- "InnoDB: Adding the table to the background drop queue.\n",
+ ibool added;
+
+ added = row_add_table_to_background_drop_list(table);
+
+ if (added) {
+ ut_print_timestamp(stderr);
+fputs(" InnoDB: You are trying to drop table ", stderr);
+ ut_print_name(stderr, trx, table->name);
+ fputs("\n"
+"InnoDB: though there is a foreign key check running on it.\n"
+"InnoDB: Adding the table to the background drop queue.\n",
stderr);
- row_add_table_to_background_drop_list(table);
+ /* We return DB_SUCCESS to MySQL though the drop will
+ happen lazily later */
- err = DB_SUCCESS;
+ err = DB_SUCCESS;
+ } else {
+ /* The table is already in the background drop list */
+ err = DB_ERROR;
+ }
goto funct_exit;
}
@@ -3284,6 +3412,20 @@ row_check_table_for_mysql(
ulint ret = DB_SUCCESS;
ulint old_isolation_level;
+ if (prebuilt->table->ibd_file_missing) {
+ ut_print_timestamp(stderr);
+ fprintf(stderr, " InnoDB: Error:\n"
+"InnoDB: MySQL is trying to use a table handle but the .ibd file for\n"
+"InnoDB: table %s does not exist.\n"
+"InnoDB: Have you deleted the .ibd file from the database directory under\n"
+"InnoDB: the MySQL datadir, or have you used DISCARD TABLESPACE?\n"
+"InnoDB: Look from\n"
+"http://dev.mysql.com/doc/mysql/en/InnoDB_troubleshooting_datadict.html\n"
+"InnoDB: how you can resolve the problem.\n",
+ prebuilt->table->name);
+ return(DB_ERROR);
+ }
+
prebuilt->trx->op_info = "checking table";
old_isolation_level = prebuilt->trx->isolation_level;
diff --git a/innobase/row/row0sel.c b/innobase/row/row0sel.c
index 61ba0b53172..52228caccb0 100644
--- a/innobase/row/row0sel.c
+++ b/innobase/row/row0sel.c
@@ -2832,7 +2832,7 @@ row_search_for_mysql(
/* out: DB_SUCCESS,
DB_RECORD_NOT_FOUND,
DB_END_OF_INDEX, DB_DEADLOCK,
- DB_LOCK_TABLE_FULL,
+ DB_LOCK_TABLE_FULL, DB_CORRUPTION,
or DB_TOO_BIG_RECORD */
byte* buf, /* in/out: buffer for the fetched
row in the MySQL format */
@@ -2884,7 +2884,21 @@ row_search_for_mysql(
ut_ad(index && pcur && search_tuple);
ut_ad(trx->mysql_thread_id == os_thread_get_curr_id());
-
+
+ if (prebuilt->table->ibd_file_missing) {
+ ut_print_timestamp(stderr);
+ fprintf(stderr, " InnoDB: Error:\n"
+"InnoDB: MySQL is trying to use a table handle but the .ibd file for\n"
+"InnoDB: table %s does not exist.\n"
+"InnoDB: Have you deleted the .ibd file from the database directory under\n"
+"InnoDB: the MySQL datadir, or have you used DISCARD TABLESPACE?\n"
+"InnoDB: Look from\n"
+"http://dev.mysql.com/doc/mysql/en/InnoDB_troubleshooting_datadict.html\n"
+"InnoDB: how you can resolve the problem.\n",
+ prebuilt->table->name);
+ return(DB_ERROR);
+ }
+
if (prebuilt->magic_n != ROW_PREBUILT_ALLOCATED) {
fprintf(stderr,
"InnoDB: Error: trying to free a corrupt\n"
diff --git a/innobase/row/row0upd.c b/innobase/row/row0upd.c
index a449b9f1736..9192f6dc692 100644
--- a/innobase/row/row0upd.c
+++ b/innobase/row/row0upd.c
@@ -381,8 +381,14 @@ row_upd_changes_field_size_or_external(
new_len = new_val->len;
if (new_len == UNIV_SQL_NULL) {
+ /* A bug fixed on Dec 31st, 2004: we looked at the
+ SQL NULL size from the wrong field! We may backport
+ this fix also to 4.0. The merge to 5.0 will be made
+ manually immediately after we commit this to 4.1. */
+
new_len = dtype_get_sql_null_size(
- dict_index_get_nth_type(index, i));
+ dict_index_get_nth_type(index,
+ upd_field->field_no));
}
old_len = rec_get_nth_field_size(rec, upd_field->field_no);