/***********************************************************************//** Updates the last not yet purged history log info in rseg when we have purged a whole undo log. Advances also purge_sys->purge_trx_no past the purged log. */ static void trx_purge_rseg_get_next_history_log( /*================================*/ trx_rseg_t* rseg) /*!< in: rollback segment */ { page_t* undo_page; trx_ulogf_t* log_hdr; fil_addr_t prev_log_addr; trx_id_t trx_no; ibool del_marks; mtr_t mtr; rseg_queue_t rseg_queue; const void* ptr; mutex_enter(&(rseg->mutex)); ut_a(rseg->last_page_no != FIL_NULL); purge_sys->purge_trx_no = rseg->last_trx_no + 1; purge_sys->purge_undo_no = 0; purge_sys->next_stored = FALSE; mtr_start(&mtr); undo_page = trx_undo_page_get_s_latched( rseg->space, rseg->zip_size, rseg->last_page_no, &mtr); log_hdr = undo_page + rseg->last_offset; /* Increase the purge page count by one for every handled log */ purge_sys->n_pages_handled++; prev_log_addr = trx_purge_get_log_from_hist( flst_get_prev_addr(log_hdr + TRX_UNDO_HISTORY_NODE, &mtr)); if (prev_log_addr.page == FIL_NULL) { /* No logs left in the history list */ rseg->last_page_no = FIL_NULL; mutex_exit(&(rseg->mutex)); mtr_commit(&mtr); mutex_enter(&kernel_mutex); /* Add debug code to track history list corruption reported on the MySQL mailing list on Nov 9, 2004. The fut0lst.c file-based list was corrupt. The prev node pointer was FIL_NULL, even though the list length was over 8 million nodes! We assume that purge truncates the history list in large size pieces, and if we here reach the head of the list, the list cannot be longer than 2000 000 undo logs now. */ if (trx_sys->rseg_history_len > 2000000) { ut_print_timestamp(stderr); fprintf(stderr, " InnoDB: Warning: purge reached the" " head of the history list,\n" "InnoDB: but its length is still" " reported as %lu! Make a detailed bug\n" "InnoDB: report, and submit it" " to http://bugs.mysql.com\n", (ulong) trx_sys->rseg_history_len); } mutex_exit(&kernel_mutex); return; } mutex_exit(&(rseg->mutex)); mtr_commit(&mtr); /* Read the trx number and del marks from the previous log header */ mtr_start(&mtr); log_hdr = trx_undo_page_get_s_latched(rseg->space, rseg->zip_size, prev_log_addr.page, &mtr) + prev_log_addr.boffset; trx_no = mach_read_from_8(log_hdr + TRX_UNDO_TRX_NO); del_marks = mach_read_from_2(log_hdr + TRX_UNDO_DEL_MARKS); mtr_commit(&mtr); mutex_enter(&(rseg->mutex)); rseg->last_page_no = prev_log_addr.page; rseg->last_offset = prev_log_addr.boffset; rseg->last_trx_no = trx_no; rseg->last_del_marks = del_marks; rseg_queue.rseg = rseg; rseg_queue.trx_no = rseg->last_trx_no; /* Purge can also produce events, however these are already ordered in the rollback segment and any user generated event will be greater than the events that Purge produces. ie. Purge can never produce events from an empty rollback segment. */ mutex_enter(&purge_sys->bh_mutex); ptr = ib_bh_push(purge_sys->ib_bh, &rseg_queue); ut_a(ptr != NULL); mutex_exit(&purge_sys->bh_mutex); mutex_exit(&(rseg->mutex)); }
/*************************************************************************** Creates and initializes a rollback segment object. The values for the fields are read from the header. The object is inserted to the rseg list of the trx system object and a pointer is inserted in the rseg array in the trx system object. @return own: rollback segment object */ static trx_rseg_t* trx_rseg_mem_create( /*================*/ ulint id, /*!< in: rollback segment id */ ulint space, /*!< in: space where the segment placed */ ulint zip_size, /*!< in: compressed page size in bytes or 0 for uncompressed pages */ ulint page_no, /*!< in: page number of the segment header */ mtr_t* mtr) /*!< in: mtr */ { ulint len; trx_rseg_t* rseg; fil_addr_t node_addr; trx_rsegf_t* rseg_header; trx_ulogf_t* undo_log_hdr; ulint sum_of_undo_sizes; ut_ad(mutex_own(&kernel_mutex)); rseg = mem_zalloc(sizeof(trx_rseg_t)); rseg->id = id; rseg->space = space; rseg->zip_size = zip_size; rseg->page_no = page_no; mutex_create(rseg_mutex_key, &rseg->mutex, SYNC_RSEG); UT_LIST_ADD_LAST(rseg_list, trx_sys->rseg_list, rseg); trx_sys_set_nth_rseg(trx_sys, id, rseg); rseg_header = trx_rsegf_get_new(space, zip_size, page_no, mtr); rseg->max_size = mtr_read_ulint(rseg_header + TRX_RSEG_MAX_SIZE, MLOG_4BYTES, mtr); /* Initialize the undo log lists according to the rseg header */ sum_of_undo_sizes = trx_undo_lists_init(rseg); rseg->curr_size = mtr_read_ulint(rseg_header + TRX_RSEG_HISTORY_SIZE, MLOG_4BYTES, mtr) + 1 + sum_of_undo_sizes; len = flst_get_len(rseg_header + TRX_RSEG_HISTORY, mtr); if (len > 0) { trx_sys->rseg_history_len += len; node_addr = trx_purge_get_log_from_hist( flst_get_last(rseg_header + TRX_RSEG_HISTORY, mtr)); rseg->last_page_no = node_addr.page; rseg->last_offset = node_addr.boffset; undo_log_hdr = trx_undo_page_get(rseg->space, rseg->zip_size, node_addr.page, mtr) + node_addr.boffset; rseg->last_trx_no = mtr_read_dulint( undo_log_hdr + TRX_UNDO_TRX_NO, mtr); rseg->last_del_marks = mtr_read_ulint( undo_log_hdr + TRX_UNDO_DEL_MARKS, MLOG_2BYTES, mtr); } else { rseg->last_page_no = FIL_NULL; } return(rseg); }
/********************************************************************//** Removes unnecessary history data from a rollback segment. */ static void trx_purge_truncate_rseg_history( /*============================*/ trx_rseg_t* rseg, /*!< in: rollback segment */ trx_id_t limit_trx_no, /*!< in: remove update undo logs whose trx number is < limit_trx_no */ undo_no_t limit_undo_no) /*!< in: if transaction number is equal to limit_trx_no, truncate undo records with undo number < limit_undo_no */ { fil_addr_t hdr_addr; fil_addr_t prev_hdr_addr; trx_rsegf_t* rseg_hdr; page_t* undo_page; trx_ulogf_t* log_hdr; trx_usegf_t* seg_hdr; ulint n_removed_logs = 0; mtr_t mtr; trx_id_t undo_trx_no; mtr_start(&mtr); mutex_enter(&(rseg->mutex)); rseg_hdr = trx_rsegf_get(rseg->space, rseg->zip_size, rseg->page_no, &mtr); hdr_addr = trx_purge_get_log_from_hist( flst_get_last(rseg_hdr + TRX_RSEG_HISTORY, &mtr)); loop: if (hdr_addr.page == FIL_NULL) { mutex_exit(&(rseg->mutex)); mtr_commit(&mtr); return; } undo_page = trx_undo_page_get(rseg->space, rseg->zip_size, hdr_addr.page, &mtr); log_hdr = undo_page + hdr_addr.boffset; undo_trx_no = mach_read_from_8(log_hdr + TRX_UNDO_TRX_NO); if (undo_trx_no >= limit_trx_no) { if (undo_trx_no == limit_trx_no) { trx_undo_truncate_start(rseg, rseg->space, hdr_addr.page, hdr_addr.boffset, limit_undo_no); } mutex_enter(&kernel_mutex); ut_a(trx_sys->rseg_history_len >= n_removed_logs); trx_sys->rseg_history_len -= n_removed_logs; mutex_exit(&kernel_mutex); flst_truncate_end(rseg_hdr + TRX_RSEG_HISTORY, log_hdr + TRX_UNDO_HISTORY_NODE, n_removed_logs, &mtr); mutex_exit(&(rseg->mutex)); mtr_commit(&mtr); return; } prev_hdr_addr = trx_purge_get_log_from_hist( flst_get_prev_addr(log_hdr + TRX_UNDO_HISTORY_NODE, &mtr)); n_removed_logs++; seg_hdr = undo_page + TRX_UNDO_SEG_HDR; if ((mach_read_from_2(seg_hdr + TRX_UNDO_STATE) == TRX_UNDO_TO_PURGE) && (mach_read_from_2(log_hdr + TRX_UNDO_NEXT_LOG) == 0)) { /* We can free the whole log segment */ mutex_exit(&(rseg->mutex)); mtr_commit(&mtr); trx_purge_free_segment(rseg, hdr_addr, n_removed_logs); n_removed_logs = 0; } else { mutex_exit(&(rseg->mutex)); mtr_commit(&mtr); } mtr_start(&mtr); mutex_enter(&(rseg->mutex)); rseg_hdr = trx_rsegf_get(rseg->space, rseg->zip_size, rseg->page_no, &mtr); hdr_addr = prev_hdr_addr; goto loop; }
/*************************************************************************** Updates the last not yet purged history log info in rseg when we have purged a whole undo log. Advances also purge_sys->purge_trx_no past the purged log. */ static void trx_purge_rseg_get_next_history_log( /*================================*/ trx_rseg_t* rseg) /* in: rollback segment */ { page_t* undo_page; trx_ulogf_t* log_hdr; trx_usegf_t* seg_hdr; fil_addr_t prev_log_addr; dulint trx_no; ibool del_marks; mtr_t mtr; ut_ad(mutex_own(&(purge_sys->mutex))); mutex_enter(&(rseg->mutex)); ut_a(rseg->last_page_no != FIL_NULL); purge_sys->purge_trx_no = ut_dulint_add(rseg->last_trx_no, 1); purge_sys->purge_undo_no = ut_dulint_zero; purge_sys->next_stored = FALSE; mtr_start(&mtr); undo_page = trx_undo_page_get_s_latched(rseg->space, rseg->last_page_no, &mtr); log_hdr = undo_page + rseg->last_offset; seg_hdr = undo_page + TRX_UNDO_SEG_HDR; /* Increase the purge page count by one for every handled log */ purge_sys->n_pages_handled++; prev_log_addr = trx_purge_get_log_from_hist( flst_get_prev_addr(log_hdr + TRX_UNDO_HISTORY_NODE, &mtr)); if (prev_log_addr.page == FIL_NULL) { /* No logs left in the history list */ rseg->last_page_no = FIL_NULL; mutex_exit(&(rseg->mutex)); mtr_commit(&mtr); mutex_enter(&kernel_mutex); /* Add debug code to track history list corruption reported on the MySQL mailing list on Nov 9, 2004. The fut0lst.c file-based list was corrupt. The prev node pointer was FIL_NULL, even though the list length was over 8 million nodes! We assume that purge truncates the history list in moderate size pieces, and if we here reach the head of the list, the list cannot be longer than 20 000 undo logs now. */ if (trx_sys->rseg_history_len > 20000) { ut_print_timestamp(stderr); fprintf(stderr, " InnoDB: Warning: purge reached the" " head of the history list,\n" "InnoDB: but its length is still" " reported as %lu! Make a detailed bug\n" "InnoDB: report, and submit it" " to http://bugs.mysql.com\n", (ulong) trx_sys->rseg_history_len); } mutex_exit(&kernel_mutex); return; } mutex_exit(&(rseg->mutex)); mtr_commit(&mtr); /* Read the trx number and del marks from the previous log header */ mtr_start(&mtr); log_hdr = trx_undo_page_get_s_latched(rseg->space, prev_log_addr.page, &mtr) + prev_log_addr.boffset; trx_no = mach_read_from_8(log_hdr + TRX_UNDO_TRX_NO); del_marks = mach_read_from_2(log_hdr + TRX_UNDO_DEL_MARKS); mtr_commit(&mtr); mutex_enter(&(rseg->mutex)); rseg->last_page_no = prev_log_addr.page; rseg->last_offset = prev_log_addr.boffset; rseg->last_trx_no = trx_no; rseg->last_del_marks = del_marks; mutex_exit(&(rseg->mutex)); }