/********************************************************************//** Insert a block in the flush_rbt and returns a pointer to its predecessor or NULL if no predecessor. The ordering is maintained on the basis of the <oldest_modification, space, offset> key. @return pointer to the predecessor or NULL if no predecessor. */ static buf_page_t* buf_flush_insert_in_flush_rbt( /*==========================*/ buf_page_t* bpage) /*!< in: bpage to be inserted. */ { buf_page_t* prev = NULL; const ib_rbt_node_t* c_node; const ib_rbt_node_t* p_node; ut_ad(buf_pool_mutex_own()); /* Insert this buffer into the rbt. */ c_node = rbt_insert(buf_pool->flush_rbt, &bpage, &bpage); ut_a(c_node != NULL); /* Get the predecessor. */ p_node = rbt_prev(buf_pool->flush_rbt, c_node); if (p_node != NULL) { prev = *rbt_value(buf_page_t*, p_node); ut_a(prev != NULL); }
byte* mach_dulint_parse_compressed( /*=========================*/ /* out: pointer to end of the stored field, NULL if not complete */ byte* ptr, /* in: pointer to buffer from where to read */ byte* end_ptr,/* in: pointer to end of the buffer */ dulint* val) /* out: read value */ { ulint high; ulint low; ulint size; ut_ad(ptr && end_ptr && val); if (end_ptr < ptr + 5) { return(NULL); } high = mach_read_compressed(ptr); size = mach_get_compressed_size(high); ptr += size; if (end_ptr < ptr + 4) { return(NULL); } low = mach_read_from_4(ptr); *val = ut_dulint_create(high, low); return(ptr + 4); }
/*********************************************************************//** Fetches the clustered index record for a secondary index record. The latches on the secondary index record are preserved. @return record or NULL, if no record found */ UNIV_INTERN rec_t* row_get_clust_rec( /*==============*/ ulint mode, /*!< in: BTR_MODIFY_LEAF, ... */ const rec_t* rec, /*!< in: record in a secondary index */ dict_index_t* index, /*!< in: secondary index */ dict_index_t** clust_index,/*!< out: clustered index */ mtr_t* mtr) /*!< in: mtr */ { mem_heap_t* heap; dtuple_t* ref; dict_table_t* table; btr_pcur_t pcur; ibool found; rec_t* clust_rec; ut_ad(!dict_index_is_clust(index)); table = index->table; heap = mem_heap_create(256); ref = row_build_row_ref(ROW_COPY_POINTERS, index, rec, heap); found = row_search_on_row_ref(&pcur, mode, table, ref, mtr); clust_rec = found ? btr_pcur_get_rec(&pcur) : NULL; mem_heap_free(heap); btr_pcur_close(&pcur); *clust_index = dict_table_get_first_index(table); return(clust_rec); }
void row_upd_index_replace_new_col_vals( /*===============================*/ dtuple_t* entry, /* in/out: index entry where replaced */ dict_index_t* index, /* in: index; NOTE that may also be a non-clustered index */ upd_t* update) /* in: update vector */ { upd_field_t* upd_field; dfield_t* dfield; dfield_t* new_val; ulint field_no; dict_index_t* clust_index; ulint i; ut_ad(index); clust_index = dict_table_get_first_index(index->table); dtuple_set_info_bits(entry, update->info_bits); for (i = 0; i < upd_get_n_fields(update); i++) { upd_field = upd_get_nth_field(update, i); field_no = dict_index_get_nth_col_pos(index, dict_index_get_nth_col_no(clust_index, upd_field->field_no)); if (field_no != ULINT_UNDEFINED) { dfield = dtuple_get_nth_field(entry, field_no); new_val = &(upd_field->new_val); dfield_set_data(dfield, new_val->data, new_val->len); } } }
byte* mlog_parse_initial_log_record( /*==========================*/ /* out: parsed record end, NULL if not a complete record */ byte* ptr, /* in: buffer */ byte* end_ptr,/* in: buffer end */ byte* type, /* out: log record type: MLOG_1BYTE, ... */ ulint* space, /* out: space id */ ulint* page_no)/* out: page number */ { if (end_ptr < ptr + 1) { return(NULL); } *type = (byte)((ulint)*ptr & ~MLOG_SINGLE_REC_FLAG); ut_ad(*type <= MLOG_BIGGEST_TYPE); ptr++; if (end_ptr < ptr + 2) { return(NULL); } ptr = mach_parse_compressed(ptr, end_ptr, space); if (ptr == NULL) { return(NULL); } ptr = mach_parse_compressed(ptr, end_ptr, page_no); return(ptr); }
void mlog_write_dulint( /*==============*/ byte* ptr, /* in: pointer where to write */ dulint val, /* in: value to write */ mtr_t* mtr) /* in: mini-transaction handle */ { byte* log_ptr; if (ptr < buf_pool->frame_zero || ptr >= buf_pool->high_end) { fprintf(stderr, "InnoDB: Error: trying to write to a stray memory location %p\n", ptr); ut_error; } ut_ad(ptr && mtr); mach_write_to_8(ptr, val); log_ptr = mlog_open(mtr, 11 + 2 + 9); /* If no logging is requested, we may return now */ if (log_ptr == NULL) { return; } log_ptr = mlog_write_initial_log_record_fast(ptr, MLOG_8BYTES, log_ptr, mtr); mach_write_to_2(log_ptr, ptr - buf_frame_align(ptr)); log_ptr += 2; log_ptr += mach_dulint_write_compressed(log_ptr, val); mlog_close(mtr, log_ptr); }
/************************************************************************ Gets the buddy of an area, if it exists in pool. */ UNIV_INLINE mem_area_t* mem_area_get_buddy( /*===============*/ /* out: the buddy, NULL if no buddy in pool */ mem_area_t* area, /* in: memory area */ ulint size, /* in: memory area size */ mem_pool_t* pool) /* in: memory pool */ { mem_area_t* buddy; ut_ad(size != 0); if (((((byte*)area) - pool->buf) % (2 * size)) == 0) { /* The buddy is in a higher address */ buddy = (mem_area_t*)(((byte*)area) + size); if ((((byte*)buddy) - pool->buf) + size > pool->size) { /* The buddy is not wholly contained in the pool: there is no buddy */ buddy = NULL; } } else { /* The buddy is in a lower address; NOTE that area cannot be at the pool lower end, because then we would end up to the upper branch in this if-clause: the remainder would be 0 */ buddy = (mem_area_t*)(((byte*)area) - size); } return(buddy); }
void trx_purge_sys_create(void) /*======================*/ { ut_ad(mutex_own(&kernel_mutex)); purge_sys = mem_alloc(sizeof(trx_purge_t)); purge_sys->state = TRX_STOP_PURGE; purge_sys->n_pages_handled = 0; purge_sys->purge_trx_no = ut_dulint_zero; purge_sys->purge_undo_no = ut_dulint_zero; purge_sys->next_stored = FALSE; rw_lock_create(&purge_sys->latch, SYNC_PURGE_LATCH); mutex_create(&purge_sys->mutex, SYNC_PURGE_SYS); purge_sys->heap = mem_heap_create(256); purge_sys->arr = trx_undo_arr_create(); purge_sys->sess = sess_open(); purge_sys->trx = purge_sys->sess->trx; purge_sys->trx->is_purge = 1; ut_a(trx_start_low(purge_sys->trx, ULINT_UNDEFINED)); purge_sys->query = trx_purge_graph_build(); purge_sys->view = read_view_oldest_copy_or_open_new(ut_dulint_zero, purge_sys->heap); }
/***********************************************************//** Purges a delete marking of a record. */ static void row_purge_del_mark( /*===============*/ purge_node_t* node) /*!< in: row purge node */ { mem_heap_t* heap; dtuple_t* entry; dict_index_t* index; ut_ad(node); heap = mem_heap_create(1024); while (node->index != NULL) { /* skip corrupted secondary index */ dict_table_skip_corrupt_index(node->index); if (!node->index) { break; } index = node->index; /* Build the index entry */ entry = row_build_index_entry(node->row, NULL, index, heap); ut_a(entry); row_purge_remove_sec_if_poss(node, index, entry); node->index = dict_table_get_next_index(node->index); } mem_heap_free(heap); row_purge_remove_clust_if_poss(node); }
/********************************************************************//** Assigns a read view for a consistent read query. All the consistent reads within the same transaction will get the same read view, which is created when this function is first called for a new started transaction. @return consistent read view */ UNIV_INTERN read_view_t* trx_assign_read_view( /*=================*/ trx_t* trx) /*!< in: active transaction */ { ut_ad(trx->conc_state == TRX_ACTIVE); if (trx->read_view) { return(trx->read_view); } mutex_enter(&kernel_mutex); if (!trx->read_view) { trx->read_view = read_view_open_now( trx->id, trx->global_read_view_heap); trx->global_read_view = trx->read_view; } mutex_exit(&kernel_mutex); return(trx->read_view); }
/******************************************************************//** Erases an allocated memory field in the debug version. */ UNIV_INTERN void mem_field_erase( /*============*/ byte* buf, /*!< in: memory field */ ulint n __attribute__((unused))) /*!< in: how many bytes the user requested */ { byte* usr_buf; usr_buf = buf + MEM_FIELD_HEADER_SIZE; mutex_enter(&mem_hash_mutex); mem_current_allocated_memory -= n; mutex_exit(&mem_hash_mutex); /* Check that the field lengths agree */ ut_ad(n == (ulint)mem_field_header_get_len(usr_buf)); /* In the debug version, set the freed space to a random combination of 0xDE and 0xAD */ mem_erase_buf(buf, MEM_SPACE_NEEDED(n)); }
/********************************************************************//** Removes unnecessary history data from rollback segments. NOTE that when this function is called, the caller must not have any latches on undo log pages! */ static void trx_purge_truncate_history(void) /*============================*/ { trx_rseg_t* rseg; trx_id_t limit_trx_no; undo_no_t limit_undo_no; trx_purge_arr_get_biggest( purge_sys->arr, &limit_trx_no, &limit_undo_no); if (limit_trx_no == 0) { limit_trx_no = purge_sys->purge_trx_no; limit_undo_no = purge_sys->purge_undo_no; } /* We play safe and set the truncate limit at most to the purge view low_limit number, though this is not necessary */ if (limit_trx_no >= purge_sys->view->low_limit_no) { limit_trx_no = purge_sys->view->low_limit_no; limit_undo_no = 0; } ut_ad(limit_trx_no <= purge_sys->view->low_limit_no); for (rseg = UT_LIST_GET_FIRST(trx_sys->rseg_list); rseg != NULL; rseg = UT_LIST_GET_NEXT(rseg_list, rseg)) { trx_purge_truncate_rseg_history( rseg, limit_trx_no, limit_undo_no); } }
/**********************************************************************//** Deallocate a buffer frame of UNIV_PAGE_SIZE. */ static void buf_buddy_block_free( /*=================*/ buf_pool_t* buf_pool, /*!< in: buffer pool instance */ void* buf) /*!< in: buffer frame to deallocate */ { const ulint fold = BUF_POOL_ZIP_FOLD_PTR(buf); buf_page_t* bpage; buf_block_t* block; ut_ad(buf_pool_mutex_own(buf_pool)); ut_ad(!mutex_own(&buf_pool->zip_mutex)); ut_a(!ut_align_offset(buf, UNIV_PAGE_SIZE)); HASH_SEARCH(hash, buf_pool->zip_hash, fold, buf_page_t*, bpage, ut_ad(buf_page_get_state(bpage) == BUF_BLOCK_MEMORY && bpage->in_zip_hash && !bpage->in_page_hash), ((buf_block_t*) bpage)->frame == buf); ut_a(bpage); ut_a(buf_page_get_state(bpage) == BUF_BLOCK_MEMORY); ut_ad(!bpage->in_page_hash); ut_ad(bpage->in_zip_hash); ut_d(bpage->in_zip_hash = FALSE); HASH_DELETE(buf_page_t, hash, buf_pool->zip_hash, fold, bpage); ut_d(memset(buf, 0, UNIV_PAGE_SIZE)); UNIV_MEM_INVALID(buf, UNIV_PAGE_SIZE); block = (buf_block_t*) bpage; mutex_enter(&block->mutex); buf_LRU_block_free_non_file_page(block); mutex_exit(&block->mutex); ut_ad(buf_pool->buddy_n_frames > 0); ut_d(buf_pool->buddy_n_frames--); }
/****************************************************************//** Creates and initializes a transaction object. @return own: the transaction */ UNIV_INTERN trx_t* trx_create( /*=======*/ sess_t* sess) /*!< in: session */ { trx_t* trx; ut_ad(mutex_own(&kernel_mutex)); ut_ad(sess); trx = mem_alloc(sizeof(trx_t)); trx->magic_n = TRX_MAGIC_N; trx->op_info = ""; trx->is_purge = 0; trx->is_recovered = 0; trx->conc_state = TRX_NOT_STARTED; trx->start_time = time(NULL); trx->isolation_level = TRX_ISO_REPEATABLE_READ; trx->id = ut_dulint_zero; trx->no = ut_dulint_max; trx->support_xa = TRUE; trx->check_foreigns = TRUE; trx->check_unique_secondary = TRUE; trx->flush_log_later = FALSE; trx->must_flush_log_later = FALSE; trx->dict_operation = TRX_DICT_OP_NONE; trx->table_id = ut_dulint_zero; trx->mysql_thd = NULL; trx->active_trans = 0; trx->duplicates = 0; trx->n_mysql_tables_in_use = 0; trx->mysql_n_tables_locked = 0; trx->mysql_log_file_name = NULL; trx->mysql_log_offset = 0; mutex_create(&trx->undo_mutex, SYNC_TRX_UNDO); trx->rseg = NULL; trx->undo_no = ut_dulint_zero; trx->last_sql_stat_start.least_undo_no = ut_dulint_zero; trx->insert_undo = NULL; trx->update_undo = NULL; trx->undo_no_arr = NULL; trx->error_state = DB_SUCCESS; trx->error_key_num = 0; trx->detailed_error[0] = '\0'; trx->sess = sess; trx->que_state = TRX_QUE_RUNNING; trx->n_active_thrs = 0; trx->handling_signals = FALSE; UT_LIST_INIT(trx->signals); UT_LIST_INIT(trx->reply_signals); trx->graph = NULL; trx->wait_lock = NULL; trx->was_chosen_as_deadlock_victim = FALSE; UT_LIST_INIT(trx->wait_thrs); trx->lock_heap = mem_heap_create_in_buffer(256); UT_LIST_INIT(trx->trx_locks); UT_LIST_INIT(trx->trx_savepoints); trx->dict_operation_lock_mode = 0; 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->global_read_view_heap = mem_heap_create(256); trx->global_read_view = NULL; trx->read_view = NULL; /* Set X/Open XA transaction identification to NULL */ memset(&trx->xid, 0, sizeof(trx->xid)); trx->xid.formatID = -1; trx->n_autoinc_rows = 0; /* Remember to free the vector explicitly. */ trx->autoinc_locks = ib_vector_create( mem_heap_create(sizeof(ib_vector_t) + sizeof(void*) * 4), 4); return(trx); }
/****************************************************************//** Commits a transaction. */ UNIV_INTERN void trx_commit_off_kernel( /*==================*/ trx_t* trx) /*!< in: transaction */ { page_t* update_hdr_page; ib_uint64_t lsn = 0; trx_rseg_t* rseg; trx_undo_t* undo; mtr_t mtr; ut_ad(mutex_own(&kernel_mutex)); trx->must_flush_log_later = FALSE; rseg = trx->rseg; if (trx->insert_undo != NULL || trx->update_undo != NULL) { mutex_exit(&kernel_mutex); mtr_start(&mtr); /* Change the undo log segment states from TRX_UNDO_ACTIVE to some other state: these modifications to the file data structure define the transaction as committed in the file based world, at the serialization point of the log sequence number lsn obtained below. */ mutex_enter(&(rseg->mutex)); if (trx->insert_undo != NULL) { trx_undo_set_state_at_finish( rseg, trx, trx->insert_undo, &mtr); } undo = trx->update_undo; if (undo) { mutex_enter(&kernel_mutex); trx->no = trx_sys_get_new_trx_no(); mutex_exit(&kernel_mutex); /* It is not necessary to obtain trx->undo_mutex here because only a single OS thread is allowed to do the transaction commit for this transaction. */ update_hdr_page = trx_undo_set_state_at_finish( rseg, trx, undo, &mtr); /* We have to do the cleanup for the update log while holding the rseg mutex because update log headers have to be put to the history list in the order of the trx number. */ trx_undo_update_cleanup(trx, update_hdr_page, &mtr); } mutex_exit(&(rseg->mutex)); /* Update the latest MySQL binlog name and offset info in trx sys header if MySQL binlogging is on or the database server is a MySQL replication slave */ if (trx->mysql_log_file_name && trx->mysql_log_file_name[0] != '\0') { trx_sys_update_mysql_binlog_offset( trx->mysql_log_file_name, trx->mysql_log_offset, TRX_SYS_MYSQL_LOG_INFO, &mtr); trx->mysql_log_file_name = NULL; } /* The following call commits the mini-transaction, making the whole transaction committed in the file-based world, at this log sequence number. The transaction becomes 'durable' when we write the log to disk, but in the logical sense the commit in the file-based data structures (undo logs etc.) happens here. NOTE that transaction numbers, which are assigned only to transactions with an update undo log, do not necessarily come in exactly the same order as commit lsn's, if the transactions have different rollback segments. To get exactly the same order we should hold the kernel mutex up to this point, adding to the contention of the kernel mutex. However, if a transaction T2 is able to see modifications made by a transaction T1, T2 will always get a bigger transaction number and a bigger commit lsn than T1. */ /*--------------*/ mtr_commit(&mtr); /*--------------*/ lsn = mtr.end_lsn; mutex_enter(&kernel_mutex); } ut_ad(trx->conc_state == TRX_ACTIVE || trx->conc_state == TRX_PREPARED); ut_ad(mutex_own(&kernel_mutex)); /* The following assignment makes the transaction committed in memory and makes its changes to data visible to other transactions. NOTE that there is a small discrepancy from the strict formal visibility rules here: a human user of the database can see modifications made by another transaction T even before the necessary log segment has been flushed to the disk. If the database happens to crash before the flush, the user has seen modifications from T which will never be a committed transaction. However, any transaction T2 which sees the modifications of the committing transaction T, and which also itself makes modifications to the database, will get an lsn larger than the committing transaction T. In the case where the log flush fails, and T never gets committed, also T2 will never get committed. */ /*--------------------------------------*/ trx->conc_state = TRX_COMMITTED_IN_MEMORY; /*--------------------------------------*/ /* If we release kernel_mutex below and we are still doing recovery i.e.: back ground rollback thread is still active then there is a chance that the rollback thread may see this trx as COMMITTED_IN_MEMORY and goes adhead to clean it up calling trx_cleanup_at_db_startup(). This can happen in the case we are committing a trx here that is left in PREPARED state during the crash. Note that commit of the rollback of a PREPARED trx happens in the recovery thread while the rollback of other transactions happen in the background thread. To avoid this race we unconditionally unset the is_recovered flag from the trx. */ trx->is_recovered = FALSE; lock_release_off_kernel(trx); if (trx->global_read_view) { read_view_close(trx->global_read_view); mem_heap_empty(trx->global_read_view_heap); trx->global_read_view = NULL; } trx->read_view = NULL; if (lsn) { mutex_exit(&kernel_mutex); if (trx->insert_undo != NULL) { trx_undo_insert_cleanup(trx); } /* NOTE that we could possibly make a group commit more efficient here: call os_thread_yield here to allow also other trxs to come to commit! */ /*-------------------------------------*/ /* Depending on the my.cnf options, we may now write the log buffer to the log files, making the transaction durable if the OS does not crash. We may also flush the log files to disk, making the transaction durable also at an OS crash or a power outage. The idea in InnoDB's group commit is that a group of transactions gather behind a trx doing a physical disk write to log files, and when that physical write has been completed, one of those transactions does a write which commits the whole group. Note that this group commit will only bring benefit if there are > 2 users in the database. Then at least 2 users can gather behind one doing the physical log write to disk. If we are calling trx_commit() under prepare_commit_mutex, we will delay possible log write and flush to a separate function trx_commit_complete_for_mysql(), which is only called when the thread has released the mutex. This is to make the group commit algorithm to work. Otherwise, the prepare_commit mutex would serialize all commits and prevent a group of transactions from gathering. */ if (trx->flush_log_later) { /* Do nothing yet */ trx->must_flush_log_later = TRUE; } else if (srv_flush_log_at_trx_commit == 0) { /* Do nothing */ } else if (srv_flush_log_at_trx_commit == 1) { if (srv_unix_file_flush_method == SRV_UNIX_NOSYNC) { /* Write the log but do not flush it to disk */ log_write_up_to(lsn, LOG_WAIT_ONE_GROUP, FALSE); } else { /* Write the log to the log files AND flush them to disk */ log_write_up_to(lsn, LOG_WAIT_ONE_GROUP, TRUE); } } else if (srv_flush_log_at_trx_commit == 2) { /* Write the log but do not flush it to disk */ log_write_up_to(lsn, LOG_WAIT_ONE_GROUP, FALSE); } else { ut_error; } trx->commit_lsn = lsn; /*-------------------------------------*/ mutex_enter(&kernel_mutex); } /* Free all savepoints */ trx_roll_free_all_savepoints(trx); trx->conc_state = TRX_NOT_STARTED; trx->rseg = NULL; trx->undo_no = ut_dulint_zero; trx->last_sql_stat_start.least_undo_no = ut_dulint_zero; ut_ad(UT_LIST_GET_LEN(trx->wait_thrs) == 0); ut_ad(UT_LIST_GET_LEN(trx->trx_locks) == 0); UT_LIST_REMOVE(trx_list, trx_sys->trx_list, trx); }
/****************************************************************//** Creates trx objects for transactions and initializes the trx list of trx_sys at database start. Rollback segment and undo log lists must already exist when this function is called, because the lists of transactions to be rolled back or cleaned up are built based on the undo log lists. */ UNIV_INTERN void trx_lists_init_at_db_start(void) /*============================*/ { trx_rseg_t* rseg; trx_undo_t* undo; trx_t* trx; ut_ad(mutex_own(&kernel_mutex)); UT_LIST_INIT(trx_sys->trx_list); /* Look from the rollback segments if there exist undo logs for transactions */ rseg = UT_LIST_GET_FIRST(trx_sys->rseg_list); while (rseg != NULL) { undo = UT_LIST_GET_FIRST(rseg->insert_undo_list); while (undo != NULL) { trx = trx_create(trx_dummy_sess); trx->is_recovered = TRUE; trx->id = undo->trx_id; trx->xid = undo->xid; trx->insert_undo = undo; trx->rseg = rseg; if (undo->state != TRX_UNDO_ACTIVE) { /* Prepared transactions are left in the prepared state waiting for a commit or abort decision from MySQL */ if (undo->state == TRX_UNDO_PREPARED) { fprintf(stderr, "InnoDB: Transaction " TRX_ID_FMT " was in the" " XA prepared state.\n", TRX_ID_PREP_PRINTF(trx->id)); if (srv_force_recovery == 0) { trx->conc_state = TRX_PREPARED; } else { fprintf(stderr, "InnoDB: Since" " innodb_force_recovery" " > 0, we will" " rollback it" " anyway.\n"); trx->conc_state = TRX_ACTIVE; } } else { trx->conc_state = TRX_COMMITTED_IN_MEMORY; } /* We give a dummy value for the trx no; this should have no relevance since purge is not interested in committed transaction numbers, unless they are in the history list, in which case it looks the number from the disk based undo log structure */ trx->no = trx->id; } else { trx->conc_state = TRX_ACTIVE; /* A running transaction always has the number field inited to ut_dulint_max */ trx->no = ut_dulint_max; } if (undo->dict_operation) { trx_set_dict_operation( trx, TRX_DICT_OP_TABLE); trx->table_id = undo->table_id; } if (!undo->empty) { trx->undo_no = ut_dulint_add(undo->top_undo_no, 1); } trx_list_insert_ordered(trx); undo = UT_LIST_GET_NEXT(undo_list, undo); } undo = UT_LIST_GET_FIRST(rseg->update_undo_list); while (undo != NULL) { trx = trx_get_on_id(undo->trx_id); if (NULL == trx) { trx = trx_create(trx_dummy_sess); trx->is_recovered = TRUE; trx->id = undo->trx_id; trx->xid = undo->xid; if (undo->state != TRX_UNDO_ACTIVE) { /* Prepared transactions are left in the prepared state waiting for a commit or abort decision from MySQL */ if (undo->state == TRX_UNDO_PREPARED) { fprintf(stderr, "InnoDB: Transaction " TRX_ID_FMT " was in the" " XA prepared state.\n", TRX_ID_PREP_PRINTF( trx->id)); if (srv_force_recovery == 0) { trx->conc_state = TRX_PREPARED; } else { fprintf(stderr, "InnoDB: Since" " innodb_force_recovery" " > 0, we will" " rollback it" " anyway.\n"); trx->conc_state = TRX_ACTIVE; } } else { trx->conc_state = TRX_COMMITTED_IN_MEMORY; } /* We give a dummy value for the trx number */ trx->no = trx->id; } else { trx->conc_state = TRX_ACTIVE; /* A running transaction always has the number field inited to ut_dulint_max */ trx->no = ut_dulint_max; } trx->rseg = rseg; trx_list_insert_ordered(trx); if (undo->dict_operation) { trx_set_dict_operation( trx, TRX_DICT_OP_TABLE); trx->table_id = undo->table_id; } } trx->update_undo = undo; if ((!undo->empty) && (ut_dulint_cmp(undo->top_undo_no, trx->undo_no) >= 0)) { trx->undo_no = ut_dulint_add(undo->top_undo_no, 1); } undo = UT_LIST_GET_NEXT(undo_list, undo); } rseg = UT_LIST_GET_NEXT(rseg_list, rseg); } }
/********************************************************************//** Frees a transaction object. */ UNIV_INTERN void trx_free( /*=====*/ trx_t* trx) /*!< in, own: trx object */ { ut_ad(mutex_own(&kernel_mutex)); if (trx->declared_to_be_inside_innodb) { ut_print_timestamp(stderr); fputs(" InnoDB: Error: Freeing a trx which is declared" " to be processing\n" "InnoDB: inside InnoDB.\n", stderr); trx_print(stderr, trx, 600); putc('\n', stderr); /* This is an error but not a fatal error. We must keep the counters like srv_conc_n_threads accurate. */ srv_conc_force_exit_innodb(trx); } if (trx->n_mysql_tables_in_use != 0 || trx->mysql_n_tables_locked != 0) { ut_print_timestamp(stderr); fprintf(stderr, " InnoDB: Error: MySQL is freeing a thd\n" "InnoDB: though trx->n_mysql_tables_in_use is %lu\n" "InnoDB: and trx->mysql_n_tables_locked is %lu.\n", (ulong)trx->n_mysql_tables_in_use, (ulong)trx->mysql_n_tables_locked); trx_print(stderr, trx, 600); ut_print_buf(stderr, trx, sizeof(trx_t)); putc('\n', stderr); } ut_a(trx->magic_n == TRX_MAGIC_N); trx->magic_n = 11112222; ut_a(trx->conc_state == TRX_NOT_STARTED); mutex_free(&(trx->undo_mutex)); ut_a(trx->insert_undo == NULL); ut_a(trx->update_undo == NULL); if (trx->undo_no_arr) { trx_undo_arr_free(trx->undo_no_arr); } ut_a(UT_LIST_GET_LEN(trx->signals) == 0); ut_a(UT_LIST_GET_LEN(trx->reply_signals) == 0); ut_a(trx->wait_lock == NULL); ut_a(UT_LIST_GET_LEN(trx->wait_thrs) == 0); ut_a(!trx->has_search_latch); ut_a(trx->dict_operation_lock_mode == 0); if (trx->lock_heap) { mem_heap_free(trx->lock_heap); } ut_a(UT_LIST_GET_LEN(trx->trx_locks) == 0); if (trx->global_read_view_heap) { mem_heap_free(trx->global_read_view_heap); } trx->global_read_view = NULL; ut_a(trx->read_view == NULL); ut_a(ib_vector_is_empty(trx->autoinc_locks)); /* We allocated a dedicated heap for the vector. */ ib_vector_free(trx->autoinc_locks); mem_free(trx); }
/********************************************************//** Opens a buffer for mlog, writes the initial log record and, if needed, the field lengths of an index. @return buffer, NULL if log mode MTR_LOG_NONE */ UNIV_INTERN byte* mlog_open_and_write_index( /*======================*/ mtr_t* mtr, /*!< in: mtr */ const byte* rec, /*!< in: index record or page */ dict_index_t* index, /*!< in: record descriptor */ byte type, /*!< in: log item type */ ulint size) /*!< in: requested buffer size in bytes (if 0, calls mlog_close() and returns NULL) */ { byte* log_ptr; const byte* log_start; const byte* log_end; ut_ad(!!page_rec_is_comp(rec) == dict_table_is_comp(index->table)); if (!page_rec_is_comp(rec)) { log_start = log_ptr = mlog_open(mtr, 11 + size); if (!log_ptr) { return(NULL); /* logging is disabled */ } log_ptr = mlog_write_initial_log_record_fast(rec, type, log_ptr, mtr); log_end = log_ptr + 11 + size; } else { ulint i; ulint n = dict_index_get_n_fields(index); /* total size needed */ ulint total = 11 + size + (n + 2) * 2; ulint alloc = total; /* allocate at most DYN_ARRAY_DATA_SIZE at a time */ if (alloc > DYN_ARRAY_DATA_SIZE) { alloc = DYN_ARRAY_DATA_SIZE; } log_start = log_ptr = mlog_open(mtr, alloc); if (!log_ptr) { return(NULL); /* logging is disabled */ } log_end = log_ptr + alloc; log_ptr = mlog_write_initial_log_record_fast(rec, type, log_ptr, mtr); mach_write_to_2(log_ptr, n); log_ptr += 2; mach_write_to_2(log_ptr, dict_index_get_n_unique_in_tree(index)); log_ptr += 2; for (i = 0; i < n; i++) { dict_field_t* field; const dict_col_t* col; ulint len; field = dict_index_get_nth_field(index, i); col = dict_field_get_col(field); len = field->fixed_len; ut_ad(len < 0x7fff); if (len == 0 && (col->len > 255 || col->mtype == DATA_BLOB)) { /* variable-length field with maximum length > 255 */ len = 0x7fff; } if (col->prtype & DATA_NOT_NULL) { len |= 0x8000; } if (log_ptr + 2 > log_end) { mlog_close(mtr, log_ptr); ut_a(total > (ulint) (log_ptr - log_start)); total -= log_ptr - log_start; alloc = total; if (alloc > DYN_ARRAY_DATA_SIZE) { alloc = DYN_ARRAY_DATA_SIZE; } log_start = log_ptr = mlog_open(mtr, alloc); if (!log_ptr) { return(NULL); /* logging is disabled */ } log_end = log_ptr + alloc; } mach_write_to_2(log_ptr, len); log_ptr += 2; } } if (size == 0) { mlog_close(mtr, log_ptr); log_ptr = NULL; } else if (log_ptr + size > log_end) { mlog_close(mtr, log_ptr); log_ptr = mlog_open(mtr, size); } return(log_ptr); }
/********************************************************************//** Adds the update undo log as the first log in the history list. Removes the update undo log segment from the rseg slot if it is too big for reuse. */ UNIV_INTERN void trx_purge_add_update_undo_to_history( /*=================================*/ trx_t* trx, /*!< in: transaction */ page_t* undo_page, /*!< in: update undo log header page, x-latched */ mtr_t* mtr) /*!< in: mtr */ { trx_undo_t* undo; trx_rsegf_t* rseg_header; trx_ulogf_t* undo_header; undo = trx->update_undo; ut_ad(undo); ut_ad(mutex_own(&undo->rseg->mutex)); rseg_header = trx_rsegf_get( undo->rseg->space, undo->rseg->zip_size, undo->rseg->page_no, mtr); undo_header = undo_page + undo->hdr_offset; /* Add the log as the first in the history list */ if (undo->state != TRX_UNDO_CACHED) { ulint hist_size; #ifdef UNIV_DEBUG trx_usegf_t* seg_header = undo_page + TRX_UNDO_SEG_HDR; #endif /* UNIV_DEBUG */ /* The undo log segment will not be reused */ if (UNIV_UNLIKELY(undo->id >= TRX_RSEG_N_SLOTS)) { fprintf(stderr, "InnoDB: Error: undo->id is %lu\n", (ulong) undo->id); ut_error; } trx_rsegf_set_nth_undo(rseg_header, undo->id, FIL_NULL, mtr); hist_size = mtr_read_ulint( rseg_header + TRX_RSEG_HISTORY_SIZE, MLOG_4BYTES, mtr); ut_ad(undo->size == flst_get_len( seg_header + TRX_UNDO_PAGE_LIST, mtr)); mlog_write_ulint( rseg_header + TRX_RSEG_HISTORY_SIZE, hist_size + undo->size, MLOG_4BYTES, mtr); } flst_add_first( rseg_header + TRX_RSEG_HISTORY, undo_header + TRX_UNDO_HISTORY_NODE, mtr); /* Write the trx number to the undo log header */ mlog_write_ull(undo_header + TRX_UNDO_TRX_NO, trx->no, mtr); /* Write information about delete markings to the undo log header */ if (!undo->del_marks) { mlog_write_ulint( undo_header + TRX_UNDO_DEL_MARKS, FALSE, MLOG_2BYTES, mtr); } if (undo->rseg->last_page_no == FIL_NULL) { undo->rseg->last_trx_no = trx->no; undo->rseg->last_offset = undo->hdr_offset; undo->rseg->last_page_no = undo->hdr_page_no; undo->rseg->last_del_marks = undo->del_marks; /* FIXME: Add a bin heap validate function to check that the rseg exists. */ } mutex_enter(&kernel_mutex); trx_sys->rseg_history_len++; mutex_exit(&kernel_mutex); // if (!(trx_sys->rseg_history_len % srv_purge_batch_size)) { /*should wake up always*/ /* Inform the purge thread that there is work to do. */ srv_wake_purge_thread_if_not_active(); // } }
/********************************************************************//** Fetches the next undo log record from the history list to purge. It must be released with the corresponding release function. @return copy of an undo log record or pointer to trx_purge_dummy_rec, if the whole undo log can skipped in purge; NULL if none left */ UNIV_INTERN trx_undo_rec_t* trx_purge_fetch_next_rec( /*=====================*/ roll_ptr_t* roll_ptr,/*!< out: roll pointer to undo record */ trx_undo_inf_t** cell, /*!< out: storage cell for the record in the purge array */ mem_heap_t* heap) /*!< in: memory heap where copied */ { trx_undo_rec_t* undo_rec; if (purge_sys->state == TRX_STOP_PURGE) { trx_purge_truncate_if_arr_empty(); return(NULL); } else if (!purge_sys->next_stored) { trx_purge_choose_next_log(); if (!purge_sys->next_stored) { purge_sys->state = TRX_STOP_PURGE; trx_purge_truncate_if_arr_empty(); if (srv_print_thread_releases) { fprintf(stderr, "Purge: No logs left in the" " history list; pages handled %lu\n", (ulong) purge_sys->n_pages_handled); } return(NULL); } } if (purge_sys->n_pages_handled >= purge_sys->handle_limit) { purge_sys->state = TRX_STOP_PURGE; trx_purge_truncate_if_arr_empty(); return(NULL); } else if (purge_sys->purge_trx_no >= purge_sys->view->low_limit_no) { purge_sys->state = TRX_STOP_PURGE; trx_purge_truncate_if_arr_empty(); return(NULL); } /* fprintf(stderr, "Thread %lu purging trx %llu undo record %llu\n", os_thread_get_curr_id(), (ullint) purge_sys->purge_trx_no, (ullint) purge_sys->purge_undo_no); */ *roll_ptr = trx_undo_build_roll_ptr( FALSE, (purge_sys->rseg)->id, purge_sys->page_no, purge_sys->offset); *cell = trx_purge_arr_store_info( purge_sys->purge_trx_no, purge_sys->purge_undo_no); ut_ad(purge_sys->purge_trx_no < purge_sys->view->low_limit_no); /* The following call will advance the stored values of purge_trx_no and purge_undo_no, therefore we had to store them first */ undo_rec = trx_purge_get_next_rec(heap); return(undo_rec); }
/******************************************************************//** Creates, or rather, initializes a mutex object in a specified memory location (which must be appropriately aligned). The mutex is initialized in the reset state. Explicit freeing of the mutex with mutex_free is necessary only if the memory block containing it is freed. */ UNIV_INTERN void mutex_create_func( /*==============*/ mutex_t* mutex, /*!< in: pointer to memory */ #ifdef UNIV_DEBUG const char* cmutex_name, /*!< in: mutex name */ # ifdef UNIV_SYNC_DEBUG ulint level, /*!< in: level */ # endif /* UNIV_SYNC_DEBUG */ #endif /* UNIV_DEBUG */ const char* cfile_name, /*!< in: file name where created */ ulint cline) /*!< in: file line where created */ { #if defined(HAVE_ATOMIC_BUILTINS) mutex_reset_lock_word(mutex); #else os_fast_mutex_init(&(mutex->os_fast_mutex)); mutex->lock_word = 0; #endif mutex->event = os_event_create(NULL); mutex_set_waiters(mutex, 0); #ifdef UNIV_DEBUG mutex->magic_n = MUTEX_MAGIC_N; #endif /* UNIV_DEBUG */ #ifdef UNIV_SYNC_DEBUG mutex->line = 0; mutex->file_name = "not yet reserved"; mutex->level = level; #endif /* UNIV_SYNC_DEBUG */ mutex->cfile_name = cfile_name; mutex->cline = cline; mutex->count_os_wait = 0; #ifdef UNIV_DEBUG mutex->cmutex_name= cmutex_name; mutex->count_using= 0; mutex->mutex_type= 0; mutex->lspent_time= 0; mutex->lmax_spent_time= 0; mutex->count_spin_loop= 0; mutex->count_spin_rounds= 0; mutex->count_os_yield= 0; #endif /* UNIV_DEBUG */ /* Check that lock_word is aligned; this is important on Intel */ ut_ad(((ulint)(&(mutex->lock_word))) % 4 == 0); /* NOTE! The very first mutexes are not put to the mutex list */ if ((mutex == &mutex_list_mutex) #ifdef UNIV_SYNC_DEBUG || (mutex == &sync_thread_mutex) #endif /* UNIV_SYNC_DEBUG */ ) { return; } mutex_enter(&mutex_list_mutex); ut_ad(UT_LIST_GET_LEN(mutex_list) == 0 || UT_LIST_GET_FIRST(mutex_list)->magic_n == MUTEX_MAGIC_N); UT_LIST_ADD_FIRST(list, mutex_list, mutex); mutex_exit(&mutex_list_mutex); }
/*****************************************************************//** Checks the compatibility of a new signal with the other signals in the queue. @return TRUE if the signal can be queued */ static ibool trx_sig_is_compatible( /*==================*/ trx_t* trx, /*!< in: trx handle */ ulint type, /*!< in: signal type */ ulint sender) /*!< in: TRX_SIG_SELF or TRX_SIG_OTHER_SESS */ { trx_sig_t* sig; ut_ad(mutex_own(&kernel_mutex)); if (UT_LIST_GET_LEN(trx->signals) == 0) { return(TRUE); } if (sender == TRX_SIG_SELF) { if (type == TRX_SIG_ERROR_OCCURRED) { return(TRUE); } else if (type == TRX_SIG_BREAK_EXECUTION) { return(TRUE); } else { return(FALSE); } } ut_ad(sender == TRX_SIG_OTHER_SESS); sig = UT_LIST_GET_FIRST(trx->signals); if (type == TRX_SIG_COMMIT) { while (sig != NULL) { if (sig->type == TRX_SIG_TOTAL_ROLLBACK) { return(FALSE); } sig = UT_LIST_GET_NEXT(signals, sig); } return(TRUE); } else if (type == TRX_SIG_TOTAL_ROLLBACK) { while (sig != NULL) { if (sig->type == TRX_SIG_COMMIT) { return(FALSE); } sig = UT_LIST_GET_NEXT(signals, sig); } return(TRUE); } else if (type == TRX_SIG_BREAK_EXECUTION) { return(TRUE); } else { ut_error; return(FALSE); } }
/*******************************************************************//** This function runs a purge batch. @return number of undo log pages handled in the batch */ UNIV_INTERN ulint trx_purge( /*======*/ ulint limit) /*!< in: the maximum number of records to purge in one batch */ { que_thr_t* thr; ulint old_pages_handled; if (srv_fake_write) return(0); ut_a(purge_sys->trx->n_active_thrs == 0); rw_lock_x_lock(&purge_sys->latch); mutex_enter(&kernel_mutex); /* Close and free the old purge view */ read_view_close(purge_sys->view); purge_sys->view = NULL; mem_heap_empty(purge_sys->heap); /* Determine how much data manipulation language (DML) statements need to be delayed in order to reduce the lagging of the purge thread. */ srv_dml_needed_delay = 0; /* in microseconds; default: no delay */ /* If we cannot advance the 'purge view' because of an old 'consistent read view', then the DML statements cannot be delayed. Also, srv_max_purge_lag <= 0 means 'infinity'. */ if (srv_max_purge_lag > 0) { float ratio = (float) trx_sys->rseg_history_len / srv_max_purge_lag; if (ratio > ULINT_MAX / 10000) { /* Avoid overflow: maximum delay is 4295 seconds */ srv_dml_needed_delay = ULINT_MAX; } else if (ratio > 1) { /* If the history list length exceeds the innodb_max_purge_lag, the data manipulation statements are delayed by at least 5000 microseconds. */ srv_dml_needed_delay = (ulint) ((ratio - .5) * 10000); } } purge_sys->view = read_view_oldest_copy_or_open_new( 0, purge_sys->heap); mutex_exit(&kernel_mutex); rw_lock_x_unlock(&(purge_sys->latch)); purge_sys->state = TRX_PURGE_ON; purge_sys->handle_limit = purge_sys->n_pages_handled + limit; old_pages_handled = purge_sys->n_pages_handled; mutex_enter(&kernel_mutex); thr = que_fork_start_command(purge_sys->query); ut_ad(thr); mutex_exit(&kernel_mutex); if (srv_print_thread_releases) { fputs("Starting purge\n", stderr); } que_run_threads(thr); if (srv_print_thread_releases) { fprintf(stderr, "Purge ends; pages handled %lu\n", (ulong) purge_sys->n_pages_handled); } return(purge_sys->n_pages_handled - old_pages_handled); }
/****************************************************************//** Sends a signal to a trx object. */ UNIV_INTERN void trx_sig_send( /*=========*/ trx_t* trx, /*!< in: trx handle */ ulint type, /*!< in: signal type */ ulint sender, /*!< in: TRX_SIG_SELF or TRX_SIG_OTHER_SESS */ que_thr_t* receiver_thr, /*!< in: query thread which wants the reply, or NULL; if type is TRX_SIG_END_WAIT, this must be NULL */ trx_savept_t* savept, /*!< in: possible rollback savepoint, or NULL */ que_thr_t** next_thr) /*!< in/out: next query thread to run; if the value which is passed in is a pointer to a NULL pointer, then the calling function can start running a new query thread; if the parameter is NULL, it is ignored */ { trx_sig_t* sig; trx_t* receiver_trx; ut_ad(trx); ut_ad(mutex_own(&kernel_mutex)); if (!trx_sig_is_compatible(trx, type, sender)) { /* The signal is not compatible with the other signals in the queue: die */ ut_error; } /* Queue the signal object */ if (UT_LIST_GET_LEN(trx->signals) == 0) { /* The signal list is empty: the 'sig' slot must be unused (we improve performance a bit by avoiding mem_alloc) */ sig = &(trx->sig); } else { /* It might be that the 'sig' slot is unused also in this case, but we choose the easy way of using mem_alloc */ sig = mem_alloc(sizeof(trx_sig_t)); } UT_LIST_ADD_LAST(signals, trx->signals, sig); sig->type = type; sig->sender = sender; sig->receiver = receiver_thr; if (savept) { sig->savept = *savept; } if (receiver_thr) { receiver_trx = thr_get_trx(receiver_thr); UT_LIST_ADD_LAST(reply_signals, receiver_trx->reply_signals, sig); } if (trx->sess->state == SESS_ERROR) { trx_sig_reply_wait_to_suspended(trx); } if ((sender != TRX_SIG_SELF) || (type == TRX_SIG_BREAK_EXECUTION)) { ut_error; } /* If there were no other signals ahead in the queue, try to start handling of the signal */ if (UT_LIST_GET_FIRST(trx->signals) == sig) { trx_sig_start_handle(trx, next_thr); } }
/**********************************************************************//** Frees an undo log segment which is in the history list. Cuts the end of the history list at the youngest undo log in this segment. */ static void trx_purge_free_segment( /*===================*/ trx_rseg_t* rseg, /*!< in: rollback segment */ fil_addr_t hdr_addr, /*!< in: the file address of log_hdr */ ulint n_removed_logs) /*!< in: count of how many undo logs we will cut off from the end of the history list */ { page_t* undo_page; trx_rsegf_t* rseg_hdr; trx_ulogf_t* log_hdr; trx_usegf_t* seg_hdr; ibool freed; ulint seg_size; ulint hist_size; ibool marked = FALSE; mtr_t mtr; /* fputs("Freeing an update undo log segment\n", stderr); */ loop: mtr_start(&mtr); mutex_enter(&(rseg->mutex)); rseg_hdr = trx_rsegf_get(rseg->space, rseg->zip_size, rseg->page_no, &mtr); undo_page = trx_undo_page_get(rseg->space, rseg->zip_size, hdr_addr.page, &mtr); seg_hdr = undo_page + TRX_UNDO_SEG_HDR; log_hdr = undo_page + hdr_addr.boffset; /* Mark the last undo log totally purged, so that if the system crashes, the tail of the undo log will not get accessed again. The list of pages in the undo log tail gets inconsistent during the freeing of the segment, and therefore purge should not try to access them again. */ if (!marked) { mlog_write_ulint(log_hdr + TRX_UNDO_DEL_MARKS, FALSE, MLOG_2BYTES, &mtr); marked = TRUE; } freed = fseg_free_step_not_header(seg_hdr + TRX_UNDO_FSEG_HEADER, &mtr); if (!freed) { mutex_exit(&(rseg->mutex)); mtr_commit(&mtr); goto loop; } /* The page list may now be inconsistent, but the length field stored in the list base node tells us how big it was before we started the freeing. */ seg_size = flst_get_len(seg_hdr + TRX_UNDO_PAGE_LIST, &mtr); /* We may free the undo log segment header page; it must be freed within the same mtr as the undo log header is removed from the history list: otherwise, in case of a database crash, the segment could become inaccessible garbage in the file space. */ flst_cut_end(rseg_hdr + TRX_RSEG_HISTORY, log_hdr + TRX_UNDO_HISTORY_NODE, n_removed_logs, &mtr); mutex_enter(&kernel_mutex); ut_ad(trx_sys->rseg_history_len >= n_removed_logs); trx_sys->rseg_history_len -= n_removed_logs; mutex_exit(&kernel_mutex); freed = FALSE; while (!freed) { /* Here we assume that a file segment with just the header page can be freed in a few steps, so that the buffer pool is not flooded with bufferfixed pages: see the note in fsp0fsp.c. */ freed = fseg_free_step(seg_hdr + TRX_UNDO_FSEG_HEADER, &mtr); } hist_size = mtr_read_ulint(rseg_hdr + TRX_RSEG_HISTORY_SIZE, MLOG_4BYTES, &mtr); ut_ad(hist_size >= seg_size); mlog_write_ulint(rseg_hdr + TRX_RSEG_HISTORY_SIZE, hist_size - seg_size, MLOG_4BYTES, &mtr); ut_ad(rseg->curr_size >= seg_size); rseg->curr_size -= seg_size; mutex_exit(&(rseg->mutex)); mtr_commit(&mtr); }
/****************************************************************//** Starts handling of a trx signal. */ UNIV_INTERN void trx_sig_start_handle( /*=================*/ trx_t* trx, /*!< in: trx handle */ que_thr_t** next_thr) /*!< in/out: next query thread to run; if the value which is passed in is a pointer to a NULL pointer, then the calling function can start running a new query thread; if the parameter is NULL, it is ignored */ { trx_sig_t* sig; ulint type; loop: /* We loop in this function body as long as there are queued signals we can process immediately */ ut_ad(trx); ut_ad(mutex_own(&kernel_mutex)); if (trx->handling_signals && (UT_LIST_GET_LEN(trx->signals) == 0)) { trx_end_signal_handling(trx); return; } if (trx->conc_state == TRX_NOT_STARTED) { trx_start_low(trx, ULINT_UNDEFINED); } /* If the trx is in a lock wait state, moves the waiting query threads to the suspended state */ if (trx->que_state == TRX_QUE_LOCK_WAIT) { trx_lock_wait_to_suspended(trx); } /* If the session is in the error state and this trx has threads waiting for reply from signals, moves these threads to the suspended state, canceling wait reservations; note that if the transaction has sent a commit or rollback signal to itself, and its session is not in the error state, then nothing is done here. */ if (trx->sess->state == SESS_ERROR) { trx_sig_reply_wait_to_suspended(trx); } /* If there are no running query threads, we can start processing of a signal, otherwise we have to wait until all query threads of this transaction are aware of the arrival of the signal. */ if (trx->n_active_thrs > 0) { return; } if (trx->handling_signals == FALSE) { trx->graph_before_signal_handling = trx->graph; trx->handling_signals = TRUE; } sig = UT_LIST_GET_FIRST(trx->signals); type = sig->type; if (type == TRX_SIG_COMMIT) { trx_handle_commit_sig_off_kernel(trx, next_thr); } else if ((type == TRX_SIG_TOTAL_ROLLBACK) || (type == TRX_SIG_ROLLBACK_TO_SAVEPT)) { trx_rollback(trx, sig, next_thr); /* No further signals can be handled until the rollback completes, therefore we return */ return; } else if (type == TRX_SIG_ERROR_OCCURRED) { trx_rollback(trx, sig, next_thr); /* No further signals can be handled until the rollback completes, therefore we return */ return; } else if (type == TRX_SIG_BREAK_EXECUTION) { trx_sig_reply(sig, next_thr); trx_sig_remove(trx, sig); } else { ut_error; } goto loop; }
/***********************************************************************//** Gets the next record to purge and updates the info in the purge system. @return copy of an undo log record or pointer to the dummy undo log record */ static trx_undo_rec_t* trx_purge_get_next_rec( /*===================*/ mem_heap_t* heap) /*!< in: memory heap where copied */ { trx_undo_rec_t* rec; trx_undo_rec_t* rec_copy; trx_undo_rec_t* rec2; trx_undo_rec_t* next_rec; page_t* undo_page; page_t* page; ulint offset; ulint page_no; ulint space; ulint zip_size; ulint type; ulint cmpl_info; mtr_t mtr; ut_ad(purge_sys->next_stored); space = purge_sys->rseg->space; zip_size = purge_sys->rseg->zip_size; page_no = purge_sys->page_no; offset = purge_sys->offset; if (offset == 0) { /* It is the dummy undo log record, which means that there is no need to purge this undo log */ trx_purge_rseg_get_next_history_log(purge_sys->rseg); /* Look for the next undo log and record to purge */ trx_purge_choose_next_log(); return(&trx_purge_dummy_rec); } mtr_start(&mtr); undo_page = trx_undo_page_get_s_latched(space, zip_size, page_no, &mtr); rec = undo_page + offset; rec2 = rec; for (;;) { /* Try first to find the next record which requires a purge operation from the same page of the same undo log */ next_rec = trx_undo_page_get_next_rec( rec2, purge_sys->hdr_page_no, purge_sys->hdr_offset); if (next_rec == NULL) { rec2 = trx_undo_get_next_rec( rec2, purge_sys->hdr_page_no, purge_sys->hdr_offset, &mtr); break; } rec2 = next_rec; type = trx_undo_rec_get_type(rec2); if (type == TRX_UNDO_DEL_MARK_REC) { break; } cmpl_info = trx_undo_rec_get_cmpl_info(rec2); if (trx_undo_rec_get_extern_storage(rec2)) { break; } if ((type == TRX_UNDO_UPD_EXIST_REC) && !(cmpl_info & UPD_NODE_NO_ORD_CHANGE)) { break; } } if (rec2 == NULL) { mtr_commit(&mtr); trx_purge_rseg_get_next_history_log(purge_sys->rseg); /* Look for the next undo log and record to purge */ trx_purge_choose_next_log(); mtr_start(&mtr); undo_page = trx_undo_page_get_s_latched(space, zip_size, page_no, &mtr); rec = undo_page + offset; } else { page = page_align(rec2); purge_sys->purge_undo_no = trx_undo_rec_get_undo_no(rec2); purge_sys->page_no = page_get_page_no(page); purge_sys->offset = rec2 - page; if (undo_page != page) { /* We advance to a new page of the undo log: */ purge_sys->n_pages_handled++; } } rec_copy = trx_undo_rec_copy(rec, heap); mtr_commit(&mtr); return(rec_copy); }
/****************************************************************//** Prepares a transaction. */ UNIV_INTERN void trx_prepare_off_kernel( /*===================*/ trx_t* trx) /*!< in: transaction */ { trx_rseg_t* rseg; ib_uint64_t lsn = 0; mtr_t mtr; ut_ad(mutex_own(&kernel_mutex)); rseg = trx->rseg; if (trx->insert_undo != NULL || trx->update_undo != NULL) { mutex_exit(&kernel_mutex); mtr_start(&mtr); /* Change the undo log segment states from TRX_UNDO_ACTIVE to TRX_UNDO_PREPARED: these modifications to the file data structure define the transaction as prepared in the file-based world, at the serialization point of lsn. */ mutex_enter(&(rseg->mutex)); if (trx->insert_undo != NULL) { /* It is not necessary to obtain trx->undo_mutex here because only a single OS thread is allowed to do the transaction prepare for this transaction. */ trx_undo_set_state_at_prepare(trx, trx->insert_undo, &mtr); } if (trx->update_undo) { trx_undo_set_state_at_prepare( trx, trx->update_undo, &mtr); } mutex_exit(&(rseg->mutex)); /*--------------*/ mtr_commit(&mtr); /* This mtr commit makes the transaction prepared in the file-based world */ /*--------------*/ lsn = mtr.end_lsn; mutex_enter(&kernel_mutex); } ut_ad(mutex_own(&kernel_mutex)); /*--------------------------------------*/ trx->conc_state = TRX_PREPARED; /*--------------------------------------*/ if (lsn) { /* Depending on the my.cnf options, we may now write the log buffer to the log files, making the prepared state of the transaction durable if the OS does not crash. We may also flush the log files to disk, making the prepared state of the transaction durable also at an OS crash or a power outage. The idea in InnoDB's group prepare is that a group of transactions gather behind a trx doing a physical disk write to log files, and when that physical write has been completed, one of those transactions does a write which prepares the whole group. Note that this group prepare will only bring benefit if there are > 2 users in the database. Then at least 2 users can gather behind one doing the physical log write to disk. TODO: find out if MySQL holds some mutex when calling this. That would spoil our group prepare algorithm. */ mutex_exit(&kernel_mutex); if (srv_flush_log_at_trx_commit == 0) { /* Do nothing */ } else if (srv_flush_log_at_trx_commit == 1) { if (srv_unix_file_flush_method == SRV_UNIX_NOSYNC) { /* Write the log but do not flush it to disk */ log_write_up_to(lsn, LOG_WAIT_ONE_GROUP, FALSE); } else { /* Write the log to the log files AND flush them to disk */ log_write_up_to(lsn, LOG_WAIT_ONE_GROUP, TRUE); } } else if (srv_flush_log_at_trx_commit == 2) { /* Write the log but do not flush it to disk */ log_write_up_to(lsn, LOG_WAIT_ONE_GROUP, FALSE); } else { ut_error; } mutex_enter(&kernel_mutex); } }
/********************************************************//** Parses a log record written by mlog_open_and_write_index. @return parsed record end, NULL if not a complete record */ UNIV_INTERN byte* mlog_parse_index( /*=============*/ byte* ptr, /*!< in: buffer */ const byte* end_ptr,/*!< in: buffer end */ ibool comp, /*!< in: TRUE=compact record format */ dict_index_t** index) /*!< out, own: dummy index */ { ulint i, n, n_uniq; dict_table_t* table; dict_index_t* ind; ut_ad(comp == FALSE || comp == TRUE); if (comp) { if (end_ptr < ptr + 4) { return(NULL); } n = mach_read_from_2(ptr); ptr += 2; n_uniq = mach_read_from_2(ptr); ptr += 2; ut_ad(n_uniq <= n); if (end_ptr < ptr + n * 2) { return(NULL); } } else { n = n_uniq = 1; } table = dict_mem_table_create("LOG_DUMMY", DICT_HDR_SPACE, n, comp ? DICT_TF_COMPACT : 0); ind = dict_mem_index_create("LOG_DUMMY", "LOG_DUMMY", DICT_HDR_SPACE, 0, n); ind->table = table; ind->n_uniq = (unsigned int) n_uniq; if (n_uniq != n) { ut_a(n_uniq + DATA_ROLL_PTR <= n); ind->type = DICT_CLUSTERED; } if (comp) { for (i = 0; i < n; i++) { ulint len = mach_read_from_2(ptr); ptr += 2; /* The high-order bit of len is the NOT NULL flag; the rest is 0 or 0x7fff for variable-length fields, and 1..0x7ffe for fixed-length fields. */ dict_mem_table_add_col( table, NULL, NULL, ((len + 1) & 0x7fff) <= 1 ? DATA_BINARY : DATA_FIXBINARY, len & 0x8000 ? DATA_NOT_NULL : 0, len & 0x7fff); dict_index_add_col(ind, table, dict_table_get_nth_col(table, i), 0); } dict_table_add_system_columns(table, table->heap); if (n_uniq != n) { /* Identify DB_TRX_ID and DB_ROLL_PTR in the index. */ ut_a(DATA_TRX_ID_LEN == dict_index_get_nth_col(ind, DATA_TRX_ID - 1 + n_uniq)->len); ut_a(DATA_ROLL_PTR_LEN == dict_index_get_nth_col(ind, DATA_ROLL_PTR - 1 + n_uniq)->len); ind->fields[DATA_TRX_ID - 1 + n_uniq].col = &table->cols[n + DATA_TRX_ID]; ind->fields[DATA_ROLL_PTR - 1 + n_uniq].col = &table->cols[n + DATA_ROLL_PTR]; } } /* avoid ut_ad(index->cached) in dict_index_get_n_unique_in_tree */ ind->cached = TRUE; *index = ind; return(ptr); }
/**********************************************************************//** This function is used to find number of prepared transactions and their transaction objects for a recovery. @return number of prepared transactions stored in xid_list */ UNIV_INTERN int trx_recover_for_mysql( /*==================*/ XID* xid_list, /*!< in/out: prepared transactions */ ulint len) /*!< in: number of slots in xid_list */ { trx_t* trx; ulint count = 0; ut_ad(xid_list); ut_ad(len); /* We should set those transactions which are in the prepared state to the xid_list */ mutex_enter(&kernel_mutex); trx = UT_LIST_GET_FIRST(trx_sys->trx_list); while (trx) { if (trx->conc_state == TRX_PREPARED) { xid_list[count] = trx->xid; if (count == 0) { ut_print_timestamp(stderr); fprintf(stderr, " InnoDB: Starting recovery for" " XA transactions...\n"); } ut_print_timestamp(stderr); fprintf(stderr, " InnoDB: Transaction " TRX_ID_FMT " in" " prepared state after recovery\n", TRX_ID_PREP_PRINTF(trx->id)); ut_print_timestamp(stderr); fprintf(stderr, " InnoDB: Transaction contains changes" " to %lu rows\n", (ulong) ut_conv_dulint_to_longlong( trx->undo_no)); count++; if (count == len) { break; } } trx = UT_LIST_GET_NEXT(trx_list, trx); } mutex_exit(&kernel_mutex); if (count > 0){ ut_print_timestamp(stderr); fprintf(stderr, " InnoDB: %lu transactions in prepared state" " after recovery\n", (ulong) count); } return ((int) count); }