diff --git a/innobase/dict/dict0dict.c b/innobase/dict/dict0dict.c index 9e32be9e02d..95f9b54c914 100644 --- a/innobase/dict/dict0dict.c +++ b/innobase/dict/dict0dict.c @@ -195,6 +195,38 @@ dict_mutex_exit_for_mysql(void) mutex_exit(&(dict_sys->mutex)); } +/************************************************************************ +Increments the count of open MySQL handles to a table. */ + +void +dict_table_increment_handle_count( +/*==============================*/ + dict_table_t* table) /* in: table */ +{ + mutex_enter(&(dict_sys->mutex)); + + table->n_mysql_handles_opened++; + + mutex_exit(&(dict_sys->mutex)); +} + +/************************************************************************ +Decrements the count of open MySQL handles to a table. */ + +void +dict_table_decrement_handle_count( +/*==============================*/ + dict_table_t* table) /* in: table */ +{ + mutex_enter(&(dict_sys->mutex)); + + ut_a(table->n_mysql_handles_opened > 0); + + table->n_mysql_handles_opened--; + + mutex_exit(&(dict_sys->mutex)); +} + /************************************************************************ Gets the nth column of a table. */ diff --git a/innobase/dict/dict0mem.c b/innobase/dict/dict0mem.c index 019d6ef334f..1f9a44aca35 100644 --- a/innobase/dict/dict0mem.c +++ b/innobase/dict/dict0mem.c @@ -59,6 +59,9 @@ dict_mem_table_create( table->n_def = 0; table->n_cols = n_cols + DATA_N_SYS_COLS; table->mem_fix = 0; + + table->n_mysql_handles_opened = 0; + table->cached = FALSE; table->cols = mem_heap_alloc(heap, (n_cols + DATA_N_SYS_COLS) diff --git a/innobase/include/dict0dict.h b/innobase/include/dict0dict.h index ae313398c99..79d67ecae15 100644 --- a/innobase/include/dict0dict.h +++ b/innobase/include/dict0dict.h @@ -26,6 +26,20 @@ Created 1/8/1996 Heikki Tuuri #include "ut0byte.h" #include "trx0types.h" +/************************************************************************ +Increments the count of open MySQL handles to a table. */ + +void +dict_table_increment_handle_count( +/*==============================*/ + dict_table_t* table); /* in: table */ +/************************************************************************ +Decrements the count of open MySQL handles to a table. */ + +void +dict_table_decrement_handle_count( +/*==============================*/ + dict_table_t* table); /* in: table */ /************************************************************************** Inits the data dictionary module. */ diff --git a/innobase/include/dict0mem.h b/innobase/include/dict0mem.h index 2f00ccfcf32..5ef0103087a 100644 --- a/innobase/include/dict0mem.h +++ b/innobase/include/dict0mem.h @@ -307,6 +307,12 @@ struct dict_table_struct{ ulint mem_fix;/* count of how many times the table and its indexes has been fixed in memory; currently NOT used */ + ulint n_mysql_handles_opened; + /* count of how many handles MySQL has opened + to this table; dropping of the table is + NOT allowed until this count gets to zero; + MySQL does NOT itself check the number of + open handles at drop */ ibool cached; /* TRUE if the table object has been added to the dictionary cache */ lock_t* auto_inc_lock;/* a buffer for an auto-inc lock diff --git a/innobase/include/dyn0dyn.h b/innobase/include/dyn0dyn.h index 07ad8539b38..0952a8b4647 100644 --- a/innobase/include/dyn0dyn.h +++ b/innobase/include/dyn0dyn.h @@ -18,7 +18,7 @@ typedef dyn_block_t dyn_array_t; /* Initial 'payload' size in bytes in a dynamic array block */ -#define DYN_ARRAY_DATA_SIZE 1024 +#define DYN_ARRAY_DATA_SIZE 512 /************************************************************************* Initializes a dynamic array. */ diff --git a/innobase/include/mem0dbg.h b/innobase/include/mem0dbg.h index dda37626198..0b1aa53d694 100644 --- a/innobase/include/mem0dbg.h +++ b/innobase/include/mem0dbg.h @@ -10,11 +10,14 @@ Created 6/9/1994 Heikki Tuuri /* In the debug version each allocated field is surrounded with check fields whose sizes are given below */ +#ifdef UNIV_MEM_DEBUG #define MEM_FIELD_HEADER_SIZE ut_calc_align(2 * sizeof(ulint),\ UNIV_MEM_ALIGNMENT) #define MEM_FIELD_TRAILER_SIZE sizeof(ulint) +#else +#define MEM_FIELD_HEADER_SIZE 0 +#endif -#define MEM_BLOCK_MAGIC_N 764741 /* Space needed when allocating for a user a field of length N. The space is allocated only in multiples of @@ -115,3 +118,12 @@ ibool mem_validate(void); /*===============*/ /* out: TRUE if ok */ +/**************************************************************** +Tries to find neigboring memory allocation blocks and dumps to stderr +the neighborhood of a given pointer. */ + +void +mem_analyze_corruption( +/*===================*/ + byte* ptr); /* in: pointer to place of possible corruption */ + diff --git a/innobase/include/mem0mem.h b/innobase/include/mem0mem.h index 95024cf8011..57fac93d3ac 100644 --- a/innobase/include/mem0mem.h +++ b/innobase/include/mem0mem.h @@ -61,58 +61,41 @@ mem_init( /****************************************************************** Use this macro instead of the corresponding function! Macro for memory heap creation. */ -#ifdef UNIV_MEM_DEBUG + #define mem_heap_create(N) mem_heap_create_func(\ (N), NULL, MEM_HEAP_DYNAMIC,\ IB__FILE__, __LINE__) -#else -#define mem_heap_create(N) mem_heap_create_func(N, NULL, MEM_HEAP_DYNAMIC) -#endif /****************************************************************** Use this macro instead of the corresponding function! Macro for memory heap creation. */ -#ifdef UNIV_MEM_DEBUG + #define mem_heap_create_in_buffer(N) mem_heap_create_func(\ (N), NULL, MEM_HEAP_BUFFER,\ IB__FILE__, __LINE__) -#else -#define mem_heap_create_in_buffer(N) mem_heap_create_func(N, NULL,\ - MEM_HEAP_BUFFER) -#endif /****************************************************************** Use this macro instead of the corresponding function! Macro for memory heap creation. */ -#ifdef UNIV_MEM_DEBUG + #define mem_heap_create_in_btr_search(N) mem_heap_create_func(\ (N), NULL, MEM_HEAP_BTR_SEARCH |\ MEM_HEAP_BUFFER,\ IB__FILE__, __LINE__) -#else -#define mem_heap_create_in_btr_search(N) mem_heap_create_func(N, NULL,\ - MEM_HEAP_BTR_SEARCH | MEM_HEAP_BUFFER) -#endif /****************************************************************** Use this macro instead of the corresponding function! Macro for fast memory heap creation. An initial block of memory B is given by the caller, N is its size, and this memory block is not freed by mem_heap_free. See the parameter comment in mem_heap_create_func below. */ -#ifdef UNIV_MEM_DEBUG + #define mem_heap_fast_create(N, B) mem_heap_create_func(\ (N), (B), MEM_HEAP_DYNAMIC,\ IB__FILE__, __LINE__) -#else -#define mem_heap_fast_create(N, B) mem_heap_create_func(N, (B),\ - MEM_HEAP_DYNAMIC) -#endif + /****************************************************************** Use this macro instead of the corresponding function! Macro for memory heap freeing. */ -#ifdef UNIV_MEM_DEBUG + #define mem_heap_free(heap) mem_heap_free_func(\ (heap), IB__FILE__, __LINE__) -#else -#define mem_heap_free(heap) mem_heap_free_func(heap) -#endif /********************************************************************* NOTE: Use the corresponding macros instead of this function. Creates a memory heap which allocates memory from dynamic space. For debugging @@ -139,11 +122,9 @@ mem_heap_create_func( block is not unintentionally erased (if allocated in the stack), before the memory heap is explicitly freed. */ - ulint type /* in: MEM_HEAP_DYNAMIC or MEM_HEAP_BUFFER */ - #ifdef UNIV_MEM_DEBUG - ,char* file_name, /* in: file name where created */ + ulint type, /* in: MEM_HEAP_DYNAMIC or MEM_HEAP_BUFFER */ + char* file_name, /* in: file name where created */ ulint line /* in: line where created */ - #endif ); /********************************************************************* NOTE: Use the corresponding macro instead of this function. @@ -152,11 +133,9 @@ UNIV_INLINE void mem_heap_free_func( /*===============*/ - mem_heap_t* heap /* in, own: heap to be freed */ - #ifdef UNIV_MEM_DEBUG - ,char* file_name, /* in: file name where freed */ - ulint line /* in: line where freed */ - #endif + mem_heap_t* heap, /* in, own: heap to be freed */ + char* file_name, /* in: file name where freed */ + ulint line /* in: line where freed */ ); /******************************************************************* Allocates n bytes of memory from a memory heap. */ @@ -220,25 +199,18 @@ UNIV_INLINE ulint mem_heap_get_size( /*==============*/ - mem_heap_t* heap); /* in: heap */ + mem_heap_t* heap); /* in: heap */ /****************************************************************** Use this macro instead of the corresponding function! Macro for memory buffer allocation */ -#ifdef UNIV_MEM_DEBUG -#define mem_alloc(N) mem_alloc_func(\ - (N), IB__FILE__, __LINE__) -#else -#define mem_alloc(N) mem_alloc_func(N) -#endif + +#define mem_alloc(N) mem_alloc_func((N), IB__FILE__, __LINE__) /****************************************************************** Use this macro instead of the corresponding function! Macro for memory buffer allocation */ -#ifdef UNIV_MEM_DEBUG + #define mem_alloc_noninline(N) mem_alloc_func_noninline(\ (N), IB__FILE__, __LINE__) -#else -#define mem_alloc_noninline(N) mem_alloc_func_noninline(N) -#endif /******************************************************************* NOTE: Use the corresponding macro instead of this function. Allocates a single buffer of memory from the dynamic memory of @@ -250,11 +222,9 @@ mem_alloc_func( /*===========*/ /* out, own: free storage, NULL if did not succeed */ - ulint n /* in: desired number of bytes */ - #ifdef UNIV_MEM_DEBUG - ,char* file_name, /* in: file name where created */ - ulint line /* in: line where created */ - #endif + ulint n, /* in: desired number of bytes */ + char* file_name, /* in: file name where created */ + ulint line /* in: line where created */ ); /******************************************************************* NOTE: Use the corresponding macro instead of this function. @@ -267,21 +237,15 @@ mem_alloc_func_noninline( /*=====================*/ /* out, own: free storage, NULL if did not succeed */ - ulint n /* in: desired number of bytes */ - #ifdef UNIV_MEM_DEBUG - ,char* file_name, /* in: file name where created */ + ulint n, /* in: desired number of bytes */ + char* file_name, /* in: file name where created */ ulint line /* in: line where created */ - #endif ); /****************************************************************** Use this macro instead of the corresponding function! Macro for memory buffer freeing */ -#ifdef UNIV_MEM_DEBUG -#define mem_free(PTR) mem_free_func(\ - (PTR), IB__FILE__, __LINE__) -#else -#define mem_free(PTR) mem_free_func(PTR) -#endif + +#define mem_free(PTR) mem_free_func((PTR), IB__FILE__, __LINE__) /******************************************************************* NOTE: Use the corresponding macro instead of this function. Frees a single buffer of storage from @@ -290,11 +254,9 @@ UNIV_INLINE void mem_free_func( /*==========*/ - void* ptr /* in, own: buffer to be freed */ - #ifdef UNIV_MEM_DEBUG - ,char* file_name, /* in: file name where created */ - ulint line /* in: line where created */ - #endif + void* ptr, /* in, own: buffer to be freed */ + char* file_name, /* in: file name where created */ + ulint line /* in: line where created */ ); /******************************************************************* Implements realloc. */ @@ -304,7 +266,9 @@ mem_realloc( /*========*/ /* out, own: free storage, NULL if did not succeed */ void* buf, /* in: pointer to an old buffer */ - ulint n); /* in: desired number of bytes */ + ulint n, /* in: desired number of bytes */ + char* file_name,/* in: file name where called */ + ulint line); /* in: line where called */ /*#######################################################################*/ @@ -336,8 +300,13 @@ struct mem_block_info_struct { free block to the heap, if we need more space; otherwise, this is NULL */ ulint magic_n;/* magic number for debugging */ + char file_name[8];/* file name where the mem heap was created */ + ulint line; /* line number where the mem heap was created */ }; +#define MEM_BLOCK_MAGIC_N 764741555 +#define MEM_FREED_BLOCK_MAGIC_N 547711122 + /* Header size for a memory heap block */ #define MEM_BLOCK_HEADER_SIZE ut_calc_align(sizeof(mem_block_info_t),\ UNIV_MEM_ALIGNMENT) diff --git a/innobase/include/mem0mem.ic b/innobase/include/mem0mem.ic index edc3ab17f2a..a7abb93d91d 100644 --- a/innobase/include/mem0mem.ic +++ b/innobase/include/mem0mem.ic @@ -24,8 +24,10 @@ mem_heap_create_block( if init_block is not NULL, its size in bytes */ void* init_block, /* in: init block in fast create, type must be MEM_HEAP_DYNAMIC */ - ulint type); /* in: type of heap: MEM_HEAP_DYNAMIC or + ulint type, /* in: type of heap: MEM_HEAP_DYNAMIC or MEM_HEAP_BUFFER */ + char* file_name,/* in: file name where created */ + ulint line); /* in: line where created */ /********************************************************************** Frees a block from a memory heap. */ @@ -392,21 +394,20 @@ mem_heap_create_func( block is not unintentionally erased (if allocated in the stack), before the memory heap is explicitly freed. */ - ulint type /* in: MEM_HEAP_DYNAMIC, or MEM_HEAP_BUFFER + ulint type, /* in: MEM_HEAP_DYNAMIC, or MEM_HEAP_BUFFER possibly ORed to MEM_HEAP_BTR_SEARCH */ - #ifdef UNIV_MEM_DEBUG - ,char* file_name, /* in: file name where created */ + char* file_name, /* in: file name where created */ ulint line /* in: line where created */ - #endif ) { mem_block_t* block; if (n > 0) { - block = mem_heap_create_block(NULL, n, init_block, type); + block = mem_heap_create_block(NULL, n, init_block, type, + file_name, line); } else { block = mem_heap_create_block(NULL, MEM_BLOCK_START_SIZE, - init_block, type); + init_block, type, file_name, line); } ut_ad(block); @@ -438,11 +439,9 @@ UNIV_INLINE void mem_heap_free_func( /*===============*/ - mem_heap_t* heap /* in, own: heap to be freed */ - #ifdef UNIV_MEM_DEBUG - ,char* file_name, /* in: file name where freed */ + mem_heap_t* heap, /* in, own: heap to be freed */ + char* file_name, /* in: file name where freed */ ulint line /* in: line where freed */ - #endif ) { mem_block_t* block; @@ -488,14 +487,12 @@ mem_alloc_func( /*===========*/ /* out, own: free storage, NULL if did not succeed */ - ulint n /* in: desired number of bytes */ - #ifdef UNIV_MEM_DEBUG - ,char* file_name, /* in: file name where created */ + ulint n, /* in: desired number of bytes */ + char* file_name, /* in: file name where created */ ulint line /* in: line where created */ - #endif ) { - #ifndef UNIV_MEM_DEBUG +#ifdef notdefined void* buf; buf = mem_area_alloc(n, mem_comm_pool); @@ -505,7 +502,7 @@ mem_alloc_func( #endif return(buf); - #else +#else mem_heap_t* heap; void* buf; @@ -524,11 +521,11 @@ mem_alloc_func( buf = mem_heap_alloc(heap, n); - ut_ad((byte*)heap == (byte*)buf - MEM_BLOCK_HEADER_SIZE + ut_a((byte*)heap == (byte*)buf - MEM_BLOCK_HEADER_SIZE - MEM_FIELD_HEADER_SIZE); return(buf); - #endif +#endif } /******************************************************************* @@ -539,26 +536,22 @@ UNIV_INLINE void mem_free_func( /*==========*/ - void* ptr /* in, own: buffer to be freed */ - #ifdef UNIV_MEM_DEBUG - ,char* file_name, /* in: file name where created */ + void* ptr, /* in, own: buffer to be freed */ + char* file_name, /* in: file name where created */ ulint line /* in: line where created */ - #endif ) { - #ifndef UNIV_MEM_DEBUG +#ifdef notdefined mem_area_free(ptr, mem_comm_pool); - #else - +#else mem_heap_t* heap; heap = (mem_heap_t*)((byte*)ptr - MEM_BLOCK_HEADER_SIZE - MEM_FIELD_HEADER_SIZE); mem_heap_free_func(heap, file_name, line); - - #endif +#endif } /********************************************************************* @@ -567,7 +560,7 @@ UNIV_INLINE ulint mem_heap_get_size( /*==============*/ - mem_heap_t* heap) /* in: heap */ + mem_heap_t* heap) /* in: heap */ { mem_block_t* block; ulint size = 0; @@ -597,9 +590,11 @@ mem_realloc( /*========*/ /* out, own: free storage, NULL if did not succeed */ void* buf, /* in: pointer to an old buffer */ - ulint n) /* in: desired number of bytes */ + ulint n, /* in: desired number of bytes */ + char* file_name,/* in: file name where called */ + ulint line) /* in: line where called */ { mem_free(buf); - return(mem_alloc(n)); + return(mem_alloc_func(n, file_name, line)); } diff --git a/innobase/include/page0page.ic b/innobase/include/page0page.ic index 6e33fe2ca5d..f84fe5a5606 100644 --- a/innobase/include/page0page.ic +++ b/innobase/include/page0page.ic @@ -512,6 +512,8 @@ page_dir_find_owner_slot( slot = page_dir_get_nth_slot(page, i); while (page_dir_slot_get_rec(slot) != rec) { + ut_a(i > 0); + i--; slot = page_dir_get_nth_slot(page, i); } diff --git a/innobase/include/que0que.h b/innobase/include/que0que.h index 4cbd888ba1d..cdaeeae1fde 100644 --- a/innobase/include/que0que.h +++ b/innobase/include/que0que.h @@ -327,6 +327,8 @@ mutex with the exceptions named below */ struct que_thr_struct{ que_common_t common; /* type: QUE_NODE_THR */ + ulint magic_n; /* magic number to catch memory + corruption */ que_node_t* child; /* graph child node */ que_t* graph; /* graph where this node belongs */ ibool is_active; /* TRUE if the thread has been set @@ -357,6 +359,9 @@ struct que_thr_struct{ thus far */ }; +#define QUE_THR_MAGIC_N 8476583 +#define QUE_THR_MAGIC_FREED 123461526 + /* Query graph fork node: its fields are protected by the kernel mutex */ struct que_fork_struct{ que_common_t common; /* type: QUE_NODE_FORK */ diff --git a/innobase/include/row0mysql.h b/innobase/include/row0mysql.h index 32354219e64..0346299bb10 100644 --- a/innobase/include/row0mysql.h +++ b/innobase/include/row0mysql.h @@ -323,11 +323,18 @@ struct mysql_row_templ_struct { /* After fetching this many rows, we start caching them in fetch_cache */ #define MYSQL_FETCH_CACHE_THRESHOLD 4 +#define ROW_PREBUILT_ALLOCATED 78540783 +#define ROW_PREBUILT_FREED 26423527 /* A struct for (sometimes lazily) prebuilt structures in an Innobase table handle used within MySQL; these are used to save CPU time. */ struct row_prebuilt_struct { + ulint magic_n; /* this magic number is set to + ROW_PREBUILT_ALLOCATED when created + and to ROW_PREBUILT_FREED when the + struct has been freed; used in + debugging */ dict_table_t* table; /* Innobase table handle */ trx_t* trx; /* current transaction handle */ ibool sql_stat_start; /* TRUE when we start processing of diff --git a/innobase/include/srv0srv.h b/innobase/include/srv0srv.h index 1a37460569b..05989c6410e 100644 --- a/innobase/include/srv0srv.h +++ b/innobase/include/srv0srv.h @@ -17,6 +17,8 @@ Created 10/10/1995 Heikki Tuuri #include "que0types.h" #include "trx0types.h" +/* Buffer which can be used in printing fatal error messages */ +extern char srv_fatal_errbuf[]; /* When this event is set the lock timeout and InnoDB monitor thread starts running */ @@ -261,15 +263,27 @@ This lets a thread enter InnoDB regardless of the number of threads inside InnoDB. This must be called when a thread ends a lock wait. */ void -srv_conc_force_enter_innodb(void); -/*=============================*/ +srv_conc_force_enter_innodb( +/*========================*/ + trx_t* trx); /* in: transaction object associated with the + thread */ /************************************************************************* -This must be called when a thread exits InnoDB. This must also be called -when a thread goes to wait for a lock. */ +This must be called when a thread exits InnoDB in a lock wait or at the +end of an SQL statement. */ void -srv_conc_exit_innodb(void); -/*======================*/ +srv_conc_force_exit_innodb( +/*=======================*/ + trx_t* trx); /* in: transaction object associated with the + thread */ +/************************************************************************* +This must be called when a thread exits InnoDB. */ + +void +srv_conc_exit_innodb( +/*=================*/ + trx_t* trx); /* in: transaction object associated with the + thread */ /******************************************************************* Puts a MySQL OS thread to wait for a lock to be released. */ diff --git a/innobase/include/trx0sys.h b/innobase/include/trx0sys.h index 0295cd6abff..f2eded697ec 100644 --- a/innobase/include/trx0sys.h +++ b/innobase/include/trx0sys.h @@ -218,6 +218,22 @@ trx_in_trx_list( /*============*/ /* out: TRUE if is in */ trx_t* in_trx);/* in: trx */ +/********************************************************************* +Updates the offset information about the end of the MySQL binlog entry +which corresponds to the transaction just being committed. */ + +void +trx_sys_update_mysql_binlog_offset( +/*===============================*/ + trx_t* trx, /* in: transaction being committed */ + mtr_t* mtr); /* in: mtr */ +/********************************************************************* +Prints to stderr the MySQL binlog offset info in the trx system header if +the magic number shows it valid. */ + +void +trx_sys_print_mysql_binlog_offset(void); +/*===================================*/ /* The automatically created system rollback segment has this id */ #define TRX_SYS_SYSTEM_RSEG_ID 0 @@ -236,7 +252,7 @@ therefore 256 */ /* Transaction system header; protected by trx_sys->mutex */ /*-------------------------------------------------------------*/ -#define TRX_SYS_TRX_ID_STORE 0 /* The maximum trx id or trx number +#define TRX_SYS_TRX_ID_STORE 0 /* the maximum trx id or trx number modulo TRX_SYS_TRX_ID_UPDATE_MARGIN written to a file page by any transaction; the assignment of @@ -252,6 +268,23 @@ therefore 256 */ segment specification slots */ /*-------------------------------------------------------------*/ +#define TRX_SYS_MYSQL_LOG_NAME_LEN 32 +#define TRX_SYS_MYSQL_LOG_MAGIC_N 873422344 + +/* The offset of the MySQL binlog offset info on the trx system header page */ +#define TRX_SYS_MYSQL_LOG_INFO (UNIV_PAGE_SIZE - 300) +#define TRX_SYS_MYSQL_LOG_MAGIC_N_FLD 0 /* magic number which shows + if we have valid data in the + MySQL binlog info; the value + is ..._MAGIC_N if yes */ +#define TRX_SYS_MYSQL_LOG_NAME 4 /* MySQL log file name */ +#define TRX_SYS_MYSQL_LOG_OFFSET_HIGH (4 + TRX_SYS_MYSQL_LOG_NAME_LEN) + /* high 4 bytes of the offset + within that file */ +#define TRX_SYS_MYSQL_LOG_OFFSET_LOW (8 + TRX_SYS_MYSQL_LOG_NAME_LEN) + /* low 4 bytes of the offset + within that file */ + /* The offset of the doublewrite buffer header on the trx system header page */ #define TRX_SYS_DOUBLEWRITE (UNIV_PAGE_SIZE - 200) /*-------------------------------------------------------------*/ diff --git a/innobase/include/trx0trx.h b/innobase/include/trx0trx.h index 58cef01b376..d67628b8bad 100644 --- a/innobase/include/trx0trx.h +++ b/innobase/include/trx0trx.h @@ -290,10 +290,20 @@ struct trx_struct{ table */ dulint table_id; /* table id if the preceding field is TRUE */ + /*------------------------------*/ void* mysql_thd; /* MySQL thread handle corresponding to this trx, or NULL */ + char* mysql_log_file_name; + /* If MySQL binlog is used, this field + contains a pointer to the latest file + name; this is NULL if binlog is not + used */ + ib_longlong mysql_log_offset;/* If MySQL binlog is used, this field + contains the end offset of the binlog + entry */ os_thread_id_t mysql_thread_id;/* id of the MySQL thread associated with this transaction object */ + /*------------------------------*/ ulint n_mysql_tables_in_use; /* number of Innobase tables used in the processing of the current SQL statement in MySQL */ @@ -314,6 +324,18 @@ struct trx_struct{ calls from MySQL; this is intended to reduce contention on the search latch */ + /*------------------------------*/ + ibool declared_to_be_inside_innodb; + /* this is TRUE if we have declared + this transaction in + srv_conc_enter_innodb to be inside the + InnoDB engine */ + ulint n_tickets_to_enter_innodb; + /* this can be > 0 only when + declared_to_... is TRUE; when we come + to srv_conc_innodb_enter, if the value + here is > 0, we decrement this by 1 */ + /*------------------------------*/ lock_t* auto_inc_lock; /* possible auto-inc lock reserved by the transaction; note that it is also in the lock list trx_locks */ diff --git a/innobase/log/log0recv.c b/innobase/log/log0recv.c index 5cd5850d1a1..7be172685ae 100644 --- a/innobase/log/log0recv.c +++ b/innobase/log/log0recv.c @@ -51,6 +51,8 @@ recv_sys_t* recv_sys = NULL; ibool recv_recovery_on = FALSE; ibool recv_recovery_from_backup_on = FALSE; +ibool recv_needed_recovery = FALSE; + /* If the following is TRUE, the buffer pool file pages must be invalidated after recovery and no ibuf operations are allowed; this becomes TRUE if the log record hash table becomes too full, and log records must be merged @@ -1020,7 +1022,7 @@ loop: if (!has_printed) { fprintf(stderr, "InnoDB: Starting an apply batch of log records to the database...\n" -"InnoDB: Progress in percents:"); +"InnoDB: Progress in percents: "); has_printed = TRUE; } @@ -2032,12 +2034,16 @@ recv_recovery_from_checkpoint_start( if (ut_dulint_cmp(checkpoint_lsn, max_flushed_lsn) != 0 || ut_dulint_cmp(checkpoint_lsn, min_flushed_lsn) != 0) { + recv_needed_recovery = TRUE; + + ut_print_timestamp(stderr); + fprintf(stderr, - "InnoDB: Database was not shut down normally.\n" - "InnoDB: Starting recovery from log files...\n"); + " InnoDB: Database was not shut down normally.\n" + "InnoDB: Starting recovery from log files...\n"); fprintf(stderr, - "InnoDB: Starting log scan based on checkpoint at\n" - "InnoDB: log sequence number %lu %lu\n", + "InnoDB: Starting log scan based on checkpoint at\n" + "InnoDB: log sequence number %lu %lu\n", ut_dulint_get_high(checkpoint_lsn), ut_dulint_get_low(checkpoint_lsn)); } @@ -2199,6 +2205,10 @@ recv_recovery_from_checkpoint_finish(void) "InnoDB: Log records applied to the database\n"); } + if (recv_needed_recovery) { + trx_sys_print_mysql_binlog_offset(); + } + /* Free the resources of the recovery system */ recv_recovery_on = FALSE; diff --git a/innobase/mem/mem0dbg.c b/innobase/mem/mem0dbg.c index 0d71708b906..f8f62dffa8b 100644 --- a/innobase/mem/mem0dbg.c +++ b/innobase/mem/mem0dbg.c @@ -808,7 +808,7 @@ mem_validate_no_assert(void) } mutex_exit(&mem_hash_mutex); - + return(error); #else @@ -832,3 +832,95 @@ mem_validate(void) return(TRUE); } + +/**************************************************************** +Tries to find neigboring memory allocation blocks and dumps to stderr +the neighborhood of a given pointer. */ + +void +mem_analyze_corruption( +/*===================*/ + byte* ptr) /* in: pointer to place of possible corruption */ +{ + byte* p; + ulint i; + ulint dist; + + ut_sprintf_buf(srv_fatal_errbuf, ptr - 250, 500); + fprintf(stderr, + "InnoDB: Apparent memory corruption: mem dump %s\n", srv_fatal_errbuf); + + fprintf(stderr, + "InnoDB: Scanning backward trying to find previous allocated mem blocks\n"); + + p = ptr; + dist = 0; + + for (i = 0; i < 10; i++) { + for (;;) { + if (((ulint)p) % 4 == 0) { + + if (*((ulint*)p) == MEM_BLOCK_MAGIC_N) { + fprintf(stderr, + "Mem block at - %lu, file %s, line %lu\n", + dist, p + sizeof(ulint), + *(ulint*)(p + 8 + sizeof(ulint))); + + break; + } + + if (*((ulint*)p) == MEM_FREED_BLOCK_MAGIC_N) { + fprintf(stderr, + "Freed mem block at - %lu, file %s, line %lu\n", + dist, p + sizeof(ulint), + *(ulint*)(p + 8 + sizeof(ulint))); + + break; + } + } + + p--; + dist++; + } + + p--; + dist++; + } + + fprintf(stderr, + "InnoDB: Scanning forward trying to find next allocated mem blocks\n"); + + p = ptr; + dist = 0; + + for (i = 0; i < 10; i++) { + for (;;) { + if (((ulint)p) % 4 == 0) { + + if (*((ulint*)p) == MEM_BLOCK_MAGIC_N) { + fprintf(stderr, + "Mem block at + %lu, file %s, line %lu\n", + dist, p + sizeof(ulint), + *(ulint*)(p + 8 + sizeof(ulint))); + + break; + } + + if (*((ulint*)p) == MEM_FREED_BLOCK_MAGIC_N) { + fprintf(stderr, + "Freed mem block at + %lu, file %s, line %lu\n", + dist, p + sizeof(ulint), + *(ulint*)(p + 8 + sizeof(ulint))); + + break; + } + } + + p++; + dist++; + } + + p++; + dist++; + } +} diff --git a/innobase/mem/mem0mem.c b/innobase/mem/mem0mem.c index 19a2c0d61a7..58fb618e2db 100644 --- a/innobase/mem/mem0mem.c +++ b/innobase/mem/mem0mem.c @@ -14,8 +14,9 @@ Created 6/9/1994 Heikki Tuuri #include "mach0data.h" #include "buf0buf.h" -#include "mem0dbg.c" #include "btr0sea.h" +#include "srv0srv.h" +#include "mem0dbg.c" /* THE MEMORY MANAGEMENT @@ -85,18 +86,12 @@ mem_alloc_func_noninline( /*=====================*/ /* out, own: free storage, NULL if did not succeed */ - ulint n /* in: desired number of bytes */ - #ifdef UNIV_MEM_DEBUG - ,char* file_name, /* in: file name where created */ + ulint n, /* in: desired number of bytes */ + char* file_name, /* in: file name where created */ ulint line /* in: line where created */ - #endif ) { - return(mem_alloc_func(n -#ifdef UNIV_MEM_DEBUG - , file_name, line -#endif - )); + return(mem_alloc_func(n, file_name, line)); } /******************************************************************* @@ -113,8 +108,10 @@ mem_heap_create_block( if init_block is not NULL, its size in bytes */ void* init_block, /* in: init block in fast create, type must be MEM_HEAP_DYNAMIC */ - ulint type) /* in: type of heap: MEM_HEAP_DYNAMIC, or + ulint type, /* in: type of heap: MEM_HEAP_DYNAMIC, or MEM_HEAP_BUFFER possibly ORed to MEM_HEAP_BTR_SEARCH */ + char* file_name,/* in: file name where created */ + ulint line) /* in: line where created */ { mem_block_t* block; ulint len; @@ -164,7 +161,11 @@ mem_heap_create_block( } block->magic_n = MEM_BLOCK_MAGIC_N; - + ut_memcpy(&(block->file_name), file_name + ut_strlen(file_name) - 7, + 7); + block->file_name[7]='\0'; + block->line = line; + mem_block_set_len(block, len); mem_block_set_type(block, type); mem_block_set_free(block, MEM_BLOCK_HEADER_SIZE); @@ -223,8 +224,8 @@ mem_heap_add_block( new_size = n; } - new_block = mem_heap_create_block(heap, new_size, NULL, heap->type); - + new_block = mem_heap_create_block(heap, new_size, NULL, heap->type, + heap->file_name, heap->line); if (new_block == NULL) { return(NULL); @@ -255,7 +256,8 @@ mem_heap_block_free( type = heap->type; len = block->len; init_block = block->init_block; - + block->magic_n = MEM_FREED_BLOCK_MAGIC_N; + #ifdef UNIV_MEM_DEBUG /* In the debug version we set the memory to a random combination of hex 0xDE and 0xAD. */ diff --git a/innobase/pars/lexyy.c b/innobase/pars/lexyy.c index 67bd12afa60..10bdcdd0990 100644 --- a/innobase/pars/lexyy.c +++ b/innobase/pars/lexyy.c @@ -7373,7 +7373,7 @@ void *ptr; unsigned int size; #endif { - return (void *) mem_realloc( ptr, size ); + return (void *) mem_realloc( ptr, size, __FILE__, __LINE__ ); } #ifdef YY_USE_PROTOS diff --git a/innobase/que/que0que.c b/innobase/que/que0que.c index 96e505f8b80..97843311d21 100644 --- a/innobase/que/que0que.c +++ b/innobase/que/que0que.c @@ -183,6 +183,8 @@ que_thr_create( thr->common.type = QUE_NODE_THR; thr->common.parent = parent; + thr->magic_n = QUE_THR_MAGIC_N; + thr->graph = parent->graph; thr->state = QUE_THR_COMMAND_WAIT; @@ -485,7 +487,6 @@ que_graph_free_recursive( tab_node_t* cre_tab; ind_node_t* cre_ind; - if (node == NULL) { return; @@ -509,6 +510,16 @@ que_graph_free_recursive( thr = node; + if (thr->magic_n != QUE_THR_MAGIC_N) { + fprintf(stderr, + "que_thr struct appears corrupt; magic n %lu\n", + thr->magic_n); + mem_analyze_corruption((byte*)thr); + ut_a(0); + } + + thr->magic_n = QUE_THR_MAGIC_FREED; + que_graph_free_recursive(thr->child); break; @@ -606,6 +617,10 @@ que_graph_free_recursive( break; default: + fprintf(stderr, + "que_node struct appears corrupt; type %lu\n", + que_node_get_type(node)); + mem_analyze_corruption((byte*)node); ut_a(0); } } @@ -1068,20 +1083,29 @@ que_thr_stop_for_mysql( mutex_exit(&kernel_mutex); } - /************************************************************************** Moves a thread from another state to the QUE_THR_RUNNING state. Increments the n_active_thrs counters of the query graph and transaction if thr was not active. */ + void que_thr_move_to_run_state_for_mysql( /*================================*/ que_thr_t* thr, /* in: an query thread */ trx_t* trx) /* in: transaction */ { + if (thr->magic_n != QUE_THR_MAGIC_N) { + fprintf(stderr, + "que_thr struct appears corrupt; magic n %lu\n", thr->magic_n); + + mem_analyze_corruption((byte*)thr); + + ut_a(0); + } + if (!thr->is_active) { - (thr->graph)->n_active_thrs++; + thr->graph->n_active_thrs++; trx->n_active_thrs++; @@ -1097,6 +1121,7 @@ que_thr_move_to_run_state_for_mysql( /************************************************************************** A patch for MySQL used to 'stop' a dummy query thread used in MySQL select, when there is no error or lock wait. */ + void que_thr_stop_for_mysql_no_error( /*============================*/ @@ -1105,6 +1130,15 @@ que_thr_stop_for_mysql_no_error( { ut_ad(thr->state == QUE_THR_RUNNING); + if (thr->magic_n != QUE_THR_MAGIC_N) { + fprintf(stderr, + "que_thr struct appears corrupt; magic n %lu\n", thr->magic_n); + + mem_analyze_corruption((byte*)thr); + + ut_a(0); + } + thr->state = QUE_THR_COMPLETED; thr->is_active = FALSE; diff --git a/innobase/rem/rem0rec.c b/innobase/rem/rem0rec.c index 749e19575bc..3889f62afa2 100644 --- a/innobase/rem/rem0rec.c +++ b/innobase/rem/rem0rec.c @@ -105,6 +105,17 @@ rec_get_nth_field( ut_ad(rec && len); ut_ad(n < rec_get_n_fields(rec)); + if (n > 1024) { + fprintf(stderr, "Error: trying to access field %lu in rec\n", + n); + ut_a(0); + } + + if (rec == NULL) { + fprintf(stderr, "Error: rec is NULL pointer\n"); + ut_a(0); + } + if (rec_get_1byte_offs_flag(rec)) { os = rec_1_get_field_start_offs(rec, n); diff --git a/innobase/row/row0mysql.c b/innobase/row/row0mysql.c index 9622a7cee32..c0f527017e0 100644 --- a/innobase/row/row0mysql.c +++ b/innobase/row/row0mysql.c @@ -242,10 +242,14 @@ row_create_prebuilt( ulint ref_len; ulint i; + dict_table_increment_handle_count(table); + heap = mem_heap_create(128); prebuilt = mem_heap_alloc(heap, sizeof(row_prebuilt_t)); + prebuilt->magic_n = ROW_PREBUILT_ALLOCATED; + prebuilt->table = table; prebuilt->trx = NULL; @@ -294,7 +298,7 @@ row_create_prebuilt( prebuilt->blob_heap = NULL; prebuilt->old_vers_heap = NULL; - + return(prebuilt); } @@ -308,6 +312,19 @@ row_prebuilt_free( { ulint i; + if (prebuilt->magic_n != ROW_PREBUILT_ALLOCATED) { + fprintf(stderr, + "InnoDB: Error: trying to free a corrupt\n" + "InnoDB: table handle. Magic n %lu, table name %s\n", + prebuilt->magic_n, prebuilt->table->name); + + mem_analyze_corruption((byte*)prebuilt); + + ut_a(0); + } + + prebuilt->magic_n = ROW_PREBUILT_FREED; + btr_pcur_free_for_mysql(prebuilt->pcur); btr_pcur_free_for_mysql(prebuilt->clust_pcur); @@ -341,6 +358,8 @@ row_prebuilt_free( } } + dict_table_decrement_handle_count(prebuilt->table); + mem_heap_free(prebuilt->heap); } @@ -356,6 +375,28 @@ row_update_prebuilt_trx( handle */ trx_t* trx) /* in: transaction handle */ { + if (prebuilt->magic_n != ROW_PREBUILT_ALLOCATED) { + fprintf(stderr, + "InnoDB: Error: trying to free a corrupt\n" + "InnoDB: table handle. Magic n %lu, table name %s\n", + prebuilt->magic_n, prebuilt->table->name); + + mem_analyze_corruption((byte*)prebuilt); + + ut_a(0); + } + + if (prebuilt->magic_n != ROW_PREBUILT_ALLOCATED) { + fprintf(stderr, + "InnoDB: Error: trying to use a corrupt\n" + "InnoDB: table handle. Magic n %lu, table name %s\n", + prebuilt->magic_n, prebuilt->table->name); + + mem_analyze_corruption((byte*)prebuilt); + + ut_a(0); + } + prebuilt->trx = trx; if (prebuilt->ins_graph) { @@ -563,6 +604,17 @@ row_insert_for_mysql( ut_ad(trx); ut_ad(trx->mysql_thread_id == os_thread_get_curr_id()); + if (prebuilt->magic_n != ROW_PREBUILT_ALLOCATED) { + fprintf(stderr, + "InnoDB: Error: trying to free a corrupt\n" + "InnoDB: table handle. Magic n %lu, table name %s\n", + prebuilt->magic_n, prebuilt->table->name); + + mem_analyze_corruption((byte*)prebuilt); + + ut_a(0); + } + if (srv_created_new_raw || srv_force_recovery) { fprintf(stderr, "InnoDB: A new raw disk partition was initialized or\n" @@ -748,6 +800,17 @@ row_update_for_mysql( ut_ad(trx->mysql_thread_id == os_thread_get_curr_id()); UT_NOT_USED(mysql_rec); + if (prebuilt->magic_n != ROW_PREBUILT_ALLOCATED) { + fprintf(stderr, + "InnoDB: Error: trying to free a corrupt\n" + "InnoDB: table handle. Magic n %lu, table name %s\n", + prebuilt->magic_n, prebuilt->table->name); + + mem_analyze_corruption((byte*)prebuilt); + + ut_a(0); + } + if (srv_created_new_raw || srv_force_recovery) { fprintf(stderr, "InnoDB: A new raw disk partition was initialized or\n" @@ -782,38 +845,6 @@ row_update_for_mysql( generated for the table: MySQL does not know anything about the row id used as the clustered index key */ -#ifdef notdefined - /* We have to search for the correct cursor position */ - - ref_len = dict_index_get_n_unique(clust_index); - - heap = mem_heap_create(450); - - row_tuple = dtuple_create(heap, dict_table_get_n_cols(table)); - dict_table_copy_types(row_tuple, table); - - if (prebuilt->ins_upd_rec_buff == NULL) { - prebuilt->ins_upd_rec_buff = mem_heap_alloc(prebuilt->heap, - prebuilt->mysql_row_len); - } - - row_mysql_convert_row_to_innobase(row_tuple, prebuilt, mysql_rec); - - search_tuple = dtuple_create(heap, ref_len); - - row_build_row_ref_from_row(search_tuple, table, row_tuple); - - mtr_start(&mtr); - - btr_pcur_open_with_no_init(clust_index, search_tuple, PAGE_CUR_LE, - BTR_SEARCH_LEAF, node->pcur, 0, &mtr); - - btr_pcur_store_position(node->pcur, &mtr); - - mtr_commit(&mtr); - - mem_heap_free(heap); -#endif savept = trx_savept_take(trx); thr = que_fork_get_first_thr(prebuilt->upd_graph); @@ -922,6 +953,50 @@ row_get_mysql_key_number_for_index( return(i); } +/************************************************************************* +Recovers an orphaned tmp table inside InnoDB by renaming it. In the table +name #sql becomes rsql, and "_recover_innodb_tmp_table" is catenated to +the end of name. table->name should be of the form +"dbname/rsql..._recover_innodb_tmp_table". This renames a table whose +name is "#sql..." */ +static +int +row_mysql_recover_tmp_table( +/*========================*/ + /* out: error code or DB_SUCCESS */ + dict_table_t* table, /* in: table definition */ + trx_t* trx) /* in: transaction handle */ +{ + char* ptr; + char old_name[1000]; + + ut_memcpy(old_name, table->name, ut_strlen(table->name) + 1); + + ptr = old_name; + + for (;;) { + if (ptr >= old_name + ut_strlen(table->name) - 6) { + trx_commit_for_mysql(trx); + + return(DB_ERROR); + } + + if (0 == ut_memcmp(ptr, "/rsql", 5)) { + ptr++; + *ptr = '#'; + + break; + } + + ptr++; + } + + old_name[ut_strlen(table->name) + - ut_strlen("_recover_innodb_tmp_table")] = '\0'; + + return(row_rename_table_for_mysql(old_name, table->name, trx)); +} + /************************************************************************* Does a table creation operation for MySQL. If the name of the created table ends to characters INNODB_MONITOR, then this also starts @@ -976,6 +1051,24 @@ row_create_table_for_mysql( namelen = ut_strlen(table->name); + keywordlen = ut_strlen("_recover_innodb_tmp_table"); + + if (namelen >= keywordlen + && 0 == ut_memcmp(table->name + namelen - keywordlen, + "_recover_innodb_tmp_table", keywordlen)) { + + /* MySQL prevents accessing of tables whose name begins + with #sql, that is temporary tables. If mysqld crashes in + the middle of an ALTER TABLE, we may get an orphaned + #sql-table in the tablespace. We have here a special + mechanism to recover such tables by renaming them to + rsql... */ + + return(row_mysql_recover_tmp_table(table, trx)); + } + + namelen = ut_strlen(table->name); + keywordlen = ut_strlen("innodb_monitor"); if (namelen >= keywordlen @@ -1118,6 +1211,8 @@ row_create_index_for_mysql( ind_node_t* node; mem_heap_t* heap; que_thr_t* thr; + ulint namelen; + ulint keywordlen; ulint err; ut_ad(trx->mysql_thread_id == os_thread_get_curr_id()); @@ -1126,6 +1221,18 @@ row_create_index_for_mysql( trx_start_if_not_started(trx); + namelen = ut_strlen(index->table_name); + + keywordlen = ut_strlen("_recover_innodb_tmp_table"); + + if (namelen >= keywordlen + && 0 == ut_memcmp( + index->table_name + namelen - keywordlen, + "_recover_innodb_tmp_table", keywordlen)) { + + return(DB_SUCCESS); + } + /* Serialize data dictionary operations with dictionary mutex: no deadlocks can occur then in these operations */ @@ -1189,6 +1296,8 @@ row_table_add_foreign_constraints( char* name) /* in: table full name in the normalized form database_name/table_name */ { + ulint namelen; + ulint keywordlen; ulint err; ut_a(sql_string); @@ -1197,6 +1306,18 @@ row_table_add_foreign_constraints( trx_start_if_not_started(trx); + namelen = ut_strlen(name); + + keywordlen = ut_strlen("_recover_innodb_tmp_table"); + + if (namelen >= keywordlen + && 0 == ut_memcmp( + name + namelen - keywordlen, + "_recover_innodb_tmp_table", keywordlen)) { + + return(DB_SUCCESS); + } + /* Serialize data dictionary operations with dictionary mutex: no deadlocks can occur then in these operations */ @@ -1251,6 +1372,7 @@ row_drop_table_for_mysql( ulint len; ulint namelen; ulint keywordlen; + ulint rounds = 0; char buf[10000]; ut_ad(trx->mysql_thread_id == os_thread_get_curr_id()); @@ -1427,11 +1549,38 @@ row_drop_table_for_mysql( /* Remove any locks there are on the table or its records */ lock_reset_all_on_table(table); +loop: + if (table->n_mysql_handles_opened > 0) { + rw_lock_s_unlock(&(purge_sys->purge_is_running)); - /* TODO: check that MySQL prevents users from accessing the table - after this function row_drop_table_for_mysql has been called: - otherwise anyone with an open handle to the table could, for example, - come to read the table! Monty said that it prevents. */ + rw_lock_x_unlock(&(dict_foreign_key_check_lock)); + + mutex_exit(&(dict_sys->mutex)); + + if (rounds > 60) { + fprintf(stderr, + "InnoDB: waiting for queries to table %s to end before dropping it\n", + name); + } + + os_thread_sleep(1000000); + + mutex_enter(&(dict_sys->mutex)); + + rw_lock_x_lock(&(dict_foreign_key_check_lock)); + + rw_lock_s_lock(&(purge_sys->purge_is_running)); + + rounds++; + + if (rounds > 120) { + fprintf(stderr, +"InnoDB: Warning: queries to table %s have not ended but we continue anyway\n", + name); + } else { + goto loop; + } + } trx->dict_operation = TRUE; trx->table_id = table->id; diff --git a/innobase/row/row0sel.c b/innobase/row/row0sel.c index 2cccc217621..663a544faac 100644 --- a/innobase/row/row0sel.c +++ b/innobase/row/row0sel.c @@ -2492,6 +2492,17 @@ row_search_for_mysql( ut_ad(sync_thread_levels_empty_gen(FALSE)); + if (prebuilt->magic_n != ROW_PREBUILT_ALLOCATED) { + fprintf(stderr, + "InnoDB: Error: trying to free a corrupt\n" + "InnoDB: table handle. Magic n %lu, table name %s\n", + prebuilt->magic_n, prebuilt->table->name); + + mem_analyze_corruption((byte*)prebuilt); + + ut_a(0); + } + /* printf("Match mode %lu\n search tuple ", match_mode); dtuple_print(search_tuple); diff --git a/innobase/srv/srv0srv.c b/innobase/srv/srv0srv.c index d77db335366..d75783fe425 100644 --- a/innobase/srv/srv0srv.c +++ b/innobase/srv/srv0srv.c @@ -50,6 +50,9 @@ Created 10/8/1995 Heikki Tuuri #include "dict0load.h" #include "srv0start.h" +/* Buffer which can be used in printing fatal error messages */ +char srv_fatal_errbuf[5000]; + /* The following counter is incremented whenever there is some user activity in the server */ ulint srv_activity_count = 0; @@ -132,6 +135,9 @@ lint srv_conc_n_threads = 0; /* number of OS threads currently thread increments this, but a thread waiting for a lock decrements this temporarily */ +ulint srv_conc_n_waiting_threads = 0; /* number of OS threads waiting in the + FIFO for a permission to enter InnoDB + */ typedef struct srv_conc_slot_struct srv_conc_slot_t; struct srv_conc_slot_struct{ @@ -152,6 +158,11 @@ UT_LIST_BASE_NODE_T(srv_conc_slot_t) srv_conc_queue; /* queue of threads waiting to get in */ srv_conc_slot_t srv_conc_slots[OS_THREAD_MAX_N]; /* array of wait slots */ + +/* Number of times a thread is allowed to enter InnoDB within the same +SQL query after it has once got the ticket at srv_conc_enter_innodb */ +#define SRV_FREE_TICKETS_TO_ENTER 500 + /*-----------------------*/ /* If the following is set TRUE then we do not run purge and insert buffer merge to completion before shutdown */ @@ -1627,6 +1638,8 @@ srv_general_init(void) thr_local_init(); } +/*======================= InnoDB Server FIFO queue =======================*/ + /************************************************************************* Puts an OS thread to wait if there are too many concurrent threads (>= srv_thread_concurrency) inside InnoDB. The threads wait in a FIFO queue. */ @@ -1640,11 +1653,29 @@ srv_conc_enter_innodb( srv_conc_slot_t* slot; ulint i; + if (srv_thread_concurrency >= 500) { + /* Disable the concurrency check */ + + return; + } + + /* If trx has 'free tickets' to enter the engine left, then use one + such ticket */ + + if (trx->n_tickets_to_enter_innodb > 0) { + trx->n_tickets_to_enter_innodb--; + + return; + } + os_fast_mutex_lock(&srv_conc_mutex); if (srv_conc_n_threads < (lint)srv_thread_concurrency) { - srv_conc_n_threads++; + srv_conc_n_threads++; + trx->declared_to_be_inside_innodb = TRUE; + trx->n_tickets_to_enter_innodb = SRV_FREE_TICKETS_TO_ENTER; + os_fast_mutex_unlock(&srv_conc_mutex); return; @@ -1665,6 +1696,8 @@ srv_conc_enter_innodb( thread enter */ srv_conc_n_threads++; + trx->declared_to_be_inside_innodb = TRUE; + trx->n_tickets_to_enter_innodb = 0; os_fast_mutex_unlock(&srv_conc_mutex); @@ -1684,6 +1717,8 @@ srv_conc_enter_innodb( os_event_reset(slot->event); + srv_conc_n_waiting_threads++; + os_fast_mutex_unlock(&srv_conc_mutex); /* Go to wait for the event; when a thread leaves InnoDB it will @@ -1693,6 +1728,8 @@ srv_conc_enter_innodb( os_fast_mutex_lock(&srv_conc_mutex); + srv_conc_n_waiting_threads--; + /* NOTE that the thread which released this thread already incremented the thread counter on behalf of this thread */ @@ -1700,6 +1737,9 @@ srv_conc_enter_innodb( UT_LIST_REMOVE(srv_conc_queue, srv_conc_queue, slot); + trx->declared_to_be_inside_innodb = TRUE; + trx->n_tickets_to_enter_innodb = SRV_FREE_TICKETS_TO_ENTER; + os_fast_mutex_unlock(&srv_conc_mutex); } @@ -1708,29 +1748,52 @@ This lets a thread enter InnoDB regardless of the number of threads inside InnoDB. This must be called when a thread ends a lock wait. */ void -srv_conc_force_enter_innodb(void) -/*=============================*/ +srv_conc_force_enter_innodb( +/*========================*/ + trx_t* trx) /* in: transaction object associated with the + thread */ { + if (srv_thread_concurrency >= 500) { + + return; + } + os_fast_mutex_lock(&srv_conc_mutex); srv_conc_n_threads++; + trx->declared_to_be_inside_innodb = TRUE; + trx->n_tickets_to_enter_innodb = 0; os_fast_mutex_unlock(&srv_conc_mutex); } /************************************************************************* -This must be called when a thread exits InnoDB. This must also be called -when a thread goes to wait for a lock. */ +This must be called when a thread exits InnoDB in a lock wait or at the +end of an SQL statement. */ void -srv_conc_exit_innodb(void) -/*======================*/ +srv_conc_force_exit_innodb( +/*=======================*/ + trx_t* trx) /* in: transaction object associated with the + thread */ { srv_conc_slot_t* slot = NULL; + if (srv_thread_concurrency >= 500) { + + return; + } + + if (trx->declared_to_be_inside_innodb == FALSE) { + + return; + } + os_fast_mutex_lock(&srv_conc_mutex); srv_conc_n_threads--; + trx->declared_to_be_inside_innodb = FALSE; + trx->n_tickets_to_enter_innodb = 0; if (srv_conc_n_threads < (lint)srv_thread_concurrency) { /* Look for a slot where a thread is waiting and no other @@ -1759,6 +1822,38 @@ srv_conc_exit_innodb(void) } } +/************************************************************************* +This must be called when a thread exits InnoDB. */ + +void +srv_conc_exit_innodb( +/*=================*/ + trx_t* trx) /* in: transaction object associated with the + thread */ +{ + srv_conc_slot_t* slot = NULL; + + if (srv_thread_concurrency >= 500) { + + return; + } + + if (trx->n_tickets_to_enter_innodb > 0) { + /* We will pretend the thread is still inside InnoDB though it + now leaves the InnoDB engine. In this way we save + a lot of semaphore operations. srv_conc_force_exit_innodb is + used to declare the thread definitely outside InnoDB. It + should be called when there is a lock wait or an SQL statement + ends. */ + + return; + } + + srv_conc_force_exit_innodb(trx); +} + +/*========================================================================*/ + /************************************************************************* Normalizes init parameter values to use units we use inside InnoDB. */ static @@ -1905,7 +2000,7 @@ srv_suspend_mysql_thread( other thread holding a lock which this thread waits for must be allowed to enter, sooner or later */ - srv_conc_exit_innodb(); + srv_conc_force_exit_innodb(thr_get_trx(thr)); /* Wait for the release */ @@ -1913,7 +2008,7 @@ srv_suspend_mysql_thread( /* Return back inside InnoDB */ - srv_conc_force_enter_innodb(); + srv_conc_force_enter_innodb(thr_get_trx(thr)); mutex_enter(&kernel_mutex); @@ -2052,8 +2147,9 @@ loop: "ROW OPERATIONS\n" "--------------\n"); printf( - "%ld queries inside InnoDB; main thread: %s\n", - srv_conc_n_threads, srv_main_thread_op_info); + "%ld queries inside InnoDB, %ld queries in queue; main thread: %s\n", + srv_conc_n_threads, srv_conc_n_waiting_threads, + srv_main_thread_op_info); printf( "Number of rows inserted %lu, updated %lu, deleted %lu, read %lu\n", srv_n_rows_inserted, @@ -2315,6 +2411,12 @@ loop: srv_main_thread_op_info = "sleeping"; os_thread_sleep(1000000); + /* ALTER TABLE in MySQL requires on Unix that the table handler + can drop tables lazily after there no longer are SELECT + queries to them. */ + +/* row_drop_tables_for_mysql_in_background(); */ + if (srv_force_recovery >= SRV_FORCE_NO_BACKGROUND) { goto suspend_thread; diff --git a/innobase/sync/sync0arr.c b/innobase/sync/sync0arr.c index c41754316d0..7788b104120 100644 --- a/innobase/sync/sync0arr.c +++ b/innobase/sync/sync0arr.c @@ -913,6 +913,17 @@ sync_array_print_long_waits(void) noticed = TRUE; } + + if (cell->wait_object != NULL + && difftime(time(NULL), cell->reservation_time) > 420) { + + fprintf(stderr, +"InnoDB: Error: semaphore wait has lasted > 420 seconds\n" +"InnoDB: We intentionally crash the server, because it appears to be hung.\n" + ); + + ut_a(0); + } } if (noticed) { diff --git a/innobase/trx/trx0sys.c b/innobase/trx/trx0sys.c index e79e4594637..b29ffb4b3bf 100644 --- a/innobase/trx/trx0sys.c +++ b/innobase/trx/trx0sys.c @@ -389,6 +389,115 @@ trx_sys_flush_max_trx_id(void) mtr_commit(&mtr); } +/********************************************************************* +Updates the offset information about the end of the MySQL binlog entry +which corresponds to the transaction just being committed. */ + +void +trx_sys_update_mysql_binlog_offset( +/*===============================*/ + trx_t* trx, /* in: transaction being committed */ + mtr_t* mtr) /* in: mtr */ +{ + trx_sysf_t* sys_header; + char namebuf[TRX_SYS_MYSQL_LOG_NAME_LEN]; + + ut_ad(mutex_own(&kernel_mutex)); + ut_ad(trx->mysql_log_file_name); + + memset(namebuf, ' ', TRX_SYS_MYSQL_LOG_NAME_LEN - 1); + namebuf[TRX_SYS_MYSQL_LOG_NAME_LEN - 1] = '\0'; + + /* Copy the whole MySQL log file name to the buffer, or only the + last characters, if it does not fit */ + + if (ut_strlen(trx->mysql_log_file_name) + > TRX_SYS_MYSQL_LOG_NAME_LEN - 1) { + ut_memcpy(namebuf, trx->mysql_log_file_name + + ut_strlen(trx->mysql_log_file_name) + - (TRX_SYS_MYSQL_LOG_NAME_LEN - 1), + TRX_SYS_MYSQL_LOG_NAME_LEN - 1); + } else { + ut_memcpy(namebuf, trx->mysql_log_file_name, + 1 + ut_strlen(trx->mysql_log_file_name)); + } + + namebuf[TRX_SYS_MYSQL_LOG_NAME_LEN - 1] = '\0'; + + sys_header = trx_sysf_get(mtr); + + if (mach_read_from_4(sys_header + TRX_SYS_MYSQL_LOG_INFO + + TRX_SYS_MYSQL_LOG_MAGIC_N_FLD) + != TRX_SYS_MYSQL_LOG_MAGIC_N) { + + mlog_write_ulint(sys_header + TRX_SYS_MYSQL_LOG_INFO + + TRX_SYS_MYSQL_LOG_MAGIC_N_FLD, + TRX_SYS_MYSQL_LOG_MAGIC_N, + MLOG_4BYTES, mtr); + } + + if (0 != ut_memcmp(sys_header + TRX_SYS_MYSQL_LOG_INFO + + TRX_SYS_MYSQL_LOG_NAME, + namebuf, TRX_SYS_MYSQL_LOG_NAME_LEN)) { + + mlog_write_string(sys_header + TRX_SYS_MYSQL_LOG_INFO + + TRX_SYS_MYSQL_LOG_NAME, + namebuf, TRX_SYS_MYSQL_LOG_NAME_LEN, mtr); + } + + if (mach_read_from_4(sys_header + TRX_SYS_MYSQL_LOG_INFO + + TRX_SYS_MYSQL_LOG_OFFSET_HIGH) > 0 + || (trx->mysql_log_offset >> 32) > 0) { + + mlog_write_ulint(sys_header + TRX_SYS_MYSQL_LOG_INFO + + TRX_SYS_MYSQL_LOG_OFFSET_HIGH, + (ulint)(trx->mysql_log_offset >> 32), + MLOG_4BYTES, mtr); + } + + mlog_write_ulint(sys_header + TRX_SYS_MYSQL_LOG_INFO + + TRX_SYS_MYSQL_LOG_OFFSET_LOW, + (ulint)(trx->mysql_log_offset & 0xFFFFFFFF), + MLOG_4BYTES, mtr); + + trx->mysql_log_file_name = NULL; +} + +/********************************************************************* +Prints to stderr the MySQL binlog offset info in the trx system header if +the magic number shows it valid. */ + +void +trx_sys_print_mysql_binlog_offset(void) +/*===================================*/ +{ + trx_sysf_t* sys_header; + mtr_t mtr; + + mtr_start(&mtr); + + sys_header = trx_sysf_get(&mtr); + + if (mach_read_from_4(sys_header + TRX_SYS_MYSQL_LOG_INFO + + TRX_SYS_MYSQL_LOG_MAGIC_N_FLD) + != TRX_SYS_MYSQL_LOG_MAGIC_N) { + + mtr_commit(&mtr); + + return; + } + + fprintf(stderr, + "InnoDB: Last MySQL binlog file offset %lu %lu, file name %s\n", + mach_read_from_4(sys_header + TRX_SYS_MYSQL_LOG_INFO + + TRX_SYS_MYSQL_LOG_OFFSET_HIGH), + mach_read_from_4(sys_header + TRX_SYS_MYSQL_LOG_INFO + + TRX_SYS_MYSQL_LOG_OFFSET_LOW), + sys_header + TRX_SYS_MYSQL_LOG_INFO + TRX_SYS_MYSQL_LOG_NAME); + + mtr_commit(&mtr); +} + /******************************************************************** Looks for a free slot for a rollback segment in the trx system file copy. */ @@ -519,7 +628,7 @@ trx_sys_init_at_db_start(void) "InnoDB: %lu uncommitted transaction(s) which must be rolled back\n", UT_LIST_GET_LEN(trx_sys->trx_list)); - fprintf(stderr, "Trx id counter is %lu %lu\n", + fprintf(stderr, "InnoDB: Trx id counter is %lu %lu\n", ut_dulint_get_high(trx_sys->max_trx_id), ut_dulint_get_low(trx_sys->max_trx_id)); } diff --git a/innobase/trx/trx0trx.c b/innobase/trx/trx0trx.c index 18c80819245..43cca5b62b3 100644 --- a/innobase/trx/trx0trx.c +++ b/innobase/trx/trx0trx.c @@ -76,6 +76,9 @@ trx_create( trx->n_mysql_tables_in_use = 0; trx->mysql_n_tables_locked = 0; + trx->mysql_log_file_name = NULL; + trx->mysql_log_offset = 0; + trx->ignore_duplicates_in_insert = FALSE; mutex_create(&(trx->undo_mutex)); @@ -111,6 +114,9 @@ trx_create( trx->has_search_latch = FALSE; trx->search_latch_timeout = BTR_SEA_TIMEOUT; + trx->declared_to_be_inside_innodb = FALSE; + trx->n_tickets_to_enter_innodb = 0; + trx->auto_inc_lock = NULL; trx->read_view_heap = mem_heap_create(256); @@ -568,6 +574,13 @@ trx_commit_off_kernel( mutex_exit(&(rseg->mutex)); + /* Update the latest MySQL binlog name and offset info + in trx sys header if MySQL binlogging is on */ + + if (trx->mysql_log_file_name) { + trx_sys_update_mysql_binlog_offset(trx, &mtr); + } + /* If we did not take the shortcut, the following call commits the mini-transaction, making the whole transaction committed in the file-based world at this log sequence number; diff --git a/innobase/trx/trx0undo.c b/innobase/trx/trx0undo.c index 8b83163bfc2..b5341871228 100644 --- a/innobase/trx/trx0undo.c +++ b/innobase/trx/trx0undo.c @@ -1310,9 +1310,10 @@ trx_undo_mem_init_for_reuse( { ut_ad(mutex_own(&((undo->rseg)->mutex))); - if (undo->id >= TRX_RSEG_N_SLOTS) { - fprintf(stderr, - "InnoDB: Error: undo->id is %lu\n", undo->id); + if (undo->id >= TRX_RSEG_N_SLOTS) { + fprintf(stderr, "InnoDB: Error: undo->id is %lu\n", undo->id); + + mem_analyze_corruption((byte*)undo); ut_a(0); } @@ -1399,7 +1400,7 @@ trx_undo_create( /************************************************************************ Reuses a cached undo log. */ -UNIV_INLINE +static trx_undo_t* trx_undo_reuse_cached( /*==================*/ @@ -1442,6 +1443,12 @@ trx_undo_reuse_cached( ut_ad(undo->size == 1); ut_ad(undo->hdr_page_no == undo->top_page_no); + if (undo->id >= TRX_RSEG_N_SLOTS) { + fprintf(stderr, "InnoDB: Error: undo->id is %lu\n", undo->id); + mem_analyze_corruption((byte*)undo); + ut_a(0); + } + undo_page = trx_undo_page_get(undo->space, undo->hdr_page_no, mtr); if (type == TRX_UNDO_INSERT) { @@ -1572,8 +1579,8 @@ trx_undo_set_state_at_finish( ut_ad(trx && undo && mtr); if (undo->id >= TRX_RSEG_N_SLOTS) { - fprintf(stderr, - "InnoDB: Error: undo->id is %lu\n", undo->id); + fprintf(stderr, "InnoDB: Error: undo->id is %lu\n", undo->id); + mem_analyze_corruption((byte*)undo); ut_a(0); } diff --git a/sql/ha_innobase.cc b/sql/ha_innobase.cc index 62c721de13f..a923a259ebd 100644 --- a/sql/ha_innobase.cc +++ b/sql/ha_innobase.cc @@ -21,7 +21,7 @@ InnoDB */ - Ask Monty if strings of different languages can exist in the same database. Answer: in near future yes, but not yet. */ - + #ifdef __GNUC__ #pragma implementation // gcc: Class implementation #endif @@ -112,6 +112,36 @@ static void innobase_print_error(const char* db_errpfx, char* buffer); /* General functions */ +/********************************************************************** +Releases possible search latch, auto inc lock, and InnoDB thread FIFO ticket. +These should be released at each SQL statement end. */ +static +void +innobase_release_stat_resources( +/*============================*/ + trx_t* trx) /* in: transaction object */ +{ + if (trx->has_search_latch) { + trx_search_latch_release_if_reserved(trx); + } + + if (trx->auto_inc_lock) { + + /* If we had reserved the auto-inc lock for + some table in this SQL statement, we release it now */ + + srv_conc_enter_innodb(trx); + row_unlock_table_autoinc_for_mysql(trx); + srv_conc_exit_innodb(trx); + } + + if (trx->declared_to_be_inside_innodb) { + /* Release our possible ticket in the FIFO */ + + srv_conc_force_exit_innodb(trx); + } +} + /************************************************************************ Increments innobase_active_counter and every INNOBASE_WAKE_INTERVALth time calls srv_active_wake_master_thread. This function should be used @@ -708,13 +738,13 @@ innobase_commit( trx = check_trx_exists(thd); if (trx_handle != (void*)&innodb_dummy_stmt_trx_handle) { - srv_conc_enter_innodb(trx); trx_commit_for_mysql(trx); - - srv_conc_exit_innodb(); } + /* Release possible statement level resources */ + innobase_release_stat_resources(trx); + trx_mark_sql_stat_end(trx); #ifndef DBUG_OFF @@ -730,6 +760,32 @@ innobase_commit( DBUG_RETURN(error); } +/********************************************************************* +This is called when MySQL writes the binlog entry for the current +transaction. Writes to the InnoDB tablespace info which tells where the +MySQL binlog entry for the current transaction ended. Also commits the +transaction inside InnoDB. */ + +int +innobase_report_binlog_offset_and_commit( +/*=====================================*/ + /* out: 0 or error code */ + THD* thd, /* in: user thread */ + void* trx_handle, /* in: InnoDB trx handle */ + char* log_file_name, /* in: latest binlog file name */ + my_off_t end_offset) /* in: the offset in the binlog file + up to which we wrote */ +{ + trx_t* trx; + + trx = (trx_t*)trx_handle; + + trx->mysql_log_file_name = log_file_name; + trx->mysql_log_offset = (ib_longlong)end_offset; + + return(innobase_commit(thd, trx_handle)); +} + /********************************************************************* Rolls back a transaction in an InnoDB database. */ @@ -757,8 +813,11 @@ innobase_rollback( error = trx_rollback_last_sql_stat_for_mysql(trx); } - srv_conc_exit_innodb(); - + srv_conc_exit_innodb(trx); + + /* Release possible statement level resources */ + innobase_release_stat_resources(trx); + trx_mark_sql_stat_end(trx); DBUG_RETURN(convert_error_code_to_mysql(error)); @@ -1430,6 +1489,8 @@ ha_innobase::write_row( if (last_query_id != user_thd->query_id) { prebuilt->sql_stat_start = TRUE; last_query_id = user_thd->query_id; + + innobase_release_stat_resources(prebuilt->trx); } if (table->next_number_field && record == table->record[0]) { @@ -1480,7 +1541,7 @@ ha_innobase::write_row( srv_conc_enter_innodb(prebuilt->trx); error = row_lock_table_autoinc_for_mysql(prebuilt); - srv_conc_exit_innodb(); + srv_conc_exit_innodb(prebuilt->trx); if (error != DB_SUCCESS) { @@ -1497,7 +1558,7 @@ ha_innobase::write_row( error = row_lock_table_autoinc_for_mysql( prebuilt); if (error != DB_SUCCESS) { - srv_conc_exit_innodb(); + srv_conc_exit_innodb(prebuilt->trx); error = convert_error_code_to_mysql( error); @@ -1506,7 +1567,7 @@ ha_innobase::write_row( } auto_inc = dict_table_autoinc_get(prebuilt->table); - srv_conc_exit_innodb(); + srv_conc_exit_innodb(prebuilt->trx); /* If auto_inc is now != 0 the autoinc counter was already initialized for the table: we can give @@ -1533,7 +1594,7 @@ ha_innobase::write_row( srv_conc_enter_innodb(prebuilt->trx); error = row_lock_table_autoinc_for_mysql(prebuilt); - srv_conc_exit_innodb(); + srv_conc_exit_innodb(prebuilt->trx); if (error != DB_SUCCESS) { @@ -1573,7 +1634,7 @@ ha_innobase::write_row( error = row_insert_for_mysql((byte*) record, prebuilt); - srv_conc_exit_innodb(); + srv_conc_exit_innodb(prebuilt->trx); prebuilt->trx->ignore_duplicates_in_insert = FALSE; @@ -1768,6 +1829,8 @@ ha_innobase::update_row( if (last_query_id != user_thd->query_id) { prebuilt->sql_stat_start = TRUE; last_query_id = user_thd->query_id; + + innobase_release_stat_resources(prebuilt->trx); } if (prebuilt->upd_node) { @@ -1792,7 +1855,7 @@ ha_innobase::update_row( error = row_update_for_mysql((byte*) old_row, prebuilt); - srv_conc_exit_innodb(); + srv_conc_exit_innodb(prebuilt->trx); error = convert_error_code_to_mysql(error); @@ -1821,6 +1884,8 @@ ha_innobase::delete_row( if (last_query_id != user_thd->query_id) { prebuilt->sql_stat_start = TRUE; last_query_id = user_thd->query_id; + + innobase_release_stat_resources(prebuilt->trx); } if (!prebuilt->upd_node) { @@ -1836,7 +1901,7 @@ ha_innobase::delete_row( error = row_update_for_mysql((byte*) record, prebuilt); - srv_conc_exit_innodb(); + srv_conc_exit_innodb(prebuilt->trx); error = convert_error_code_to_mysql(error); @@ -1935,6 +2000,8 @@ ha_innobase::index_read( if (last_query_id != user_thd->query_id) { prebuilt->sql_stat_start = TRUE; last_query_id = user_thd->query_id; + + innobase_release_stat_resources(prebuilt->trx); } index = prebuilt->index; @@ -1980,7 +2047,7 @@ ha_innobase::index_read( ret = row_search_for_mysql((byte*) buf, mode, prebuilt, match_mode, 0); - srv_conc_exit_innodb(); + srv_conc_exit_innodb(prebuilt->trx); if (ret == DB_SUCCESS) { error = 0; @@ -2106,7 +2173,7 @@ ha_innobase::general_fetch( ret = row_search_for_mysql((byte*)buf, 0, prebuilt, match_mode, direction); - srv_conc_exit_innodb(); + srv_conc_exit_innodb(prebuilt->trx); if (ret == DB_SUCCESS) { error = 0; @@ -2406,89 +2473,6 @@ int ha_innobase::reset(void) return(0); } -/********************************************************************** -As MySQL will execute an external lock for every new table it uses when it -starts to process an SQL statement, we can use this function to store the -pointer to the THD in the handle. We will also use this function to communicate -to InnoDB that a new SQL statement has started and that we must store a -savepoint to our transaction handle, so that we are able to roll back -the SQL statement in case of an error. */ - -int -ha_innobase::external_lock( -/*=======================*/ - THD* thd, /* in: handle to the user thread */ - int lock_type) /* in: lock type */ -{ - row_prebuilt_t* prebuilt = (row_prebuilt_t*) innobase_prebuilt; - int error = 0; - trx_t* trx; - - DBUG_ENTER("ha_innobase::external_lock"); - - update_thd(thd); - - trx = prebuilt->trx; - - prebuilt->sql_stat_start = TRUE; - prebuilt->in_update_remember_pos = TRUE; - - prebuilt->read_just_key = 0; - - if (lock_type == F_WRLCK) { - - /* If this is a SELECT, then it is in UPDATE TABLE ... - or SELECT ... FOR UPDATE */ - prebuilt->select_lock_type = LOCK_X; - } - - if (lock_type != F_UNLCK) { - if (trx->n_mysql_tables_in_use == 0) { - trx_mark_sql_stat_end(trx); - } - - thd->transaction.all.innodb_active_trans = 1; - trx->n_mysql_tables_in_use++; - - if (prebuilt->select_lock_type != LOCK_NONE) { - - trx->mysql_n_tables_locked++; - } - } else { - trx->n_mysql_tables_in_use--; - auto_inc_counter_for_this_stat = 0; - - if (trx->n_mysql_tables_in_use == 0) { - - trx->mysql_n_tables_locked = 0; - - if (trx->has_search_latch) { - - trx_search_latch_release_if_reserved(trx); - } - - if (trx->auto_inc_lock) { - - /* If we had reserved the auto-inc lock for - some table in this SQL statement, we release - it now */ - - srv_conc_enter_innodb(trx); - row_unlock_table_autoinc_for_mysql(trx); - srv_conc_exit_innodb(); - } - - if (!(thd->options - & (OPTION_NOT_AUTO_COMMIT | OPTION_BEGIN))) { - - innobase_commit(thd, trx); - } - } - } - - DBUG_RETURN(error); -} - /********************************************************************* Creates a table definition to an InnoDB database. */ static @@ -2664,7 +2648,7 @@ ha_innobase::create( /* Create the table definition in InnoDB */ - if ((error = create_table_def(trx, form, norm_name))) { + if (error = create_table_def(trx, form, norm_name)) { trx_commit_for_mysql(trx); @@ -3244,6 +3228,78 @@ ha_innobase::update_table_comment( return(str); } +/********************************************************************** +As MySQL will execute an external lock for every new table it uses when it +starts to process an SQL statement, we can use this function to store the +pointer to the THD in the handle. We will also use this function to communicate +to InnoDB that a new SQL statement has started and that we must store a +savepoint to our transaction handle, so that we are able to roll back +the SQL statement in case of an error. */ + +int +ha_innobase::external_lock( +/*=======================*/ + THD* thd, /* in: handle to the user thread */ + int lock_type) /* in: lock type */ +{ + row_prebuilt_t* prebuilt = (row_prebuilt_t*) innobase_prebuilt; + int error = 0; + trx_t* trx; + + DBUG_ENTER("ha_innobase::external_lock"); + + update_thd(thd); + + trx = prebuilt->trx; + + prebuilt->sql_stat_start = TRUE; + prebuilt->in_update_remember_pos = TRUE; + + prebuilt->read_just_key = 0; + + if (lock_type == F_WRLCK) { + + /* If this is a SELECT, then it is in UPDATE TABLE ... + or SELECT ... FOR UPDATE */ + prebuilt->select_lock_type = LOCK_X; + } + + if (lock_type != F_UNLCK) { + if (trx->n_mysql_tables_in_use == 0) { + trx_mark_sql_stat_end(trx); + } + + thd->transaction.all.innodb_active_trans = 1; + trx->n_mysql_tables_in_use++; + + if (prebuilt->select_lock_type != LOCK_NONE) { + + trx->mysql_n_tables_locked++; + } + } else { + trx->n_mysql_tables_in_use--; + auto_inc_counter_for_this_stat = 0; + + if (trx->n_mysql_tables_in_use == 0) { + + trx->mysql_n_tables_locked = 0; + + /* Here we release the search latch, auto_inc_lock, + and InnoDB thread FIFO ticket if they were reserved. */ + + innobase_release_stat_resources(trx); + + if (!(thd->options + & (OPTION_NOT_AUTO_COMMIT | OPTION_BEGIN))) { + + innobase_commit(thd, trx); + } + } + } + + DBUG_RETURN(error); +} + /**************************************************************************** Handling the shared INNOBASE_SHARE structure that is needed to provide table locking. diff --git a/sql/ha_innobase.h b/sql/ha_innobase.h index 3f4bd5144eb..54449a1ef2b 100644 --- a/sql/ha_innobase.h +++ b/sql/ha_innobase.h @@ -185,6 +185,11 @@ bool innobase_flush_logs(void); uint innobase_get_free_space(void); int innobase_commit(THD *thd, void* trx_handle); +int innobase_report_binlog_offset_and_commit( + THD* thd, + void* trx_handle, + char* log_file_name, + my_off_t end_offset); int innobase_rollback(THD *thd, void* trx_handle); int innobase_close_connection(THD *thd); int innobase_drop_database(char *path); diff --git a/sql/handler.cc b/sql/handler.cc index 0b493219674..c1e28b0058e 100644 --- a/sql/handler.cc +++ b/sql/handler.cc @@ -257,6 +257,44 @@ int ha_autocommit_or_rollback(THD *thd, int error) DBUG_RETURN(error); } +/* This function is called when MySQL writes the log segment of a transaction +to the binlog. It is called when the LOCK_log mutex is reserved. Here we +communicate to transactional table handlers whta binlog position corresponds +to the current transaction. The handler can store it and in recovery print +to the user, so that the user knows from what position in the binlog to +start possible roll-forward, for example, if the crashed server was a slave +in replication. This function also calls the commit of the table handler, +because the order of trasnactions in the log of the table handler must be +the same as in the binlog. */ + +int ha_report_binlog_offset_and_commit( + THD *thd, /* in: user thread */ + char *log_file_name, /* in: latest binlog file name */ + my_off_t end_offset) /* in: the offset in the binlog file + up to which we wrote */ +{ + THD_TRANS *trans; + int error = 0; + + trans = &thd->transaction.all; + +#ifdef HAVE_INNOBASE_DB + if (trans->innobase_tid) + { + if ((error=innobase_report_binlog_offset_and_commit(thd, + trans->innobase_tid, + log_file_name, + end_offset))) + { + my_error(ER_ERROR_DURING_COMMIT, MYF(0), error); + error=1; + } + trans->innodb_active_trans=0; + } +#endif + + return error; +} int ha_commit_trans(THD *thd, THD_TRANS* trans) { @@ -269,7 +307,7 @@ int ha_commit_trans(THD *thd, THD_TRANS* trans) if (trans == &thd->transaction.all && mysql_bin_log.is_open() && my_b_tell(&thd->transaction.trans_log)) { - mysql_bin_log.write(&thd->transaction.trans_log); + mysql_bin_log.write(thd, &thd->transaction.trans_log); reinit_io_cache(&thd->transaction.trans_log, WRITE_CACHE, (my_off_t) 0, 0, 1); thd->transaction.trans_log.end_of_file= max_binlog_cache_size; diff --git a/sql/handler.h b/sql/handler.h index 560420a480d..e4cac60ed67 100644 --- a/sql/handler.h +++ b/sql/handler.h @@ -353,6 +353,10 @@ int ha_delete_table(enum db_type db_type, const char *path); void ha_drop_database(char* path); void ha_key_cache(void); int ha_start_stmt(THD *thd); +int ha_report_binlog_offset_and_commit( + THD *thd, + char *log_file_name, + my_off_t end_offset); int ha_commit_trans(THD *thd, THD_TRANS *trans); int ha_rollback_trans(THD *thd, THD_TRANS *trans); int ha_autocommit_or_rollback(THD *thd, int error); diff --git a/sql/log.cc b/sql/log.cc index 1b236d342f5..bc2b19d921f 100644 --- a/sql/log.cc +++ b/sql/log.cc @@ -658,6 +658,7 @@ bool MYSQL_LOG::write(Query_log_event* event_info) VOID(pthread_mutex_unlock(&LOCK_log)); return 0; } + error=1; if (thd->last_insert_id_used) @@ -694,6 +695,20 @@ bool MYSQL_LOG::write(Query_log_event* event_info) goto err; error=0; should_rotate = (file == &log_file && my_b_tell(file) >= max_binlog_size); + + /* Tell for transactional table handlers up to which position in the + binlog file we wrote. The table handler can store this info, and + after crash recovery print for the user the offset of the last + transactions which were recovered. Actually, we must also call + the table handler commit here, protected by the LOCK_log mutex, + because otherwise the transactions may end up in a different order + in the table handler log! */ + + if (file == &log_file) { + error = ha_report_binlog_offset_and_commit(thd, log_file_name, + file->pos_in_file); + } + err: if (error) { @@ -718,7 +733,7 @@ err: 'cache' needs to be reinitialized after this functions returns. */ -bool MYSQL_LOG::write(IO_CACHE *cache) +bool MYSQL_LOG::write(THD *thd, IO_CACHE *cache) { VOID(pthread_mutex_lock(&LOCK_log)); bool error=1; @@ -754,6 +769,10 @@ bool MYSQL_LOG::write(IO_CACHE *cache) sql_print_error(ER(ER_ERROR_ON_READ), cache->file_name, errno); goto err; } + error = ha_report_binlog_offset_and_commit(thd, log_file_name, + log_file.pos_in_file); + if (error) + goto err; } error=0; diff --git a/sql/sql_class.h b/sql/sql_class.h index 3d218a06d0c..d9497907926 100644 --- a/sql/sql_class.h +++ b/sql/sql_class.h @@ -82,7 +82,7 @@ public: time_t query_start=0); bool write(Query_log_event* event_info); // binary log write bool write(Load_log_event* event_info); - bool write(IO_CACHE *cache); + bool write(THD *thd, IO_CACHE *cache); int generate_new_name(char *new_name,const char *old_name); void make_log_name(char* buf, const char* log_ident); bool is_active(const char* log_file_name);