From d3827c1b22128fb7851a8ac1eabef5c815d0893d Mon Sep 17 00:00:00 2001 From: "heikki@hundin.mysql.fi" <> Date: Tue, 14 Oct 2003 14:53:16 +0300 Subject: [PATCH] row0purge.c, row0mysql.c, os0file.c, os0file.h, fil0fil.h, fil0fil.c: IMPORT TABLESPACE must reset lsns if they are too high ha_innodb.cc: DISCARD/IMPORT TABLESPACE must have a TL_WRITE lock on the table --- innobase/fil/fil0fil.c | 161 +++++++++++++++++++++++++++++++++++-- innobase/include/fil0fil.h | 19 +++++ innobase/include/os0file.h | 8 ++ innobase/os/os0file.c | 23 ++++++ innobase/row/row0mysql.c | 45 +++++++++++ innobase/row/row0purge.c | 2 +- sql/ha_innodb.cc | 3 +- 7 files changed, 250 insertions(+), 11 deletions(-) diff --git a/innobase/fil/fil0fil.c b/innobase/fil/fil0fil.c index f96d98c7b2d..c09590358e2 100644 --- a/innobase/fil/fil0fil.c +++ b/innobase/fil/fil0fil.c @@ -1302,11 +1302,13 @@ fil_write_flushed_lsn_to_data_files( space = UT_LIST_GET_FIRST(fil_system->space_list); while (space) { - /* We only write the lsn to the system tablespace - (space id == 0) files */ + /* We only write the lsn to all existing data files which have + been open during the lifetime of the mysqld process; they are + represented by the space objects in the tablespace memory + cache. Note that all data files in the system tablespace 0 are + always open. */ - if (space->id == 0) { - ut_a(space->purpose == FIL_TABLESPACE); + if (space->purpose == FIL_TABLESPACE) { sum_of_sizes = 0; node = UT_LIST_GET_FIRST(space->chain); @@ -1326,8 +1328,6 @@ fil_write_flushed_lsn_to_data_files( sum_of_sizes += node->size; node = UT_LIST_GET_NEXT(chain, node); } - - break; /* there is only one space with id == 0 */ } space = UT_LIST_GET_NEXT(space_list, space); } @@ -1937,6 +1937,147 @@ fil_create_new_single_table_tablespace( return(DB_SUCCESS); } +/************************************************************************ +It is possible, though very improbable, that the lsn's in the tablespace to be +imported have risen above the current system lsn, if a lengthy purge, ibuf +merge, or rollback was performed on a backup taken with ibbackup. If that is +the case, reset page lsn's in the file. We assume that mysqld was shut down +after it performed these cleanup operations on the .ibd file, so that it at +the shutdown stamped the latest lsn to the FIL_PAGE_FILE_FLUSH_LSN in the +first page of the .ibd file, and we can determine whether we need to reset the +lsn's just by looking at that flush lsn. */ + +ibool +fil_reset_too_high_lsns( +/*====================*/ + /* out: TRUE if success */ + char* name, /* in: table name in the databasename/tablename + format */ + dulint current_lsn) /* in: reset lsn's if the lsn stamped to + FIL_PAGE_FILE_FLUSH_LSN in the first page is + too high */ +{ + os_file_t file; + char* filepath; + byte* page; + dulint flush_lsn; + ulint space_id; + ib_longlong file_size; + ib_longlong offset; + ulint page_no; + ibool success; + + filepath = ut_malloc(OS_FILE_MAX_PATH); + + ut_a(strlen(name) < OS_FILE_MAX_PATH - 10); + + sprintf(filepath, "./%s.ibd", name); + + srv_normalize_path_for_win(filepath); + + file = os_file_create_simple_no_error_handling(filepath, OS_FILE_OPEN, + OS_FILE_READ_WRITE, &success); + if (!success) { + ut_free(filepath); + + return(FALSE); + } + + /* Read the first page of the tablespace */ + + page = ut_malloc(UNIV_PAGE_SIZE); + + success = os_file_read(file, page, 0, 0, UNIV_PAGE_SIZE); + if (!success) { + + goto func_exit; + } + + /* We have to read the file flush lsn from the header of the file */ + + flush_lsn = mach_read_from_8(page + FIL_PAGE_FILE_FLUSH_LSN); + + if (ut_dulint_cmp(current_lsn, flush_lsn) >= 0) { + /* Ok */ + success = TRUE; + + goto func_exit; + } + + space_id = fsp_header_get_space_id(page); + + ut_print_timestamp(stderr); + fprintf(stderr, +" InnoDB: Flush lsn in the tablespace file %lu to be imported\n" +"InnoDB: is %lu %lu, which exceeds current system lsn %lu %lu.\n" +"InnoDB: We reset the lsn's in the file %s.\n", + space_id, + ut_dulint_get_high(flush_lsn), + ut_dulint_get_low(flush_lsn), + ut_dulint_get_high(current_lsn), + ut_dulint_get_low(current_lsn), filepath); + + /* Loop through all the pages in the tablespace and reset the lsn and + the page checksum if necessary */ + + file_size = os_file_get_size_as_iblonglong(file); + + for (offset = 0; offset < file_size; offset += UNIV_PAGE_SIZE) { + success = os_file_read(file, page, + (ulint)(offset & 0xFFFFFFFFUL), + (ulint)(offset >> 32), UNIV_PAGE_SIZE); + if (!success) { + + goto func_exit; + } + if (ut_dulint_cmp(mach_read_from_8(page + FIL_PAGE_LSN), + current_lsn) > 0) { + /* We have to reset the lsn */ + space_id = mach_read_from_4(page + + FIL_PAGE_ARCH_LOG_NO_OR_SPACE_ID); + page_no = mach_read_from_4(page + FIL_PAGE_OFFSET); + + buf_flush_init_for_writing(page, current_lsn, space_id, + page_no); + success = os_file_write(filepath, file, page, + (ulint)(offset & 0xFFFFFFFFUL), + (ulint)(offset >> 32), UNIV_PAGE_SIZE); + if (!success) { + + goto func_exit; + } + } + } + + success = os_file_flush(file); + if (!success) { + + goto func_exit; + } + + /* We now update the flush_lsn stamp at the start of the file */ + success = os_file_read(file, page, 0, 0, UNIV_PAGE_SIZE); + if (!success) { + + goto func_exit; + } + + mach_write_to_8(page + FIL_PAGE_FILE_FLUSH_LSN, current_lsn); + + success = os_file_write(filepath, file, page, 0, 0, UNIV_PAGE_SIZE); + if (!success) { + + goto func_exit; + } + success = os_file_flush(file); +func_exit: + os_file_close(file); + ut_free(page); + ut_free(filepath); + + return(success); +} + /************************************************************************ Tries to open a single-table tablespace and checks the space id is right in it. If does not succeed, prints an error message to the .err log. This @@ -1982,7 +2123,9 @@ fil_open_single_table_tablespace( "InnoDB: open the tablespace file %s!\n", filepath); fprintf(stderr, "InnoDB: have you moved InnoDB .ibd files around without using the\n" -"InnoDB: commands DISCARD TABLESPACE and IMPORT TABLESPACE?\n"); +"InnoDB: commands DISCARD TABLESPACE and IMPORT TABLESPACE?\n" +"InnoDB: You can look from section 15.1 of http://www.innodb.com/ibman.html\n" +"InnoDB: how to resolve the issue.\n"); ut_free(filepath); @@ -2007,7 +2150,9 @@ fil_open_single_table_tablespace( "InnoDB: data dictionary it is %lu.\n", filepath, space_id, id); fprintf(stderr, "InnoDB: Have you moved InnoDB .ibd files around without using the\n" -"InnoDB: commands DISCARD TABLESPACE and IMPORT TABLESPACE?\n"); +"InnoDB: commands DISCARD TABLESPACE and IMPORT TABLESPACE?\n" +"InnoDB: You can look from section 15.1 of http://www.innodb.com/ibman.html\n" +"InnoDB: how to resolve the issue.\n"); ret = FALSE; diff --git a/innobase/include/fil0fil.h b/innobase/include/fil0fil.h index c76c87395b4..506bc6c1870 100644 --- a/innobase/include/fil0fil.h +++ b/innobase/include/fil0fil.h @@ -326,6 +326,25 @@ fil_open_single_table_tablespace( char* name); /* in: table name in the databasename/tablename format */ /************************************************************************ +It is possible, though very improbable, that the lsn's in the tablespace to be +imported have risen above the current system lsn, if a lengthy purge, ibuf +merge, or rollback was performed on a backup taken with ibbackup. If that is +the case, reset page lsn's in the file. We assume that mysqld was shut down +after it performed these cleanup operations on the .ibd file, so that it at +the shutdown stamped the latest lsn to the FIL_PAGE_FILE_FLUSH_LSN in the +first page of the .ibd file, and we can determine whether we need to reset the +lsn's just by looking at that flush lsn. */ + +ibool +fil_reset_too_high_lsns( +/*====================*/ + /* out: TRUE if success */ + char* name, /* in: table name in the databasename/tablename + format */ + dulint current_lsn); /* in: reset lsn's if the lsn stamped to + FIL_PAGE_FILE_FLUSH_LSN in the first page is + too high */ +/************************************************************************ At the server startup, if we need crash recovery, scans the database directories under the MySQL datadir, looking for .ibd files. Those files are single-table tablespaces. We need to know the space id in each of them so that diff --git a/innobase/include/os0file.h b/innobase/include/os0file.h index 52303e735ff..d1dc9dea18b 100644 --- a/innobase/include/os0file.h +++ b/innobase/include/os0file.h @@ -301,6 +301,14 @@ os_file_get_size( size */ ulint* size_high);/* out: most significant 32 bits of size */ /*************************************************************************** +Gets file size as a 64-bit integer ib_longlong. */ + +ib_longlong +os_file_get_size_as_iblonglong( +/*===========================*/ + /* out: size in bytes, -1 if error */ + os_file_t file); /* in: handle to a file */ +/*************************************************************************** Sets a file size. This function can be used to extend or truncate a file. */ ibool diff --git a/innobase/os/os0file.c b/innobase/os/os0file.c index 0e44104a53c..3682c160e5d 100644 --- a/innobase/os/os0file.c +++ b/innobase/os/os0file.c @@ -1204,6 +1204,29 @@ os_file_get_size( #endif } +/*************************************************************************** +Gets file size as a 64-bit integer ib_longlong. */ + +ib_longlong +os_file_get_size_as_iblonglong( +/*===========================*/ + /* out: size in bytes, -1 if error */ + os_file_t file) /* in: handle to a file */ +{ + ulint size; + ulint size_high; + ibool success; + + success = os_file_get_size(file, &size, &size_high); + + if (!success) { + + return(-1); + } + + return((((ib_longlong)size_high) << 32) + (ib_longlong)size); +} + /*************************************************************************** Sets a file size. This function can be used to extend or truncate a file. */ diff --git a/innobase/row/row0mysql.c b/innobase/row/row0mysql.c index 5d5985d16af..661aec3d9a9 100644 --- a/innobase/row/row0mysql.c +++ b/innobase/row/row0mysql.c @@ -1871,6 +1871,16 @@ row_discard_tablespace_for_mysql( goto funct_exit; } + if (table->space == 0) { + ut_print_timestamp(stderr); + fprintf(stderr, +" InnoDB: Error: table %s\n" +"InnoDB: is in the system tablespace 0 which cannot be discarded\n", name); + err = DB_ERROR; + + goto funct_exit; + } + new_id = dict_hdr_get_new_id(DICT_HDR_TABLE_ID); sprintf(buf, @@ -1967,6 +1977,7 @@ row_import_tablespace_for_mysql( { dict_table_t* table; ibool success; + dulint current_lsn; ulint err = DB_SUCCESS; ut_ad(trx->mysql_thread_id == os_thread_get_curr_id()); @@ -1975,6 +1986,30 @@ row_import_tablespace_for_mysql( trx->op_info = (char*) "importing tablespace"; + current_lsn = log_get_lsn(); + + /* It is possible, though very improbable, that the lsn's in the + tablespace to be imported have risen above the current system lsn, if + a lengthy purge, ibuf merge, or rollback was performed on a backup + taken with ibbackup. If that is the case, reset page lsn's in the + file. We assume that mysqld was shut down after it performed these + cleanup operations on the .ibd file, so that it stamped the latest lsn + to the FIL_PAGE_FILE_FLUSH_LSN in the first page of the .ibd file. + + TODO: reset also the trx id's in clustered index records and write + a new space id to each data page. That would allow us to import clean + .ibd files from another MySQL installation. */ + + success = fil_reset_too_high_lsns(name, current_lsn); + + if (!success) { + err = DB_ERROR; + + row_mysql_lock_data_dictionary(trx); + + goto funct_exit; + } + /* Serialize data dictionary operations with dictionary mutex: no deadlocks can occur then in these operations */ @@ -1988,6 +2023,16 @@ row_import_tablespace_for_mysql( goto funct_exit; } + if (table->space == 0) { + ut_print_timestamp(stderr); + fprintf(stderr, +" InnoDB: Error: table %s\n" +"InnoDB: is in the system tablespace 0 which cannot be imported\n", name); + err = DB_ERROR; + + goto funct_exit; + } + if (!table->tablespace_discarded) { ut_print_timestamp(stderr); fprintf(stderr, diff --git a/innobase/row/row0purge.c b/innobase/row/row0purge.c index 99332dc275d..a455722f15a 100644 --- a/innobase/row/row0purge.c +++ b/innobase/row/row0purge.c @@ -534,7 +534,7 @@ row_purge_parse_undo_rec( node->table = NULL; - return; + return(FALSE); } clust_index = dict_table_get_first_index(node->table); diff --git a/sql/ha_innodb.cc b/sql/ha_innodb.cc index d07b166d9e2..7f8c99f7c15 100644 --- a/sql/ha_innodb.cc +++ b/sql/ha_innodb.cc @@ -4572,8 +4572,7 @@ ha_innobase::external_lock( update_thd(thd); - if (lock_type != F_UNLCK && prebuilt->table->ibd_file_missing - && !current_thd->tablespace_op) { + if (prebuilt->table->ibd_file_missing && !current_thd->tablespace_op) { ut_print_timestamp(stderr); fprintf(stderr, " InnoDB error:\n" "MySQL is trying to use a table handle but the .ibd file for\n"