/***************************************************************************** Copyright (c) 1996, 2011, Oracle and/or its affiliates. All Rights Reserved. This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; version 2 of the License. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Suite 500, Boston, MA 02110-1335 USA *****************************************************************************/ /**************************************************//** @file include/trx0sys.ic Transaction system Created 3/26/1996 Heikki Tuuri *******************************************************/ #include "trx0trx.h" #include "data0type.h" #ifndef UNIV_HOTBACKUP # include "srv0srv.h" # include "mtr0log.h" /* The typedef for rseg slot in the file copy */ typedef byte trx_sysf_rseg_t; /* Rollback segment specification slot offsets */ /*-------------------------------------------------------------*/ #define TRX_SYS_RSEG_SPACE 0 /* space where the segment header is placed; starting with MySQL/InnoDB 5.1.7, this is UNIV_UNDEFINED if the slot is unused */ #define TRX_SYS_RSEG_PAGE_NO 4 /* page number where the segment header is placed; this is FIL_NULL if the slot is unused */ /*-------------------------------------------------------------*/ /* Size of a rollback segment specification slot */ #define TRX_SYS_RSEG_SLOT_SIZE 8 /*****************************************************************//** Writes the value of max_trx_id to the file based trx system header. */ UNIV_INTERN void trx_sys_flush_max_trx_id(void); /*==========================*/ /***************************************************************//** Checks if a page address is the trx sys header page. @return TRUE if trx sys header page */ UNIV_INLINE ibool trx_sys_hdr_page( /*=============*/ ulint space, /*!< in: space */ ulint page_no)/*!< in: page number */ { if ((space == TRX_SYS_SPACE) && (page_no == TRX_SYS_PAGE_NO)) { return(TRUE); } return(FALSE); } /***************************************************************//** Gets the pointer in the nth slot of the rseg array. @return pointer to rseg object, NULL if slot not in use */ UNIV_INLINE trx_rseg_t* trx_sys_get_nth_rseg( /*=================*/ trx_sys_t* sys, /*!< in: trx system */ ulint n) /*!< in: index of slot */ { ut_ad(n < TRX_SYS_N_RSEGS); return(sys->rseg_array[n]); } /**********************************************************************//** Gets a pointer to the transaction system header and x-latches its page. @return pointer to system header, page x-latched. */ UNIV_INLINE trx_sysf_t* trx_sysf_get( /*=========*/ mtr_t* mtr) /*!< in: mtr */ { buf_block_t* block; trx_sysf_t* header; ut_ad(mtr); block = buf_page_get(TRX_SYS_SPACE, 0, TRX_SYS_PAGE_NO, RW_X_LATCH, mtr); buf_block_dbg_add_level(block, SYNC_TRX_SYS_HEADER); header = TRX_SYS + buf_block_get_frame(block); return(header); } /*****************************************************************//** Gets the space of the nth rollback segment slot in the trx system file copy. @return space id */ UNIV_INLINE ulint trx_sysf_rseg_get_space( /*====================*/ trx_sysf_t* sys_header, /*!< in: trx sys header */ ulint i, /*!< in: slot index == rseg id */ mtr_t* mtr) /*!< in: mtr */ { ut_ad(sys_header); ut_ad(i < TRX_SYS_N_RSEGS); return(mtr_read_ulint(sys_header + TRX_SYS_RSEGS + i * TRX_SYS_RSEG_SLOT_SIZE + TRX_SYS_RSEG_SPACE, MLOG_4BYTES, mtr)); } /*****************************************************************//** Gets the page number of the nth rollback segment slot in the trx system header. @return page number, FIL_NULL if slot unused */ UNIV_INLINE ulint trx_sysf_rseg_get_page_no( /*======================*/ trx_sysf_t* sys_header, /*!< in: trx system header */ ulint i, /*!< in: slot index == rseg id */ mtr_t* mtr) /*!< in: mtr */ { ut_ad(sys_header); ut_ad(i < TRX_SYS_N_RSEGS); return(mtr_read_ulint(sys_header + TRX_SYS_RSEGS + i * TRX_SYS_RSEG_SLOT_SIZE + TRX_SYS_RSEG_PAGE_NO, MLOG_4BYTES, mtr)); } /*****************************************************************//** Sets the space id of the nth rollback segment slot in the trx system file copy. */ UNIV_INLINE void trx_sysf_rseg_set_space( /*====================*/ trx_sysf_t* sys_header, /*!< in: trx sys file copy */ ulint i, /*!< in: slot index == rseg id */ ulint space, /*!< in: space id */ mtr_t* mtr) /*!< in: mtr */ { ut_ad(sys_header); ut_ad(i < TRX_SYS_N_RSEGS); mlog_write_ulint(sys_header + TRX_SYS_RSEGS + i * TRX_SYS_RSEG_SLOT_SIZE + TRX_SYS_RSEG_SPACE, space, MLOG_4BYTES, mtr); } /*****************************************************************//** Sets the page number of the nth rollback segment slot in the trx system header. */ UNIV_INLINE void trx_sysf_rseg_set_page_no( /*======================*/ trx_sysf_t* sys_header, /*!< in: trx sys header */ ulint i, /*!< in: slot index == rseg id */ ulint page_no, /*!< in: page number, FIL_NULL if the slot is reset to unused */ mtr_t* mtr) /*!< in: mtr */ { ut_ad(sys_header); ut_ad(i < TRX_SYS_N_RSEGS); mlog_write_ulint(sys_header + TRX_SYS_RSEGS + i * TRX_SYS_RSEG_SLOT_SIZE + TRX_SYS_RSEG_PAGE_NO, page_no, MLOG_4BYTES, mtr); } #endif /* !UNIV_HOTBACKUP */ /*****************************************************************//** Writes a trx id to an index page. In case that the id size changes in some future version, this function should be used instead of mach_write_... */ UNIV_INLINE void trx_write_trx_id( /*=============*/ byte* ptr, /*!< in: pointer to memory where written */ trx_id_t id) /*!< in: id */ { #if DATA_TRX_ID_LEN != 6 # error "DATA_TRX_ID_LEN != 6" #endif mach_write_to_6(ptr, id); } #ifndef UNIV_HOTBACKUP /*****************************************************************//** Reads a trx id from an index page. In case that the id size changes in some future version, this function should be used instead of mach_read_... @return id */ UNIV_INLINE trx_id_t trx_read_trx_id( /*============*/ const byte* ptr) /*!< in: pointer to memory from where to read */ { #if DATA_TRX_ID_LEN != 6 # error "DATA_TRX_ID_LEN != 6" #endif return(mach_read_from_6(ptr)); } /****************************************************************//** Looks for the trx handle with the given id in rw_trx_list. The caller must be holding trx_sys->mutex. @return the trx handle or NULL if not found; the pointer must not be dereferenced unless lock_sys->mutex was acquired before calling this function and is still being held */ UNIV_INLINE trx_t* trx_get_rw_trx_by_id( /*=================*/ trx_id_t trx_id) /*!< in: trx id to search for */ { trx_t* trx; ulint len; trx_t* first; ut_ad(mutex_own(&trx_sys->mutex)); len = UT_LIST_GET_LEN(trx_sys->rw_trx_list); if (len == 0) { return(NULL); } /* Because the list is ordered on trx id in descending order, we try to speed things up a bit. */ trx = UT_LIST_GET_FIRST(trx_sys->rw_trx_list); assert_trx_in_rw_list(trx); if (trx_id == trx->id) { return(trx); } else if (len == 1 || trx_id > trx->id) { return(NULL); } first = trx; trx = UT_LIST_GET_LAST(trx_sys->rw_trx_list); assert_trx_in_rw_list(trx); if (trx_id == trx->id) { return(trx); } else if (len == 2 || trx_id < trx->id) { return(NULL); } /* Search the list from the lower end (tail). */ if (trx_id < (first->id + trx->id) >> 1) { for (trx = UT_LIST_GET_PREV(trx_list, trx); trx != NULL && trx_id > trx->id; trx = UT_LIST_GET_PREV(trx_list, trx)) { assert_trx_in_rw_list(trx); } } else { for (trx = UT_LIST_GET_NEXT(trx_list, first); trx != NULL && trx_id < trx->id; trx = UT_LIST_GET_NEXT(trx_list, trx)) { assert_trx_in_rw_list(trx); } } return((trx != NULL && trx->id == trx_id) ? trx : NULL); } /****************************************************************//** Returns the minimum trx id in trx list. This is the smallest id for which the trx can possibly be active. (But, you must look at the trx->state to find out if the minimum trx id transaction itself is active, or already committed.). The caller must be holding the trx_sys_t::mutex in shared mode. @return the minimum trx id, or trx_sys->max_trx_id if the trx list is empty */ UNIV_INLINE trx_id_t trx_rw_min_trx_id_low(void) /*=======================*/ { trx_id_t id; const trx_t* trx; ut_ad(mutex_own(&trx_sys->mutex)); trx = UT_LIST_GET_LAST(trx_sys->rw_trx_list); if (trx == NULL) { id = trx_sys->max_trx_id; } else { assert_trx_in_rw_list(trx); id = trx->id; } return(id); } #if defined UNIV_DEBUG || defined UNIV_BLOB_LIGHT_DEBUG /***********************************************************//** Assert that a transaction has been recovered. @return TRUE */ UNIV_INLINE ibool trx_assert_recovered( /*=================*/ trx_id_t trx_id) /*!< in: transaction identifier */ { const trx_t* trx; mutex_enter(&trx_sys->mutex); trx = trx_get_rw_trx_by_id(trx_id); ut_a(trx->is_recovered); mutex_exit(&trx_sys->mutex); return(TRUE); } #endif /* UNIV_DEBUG || UNIV_BLOB_LIGHT_DEBUG */ /****************************************************************//** Returns the minimum trx id in rw trx list. This is the smallest id for which the rw trx can possibly be active. (But, you must look at the trx->state to find out if the minimum trx id transaction itself is active, or already committed.) @return the minimum trx id, or trx_sys->max_trx_id if rw trx list is empty */ UNIV_INLINE trx_id_t trx_rw_min_trx_id(void) /*===================*/ { trx_id_t id; mutex_enter(&trx_sys->mutex); id = trx_rw_min_trx_id_low(); mutex_exit(&trx_sys->mutex); return(id); } /****************************************************************//** Returns pointer to a transaction instance if a rw transaction with the given id is active. Caller must hold trx_sys->mutex. If the caller is not holding lock_sys->mutex, the transaction may already have been committed. @return transaction instance if active, or NULL; the pointer must not be dereferenced unless lock_sys->mutex was acquired before calling this function and is still being held */ UNIV_INLINE trx_t* trx_rw_get_active_trx_by_id( /*========================*/ trx_id_t trx_id, /*!< in: trx id of the transaction */ ibool* corrupt) /*!< in: NULL or pointer to a flag that will be set if corrupt */ { trx_t* trx; ut_ad(mutex_own(&trx_sys->mutex)); if (trx_id < trx_rw_min_trx_id_low()) { trx = NULL; } else if (trx_id >= trx_sys->max_trx_id) { /* There must be corruption: we let the caller handle the diagnostic prints in this case. */ trx = NULL; if (corrupt != NULL) { *corrupt = TRUE; } } else { trx = trx_get_rw_trx_by_id(trx_id); if (trx != NULL && trx_state_eq(trx, TRX_STATE_COMMITTED_IN_MEMORY)) { trx = NULL; } } return(trx); } /****************************************************************//** Checks if a rw transaction with the given id is active. Caller must hold trx_sys->mutex. If the caller is not holding lock_sys->mutex, the transaction may already have been committed. @return true if rw transaction it with a given id is active. */ UNIV_INLINE bool trx_rw_is_active_low( /*=================*/ trx_id_t trx_id, /*!< in: trx id of the transaction */ ibool* corrupt) /*!< in: NULL or pointer to a flag that will be set if corrupt */ { ut_ad(mutex_own(&trx_sys->mutex)); if (UNIV_UNLIKELY(trx_id >= trx_sys->max_trx_id)) { /* There must be corruption: we let the caller handle the diagnostic prints in this case. */ if (corrupt != NULL) { *corrupt = TRUE; } return(false); } return(trx_find_descriptor(trx_sys->descriptors, trx_sys->descr_n_used, trx_id) != NULL); } /****************************************************************//** Checks if a rw transaction with the given id is active. If the caller is not holding lock_sys->mutex, the transaction may already have been committed. @return true if rw transaction it with a given id is active. */ UNIV_INLINE bool trx_rw_is_active( /*=============*/ trx_id_t trx_id, /*!< in: trx id of the transaction */ ibool* corrupt) /*!< in: NULL or pointer to a flag that will be set if corrupt */ { bool res; mutex_enter(&trx_sys->mutex); res = trx_rw_is_active_low(trx_id, corrupt); mutex_exit(&trx_sys->mutex); return(res); } /*****************************************************************//** Allocates a new transaction id. @return new, allocated trx id */ UNIV_INLINE trx_id_t trx_sys_get_new_trx_id(void) /*========================*/ { ut_ad(mutex_own(&trx_sys->mutex)); /* VERY important: after the database is started, max_trx_id value is divisible by TRX_SYS_TRX_ID_WRITE_MARGIN, and the following if will evaluate to TRUE when this function is first time called, and the value for trx id will be written to disk-based header! Thus trx id values will not overlap when the database is repeatedly started! */ if (!(trx_sys->max_trx_id % (trx_id_t) TRX_SYS_TRX_ID_WRITE_MARGIN)) { trx_sys_flush_max_trx_id(); } return(trx_sys->max_trx_id++); } /*****************************************************************//** Determines the maximum transaction id. @return maximum currently allocated trx id; will be stale after the next call to trx_sys_get_new_trx_id() */ UNIV_INLINE trx_id_t trx_sys_get_max_trx_id(void) /*========================*/ { #if UNIV_WORD_SIZE < DATA_TRX_ID_LEN trx_id_t max_trx_id; #endif ut_ad(!mutex_own(&trx_sys->mutex)); #if UNIV_WORD_SIZE < DATA_TRX_ID_LEN /* Avoid torn reads. */ mutex_enter(&trx_sys->mutex); max_trx_id = trx_sys->max_trx_id; mutex_exit(&trx_sys->mutex); return(max_trx_id); #else /* Perform a dirty read. Callers should be prepared for stale values, and we know that the value fits in a machine word, so that it will be read and written atomically. */ return(trx_sys->max_trx_id); #endif } /*****************************************************************//** Get the number of transaction in the system, independent of their state. @return count of transactions in trx_sys_t::rw_trx_list */ UNIV_INLINE ulint trx_sys_get_n_rw_trx(void) /*======================*/ { ulint n_trx; mutex_enter(&trx_sys->mutex); n_trx = UT_LIST_GET_LEN(trx_sys->rw_trx_list); mutex_exit(&trx_sys->mutex); return(n_trx); } /*************************************************************//** Find a slot for a given trx ID in a descriptors array. @return: slot pointer */ UNIV_INLINE trx_id_t* trx_find_descriptor( /*================*/ const trx_id_t* descriptors, /*!< in: descriptors array */ ulint n_descr, /*!< in: array size */ trx_id_t trx_id) /*!< in: trx id */ { ut_ad(descriptors != trx_sys->descriptors || mutex_own(&trx_sys->mutex)); if (UNIV_UNLIKELY(n_descr == 0)) { return(NULL); } return((trx_id_t *) bsearch(&trx_id, descriptors, n_descr, sizeof(trx_id_t), trx_descr_cmp)); } #endif /* !UNIV_HOTBACKUP */