diff options
Diffstat (limited to 'innobase/row/row0mysql.c')
-rw-r--r-- | innobase/row/row0mysql.c | 357 |
1 files changed, 327 insertions, 30 deletions
diff --git a/innobase/row/row0mysql.c b/innobase/row/row0mysql.c index 35305b037c6..497b74fd320 100644 --- a/innobase/row/row0mysql.c +++ b/innobase/row/row0mysql.c @@ -22,12 +22,15 @@ Created 9/17/2000 Heikki Tuuri #include "dict0dict.h" #include "dict0crea.h" #include "dict0load.h" +#include "dict0boot.h" #include "trx0roll.h" #include "trx0purge.h" #include "lock0lock.h" #include "rem0cmp.h" #include "log0log.h" #include "btr0sea.h" +#include "fil0fil.h" +#include "ibuf0ibuf.h" /* A dummy variable used to fool the compiler */ ibool row_mysql_identically_false = FALSE; @@ -1161,7 +1164,9 @@ row_mysql_recover_tmp_table( trx_t* trx) /* in: transaction handle */ { char* ptr; - char old_name[1000]; + char old_name[OS_FILE_MAX_PATH]; + + ut_a(ut_strlen(table->name) + 10 < OS_FILE_MAX_PATH); ut_memcpy(old_name, table->name, ut_strlen(table->name) + 1); @@ -1230,7 +1235,8 @@ row_mysql_lock_data_dictionary( /*===========================*/ trx_t* trx) /* in: transaction */ { - ut_a(trx->dict_operation_lock_mode == 0); + ut_a(trx->dict_operation_lock_mode == 0 + || trx->dict_operation_lock_mode == RW_X_LATCH); /* Serialize data dictionary operations with dictionary mutex: no deadlocks or lock waits can occur then in these operations */ @@ -1431,15 +1437,14 @@ row_create_table_for_mysql( "InnoDB: Warning: cannot create table %s because tablespace full\n", table->name); row_drop_table_for_mysql(table->name, trx); - } else { - ut_a(err == DB_DUPLICATE_KEY); + } else if (err == DB_DUPLICATE_KEY) { ut_print_timestamp(stderr); fprintf(stderr, " InnoDB: Error: table %s already exists in InnoDB internal\n" "InnoDB: data dictionary. Have you deleted the .frm file\n" - "InnoDB: and not used DROP TABLE? Have you used DROP DATABASE\n" + "InnoDB: and not used DROPT ABLE? Have you used DROP DATABASE\n" "InnoDB: for InnoDB tables in MySQL version <= 3.23.43?\n" "InnoDB: See the Restrictions section of the InnoDB manual.\n", table->name); @@ -1449,9 +1454,12 @@ row_create_table_for_mysql( "InnoDB: database and moving the .frm file to the current database.\n" "InnoDB: Then MySQL thinks the table exists, and DROP TABLE will\n" "InnoDB: succeed.\n" - "InnoDB: You can look further help from section 15.1 of\n" + "InnoDB: You can look for further help from section 15.1 of\n" "InnoDB: http://www.innodb.com/ibman.html\n"); } + + /* We may also get err == DB_ERROR if the .ibd file for the + table already exists */ trx->error_state = DB_SUCCESS; } @@ -1490,7 +1498,7 @@ row_create_index_for_mysql( trx->op_info = (char *) "creating index"; /* Check that the same column does not appear twice in the index. - Starting from 4.0.14 InnoDB should be able to cope with that, but + Starting from 4.0.14, InnoDB should be able to cope with that, but safer not to allow them. */ for (i = 0; i < dict_index_get_n_fields(index); i++) { @@ -1532,6 +1540,9 @@ row_create_index_for_mysql( trx->dict_operation = TRUE; + /* Note that the space id where we store the index is inherited from + the table in dict_build_index_def_step() in dict0crea.c. */ + node = ind_create_graph_create(index, heap); thr = pars_complete_graph_for_exec(node, trx, heap); @@ -1545,7 +1556,6 @@ row_create_index_for_mysql( que_graph_free((que_t*) que_node_get_parent(thr)); error_handling: - if (err != DB_SUCCESS) { /* We have special error handling here */ @@ -1806,6 +1816,218 @@ row_add_table_to_background_drop_list( } /************************************************************************* +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. + +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 +on the table before we can do DISCARD TABLESPACE. Then there are no running +queries on the table. +2) Purge and rollback: we assign a new table id for the table. Since purge and +rollback look for the table based on the table id, they see the table as +'dropped' and discard their operations. +3) Insert buffer: we remove all entries for the tablespace in the insert +buffer tree; as long as the tablespace mem object does not exist, ongoing +insert buffer page merges are discarded in buf0rea.c. If we recreate the +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 */ + 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[2 * OS_FILE_MAX_PATH]; + + ut_ad(trx->mysql_thread_id == os_thread_get_curr_id()); + + trx->op_info = (char *) "discarding tablespace"; + trx_start_if_not_started(trx); + + /* Serialize data dictionary operations with dictionary mutex: + no deadlocks can occur then in these operations */ + + row_mysql_lock_data_dictionary(trx); + + table = dict_table_get_low(name); + + if (!table) { + err = DB_TABLE_NOT_FOUND; + + goto funct_exit; + } + + new_id = dict_hdr_get_new_id(DICT_HDR_TABLE_ID); + + sprintf(buf, + "PROCEDURE DISCARD_TABLESPACE_PROC () IS\n" + "old_id CHAR;\n" + "new_id CHAR;\n" + "new_id_low INT;\n" + "new_id_high INT;\n" + "table_name CHAR;\n" + "BEGIN\n" + "table_name :='%s';\n" + "new_id_high := %lu;\n" + "new_id_low := %lu;\n" + "new_id := CONCAT(TO_BINARY(new_id_high, 4), TO_BINARY(new_id_low, 4));\n" + "SELECT ID INTO old_id\n" + "FROM SYS_TABLES\n" + "WHERE NAME = table_name;\n" + "IF (SQL % NOTFOUND) THEN\n" + " COMMIT WORK;\n" + " RETURN;\n" + "END IF;\n" + "UPDATE SYS_TABLES SET ID = new_id\n" + "WHERE ID = old_id;\n" + "UPDATE SYS_COLUMNS SET TABLE_ID = new_id\n" + "WHERE TABLE_ID = old_id;\n" + "UPDATE SYS_INDEXES SET TABLE_ID = new_id\n" + "WHERE TABLE_ID = old_id;\n" + "COMMIT WORK;\n" + "END;\n", name, ut_dulint_get_high(new_id), ut_dulint_get_low(new_id)); + + ut_a(strlen(buf) < 2 * OS_FILE_MAX_PATH); + + graph = pars_sql(buf); + + ut_a(graph); + + graph->trx = trx; + trx->graph = NULL; + + graph->fork_type = QUE_FORK_MYSQL_INTERFACE; + + ut_a(thr = que_fork_start_command(graph, SESS_COMM_EXECUTE, 0)); + + que_run_threads(thr); + + err = trx->error_state; + + if (err != DB_SUCCESS) { + trx->error_state = DB_SUCCESS; + trx_general_rollback_for_mysql(trx, FALSE, NULL); + trx->error_state = DB_SUCCESS; + } else { + dict_table_change_id_in_cache(table, new_id); + + success = fil_discard_tablespace(table->space); + + if (!success) { + trx->error_state = DB_SUCCESS; + trx_general_rollback_for_mysql(trx, FALSE, NULL); + trx->error_state = DB_SUCCESS; + + err = DB_ERROR; + } else { + /* Set the flag which tells that now it is legal to + IMPORT a tablespace for this table */ + table->tablespace_discarded = TRUE; + table->ibd_file_missing = TRUE; + } + } +funct_exit: + row_mysql_unlock_data_dictionary(trx); + + if (graph) { + que_graph_free(graph); + } + + trx_commit_for_mysql(trx); + + trx->op_info = (char *) ""; + + return((int) err); +} + +/********************************************************************* +Imports a tablespace. The space id in the .ibd file must match the space id +of the table in the data dictionary. */ + +int +row_import_tablespace_for_mysql( +/*============================*/ + /* out: error code or DB_SUCCESS */ + char* name, /* in: table name */ + trx_t* trx) /* in: transaction handle */ +{ + dict_table_t* table; + ibool success; + ulint err = DB_SUCCESS; + + ut_ad(trx->mysql_thread_id == os_thread_get_curr_id()); + + trx_start_if_not_started(trx); + + trx->op_info = (char*) "importing tablespace"; + + /* Serialize data dictionary operations with dictionary mutex: + no deadlocks can occur then in these operations */ + + row_mysql_lock_data_dictionary(trx); + + table = dict_table_get_low(name); + + if (!table) { + err = DB_TABLE_NOT_FOUND; + + goto funct_exit; + } + + if (!table->tablespace_discarded) { + ut_print_timestamp(stderr); + fprintf(stderr, +" InnoDB: Error: you are trying to IMPORT a tablespace\n" +"InnoDB: %s, though you have not called DISCARD on it yet\n" +"InnoDB: during the lifetime of the mysqld process!\n", name); + + err = DB_ERROR; + + goto funct_exit; + } + + /* Play safe and remove all insert buffer entries, though we should + have removed them already when DISCARD TABLESPACE was called */ + + ibuf_delete_for_discarded_space(table->space); + + success = fil_open_single_table_tablespace(table->space, table->name); + + printf( +"Remember to stop purge + undo if table->ibd_file_is_missing!!!\n"); + + if (success) { + table->ibd_file_missing = FALSE; + table->tablespace_discarded = FALSE; + } else { + err = DB_ERROR; + } + +funct_exit: + row_mysql_unlock_data_dictionary(trx); + + trx_commit_for_mysql(trx); + + trx->op_info = (char *) ""; + + return((int) err); +} + +/************************************************************************* Drops a table for MySQL. If the name of the dropped table ends to characters INNODB_MONITOR, then this also stops printing of monitor output by the master thread. */ @@ -1813,11 +2035,12 @@ output by the master thread. */ int row_drop_table_for_mysql( /*=====================*/ - /* out: error code or DB_SUCCESS */ - char* name, /* in: table name */ - trx_t* trx) /* in: transaction handle */ + /* out: error code or DB_SUCCESS */ + char* name, /* in: table name */ + trx_t* trx) /* in: transaction handle */ { dict_table_t* table; + ulint space_id; que_thr_t* thr; que_t* graph; ulint err; @@ -1826,8 +2049,9 @@ row_drop_table_for_mysql( ulint len; ulint namelen; ulint keywordlen; + ibool success; ibool locked_dictionary = FALSE; - char buf[10000]; + char buf[OS_FILE_MAX_PATH + 2000]; ut_ad(trx->mysql_thread_id == os_thread_get_curr_id()); ut_a(name != NULL); @@ -1968,6 +2192,8 @@ row_drop_table_for_mysql( ut_memcpy(buf + len, str2, ut_strlen(str2) + 1); + ut_a(strlen(buf) < OS_FILE_MAX_PATH + 2000); + /* Serialize data dictionary operations with dictionary mutex: no deadlocks can occur then in these operations */ @@ -1999,11 +2225,12 @@ row_drop_table_for_mysql( ut_print_timestamp(stderr); fprintf(stderr, - " InnoDB: Error: table %s does not exist in the InnoDB internal\n" + " InnoDB: Error: table %s\n" + "InnoDB: does not exist in the InnoDB internal\n" "InnoDB: data dictionary though MySQL is trying to drop it.\n" "InnoDB: Have you copied the .frm file of the table to the\n" "InnoDB: MySQL database directory from another database?\n" - "InnoDB: You can look further help from section 15.1 of\n" + "InnoDB: You can look for further help from section 15.1 of\n" "InnoDB: http://www.innodb.com/ibman.html\n", name); goto funct_exit; @@ -2063,13 +2290,32 @@ row_drop_table_for_mysql( ut_a(0); } else { + space_id = table->space; dict_table_remove_from_cache(table); if (dict_load_table(name) != NULL) { ut_print_timestamp(stderr); fprintf(stderr, -" InnoDB: Error: dropping of table %s failed!\n", name); +" InnoDB: Error: not able to remove table %s from the dictionary cache!\n", + name); + err = DB_ERROR; + } + + /* Do not drop possible .ibd tablespace if something went + wrong: we do not want to delete valuable data of the user */ + + if (err == DB_SUCCESS && space_id != 0 + && fil_tablespace_exists_in_mem(space_id)) { + success = fil_delete_tablespace(space_id); + + if (!success) { + ut_print_timestamp(stderr); + fprintf(stderr, +" InnoDB: Error: not able to delete tablespace %lu of table %s!\n", space_id, + name); + err = DB_ERROR; + } } } funct_exit: @@ -2203,9 +2449,13 @@ row_rename_table_for_mysql( mem_heap_t* heap = NULL; char** constraints_to_drop = NULL; ulint n_constraints_to_drop = 0; + ibool recovering_temp_table = FALSE; + ulint namelen; + ulint keywordlen; ulint len; ulint i; - char buf[10000]; + ibool success; + char buf[2 * OS_FILE_MAX_PATH]; ut_ad(trx->mysql_thread_id == os_thread_get_curr_id()); ut_a(old_name != NULL); @@ -2239,16 +2489,52 @@ row_rename_table_for_mysql( trx->op_info = (char *) "renaming table"; trx_start_if_not_started(trx); + namelen = ut_strlen(new_name); + + keywordlen = ut_strlen("_recover_innodb_tmp_table"); + + if (namelen >= keywordlen + && 0 == ut_memcmp(new_name + namelen - keywordlen, + (char*)"_recover_innodb_tmp_table", keywordlen)) { + + recovering_temp_table = TRUE; + } + /* Serialize data dictionary operations with dictionary mutex: no deadlocks can occur then in these operations */ - row_mysql_lock_data_dictionary(trx); + if (!recovering_temp_table) { + row_mysql_lock_data_dictionary(trx); + } table = dict_table_get_low(old_name); if (!table) { err = DB_TABLE_NOT_FOUND; + ut_print_timestamp(stderr); + + fprintf(stderr, + " InnoDB: Error: table %s\n" + "InnoDB: does not exist in the InnoDB internal\n" + "InnoDB: data dictionary though MySQL is trying to rename the table.\n" + "InnoDB: Have you copied the .frm file of the table to the\n" + "InnoDB: MySQL database directory from another database?\n" + "InnoDB: You can look for further help from section 15.1 of\n" + "InnoDB: http://www.innodb.com/ibman.html\n", + old_name); + goto funct_exit; + } + + if (table->ibd_file_missing) { + err = DB_TABLE_NOT_FOUND; + ut_print_timestamp(stderr); + fprintf(stderr, + " InnoDB: Error: table %s\n" + "InnoDB: does not have an .ibd file in the database directory.\n" + "InnoDB: You can look for further help from section 15.1 of\n" + "InnoDB: http://www.innodb.com/ibman.html\n", + old_name); goto funct_exit; } @@ -2331,6 +2617,8 @@ row_rename_table_for_mysql( ut_memcpy(buf + len, str3, ut_strlen(str3) + 1); + ut_a(strlen(buf) < 2 * OS_FILE_MAX_PATH); + graph = pars_sql(buf); ut_a(graph); @@ -2349,20 +2637,17 @@ row_rename_table_for_mysql( if (err != DB_SUCCESS) { if (err == DB_DUPLICATE_KEY) { ut_print_timestamp(stderr); - fprintf(stderr, " InnoDB: Error: table %s exists in the InnoDB internal data\n" "InnoDB: dictionary though MySQL is trying rename table %s to it.\n" "InnoDB: Have you deleted the .frm file and not used DROP TABLE?\n" - "InnoDB: You can look further help from section 15.1 of\n" + "InnoDB: You can look for further help from section 15.1 of\n" "InnoDB: http://www.innodb.com/ibman.html\n", new_name, old_name); - fprintf(stderr, "InnoDB: If table %s is a temporary table #sql..., then it can be that\n" "InnoDB: there are still queries running on the table, and it will be\n" "InnoDB: dropped automatically when the queries end.\n", new_name); - fprintf(stderr, "InnoDB: You can drop the orphaned table inside InnoDB by\n" "InnoDB: creating an InnoDB table with the same name in another\n" @@ -2370,13 +2655,27 @@ row_rename_table_for_mysql( "InnoDB: Then MySQL thinks the table exists, and DROP TABLE will\n" "InnoDB: succeed.\n"); } - trx->error_state = DB_SUCCESS; trx_general_rollback_for_mysql(trx, FALSE, NULL); trx->error_state = DB_SUCCESS; } else { - ut_a(dict_table_rename_in_cache(table, new_name, - !row_is_mysql_tmp_table_name(new_name))); + /* The following call will also rename the .ibd data file if + the table is stored in a single-table tablespace */ + + success = dict_table_rename_in_cache(table, new_name, + !row_is_mysql_tmp_table_name(new_name)); + if (!success) { + trx->error_state = DB_SUCCESS; + trx_general_rollback_for_mysql(trx, FALSE, NULL); + trx->error_state = DB_SUCCESS; + ut_print_timestamp(stderr); + fprintf(stderr, +" InnoDB: Error in table rename, cannot rename %s to %s\n", old_name, + new_name); + err = DB_ERROR; + + goto funct_exit; + } if (row_is_mysql_tmp_table_name(old_name)) { @@ -2390,18 +2689,14 @@ row_rename_table_for_mysql( err = dict_load_foreigns(new_name); if (err != DB_SUCCESS) { - ut_print_timestamp(stderr); - fprintf(stderr, " InnoDB: Error: in ALTER TABLE table %s\n" "InnoDB: has or is referenced in foreign key constraints\n" "InnoDB: which are not compatible with the new table definition.\n", new_name); - ut_a(dict_table_rename_in_cache(table, old_name, FALSE)); - trx->error_state = DB_SUCCESS; trx_general_rollback_for_mysql(trx, FALSE, NULL); @@ -2410,7 +2705,9 @@ row_rename_table_for_mysql( } } funct_exit: - row_mysql_unlock_data_dictionary(trx); + if (!recovering_temp_table) { + row_mysql_unlock_data_dictionary(trx); + } if (graph) { que_graph_free(graph); @@ -2567,7 +2864,7 @@ row_check_table_for_mysql( ulint n_rows_in_table = ULINT_UNDEFINED; ulint ret = DB_SUCCESS; ulint old_isolation_level; - + prebuilt->trx->op_info = (char *) "checking table"; old_isolation_level = prebuilt->trx->isolation_level; |