mirror of
https://github.com/MariaDB/server.git
synced 2025-01-31 02:51:44 +01:00
0b47c126e3
The approach to handling corruption that was chosen by Oracle in
commit 177d8b0c12
is not really useful. Not only did it actually fail to prevent InnoDB
from crashing, but it is making things worse by blocking attempts to
rescue data from or rebuild a partially readable table.
We will try to prevent crashes in a different way: by propagating
errors up the call stack. We will never mark the clustered index
persistently corrupted, so that data recovery may be attempted by
reading from the table, or by rebuilding the table.
This should also fix MDEV-13680 (crash on btr_page_alloc() failure);
it was extensively tested with innodb_file_per_table=0 and a
non-autoextend system tablespace.
We should now avoid crashes in many cases, such as when a page
cannot be read or allocated, or an inconsistency is detected when
attempting to update multiple pages. We will not crash on double-free,
such as on the recovery of DDL in system tablespace in case something
was corrupted.
Crashes on corrupted data are still possible. The fault injection mechanism
that is introduced in the subsequent commit may help catch more of them.
buf_page_import_corrupt_failure: Remove the fault injection, and instead
corrupt some pages using Perl code in the tests.
btr_cur_pessimistic_insert(): Always reserve extents (except for the
change buffer), in order to prevent a subsequent allocation failure.
btr_pcur_open_at_rnd_pos(): Merged to the only caller ibuf_merge_pages().
btr_assert_not_corrupted(), btr_corruption_report(): Remove.
Similar checks are already part of btr_block_get().
FSEG_MAGIC_N_BYTES: Replaces FSEG_MAGIC_N_VALUE.
dict_hdr_get(), trx_rsegf_get_new(), trx_undo_page_get(),
trx_undo_page_get_s_latched(): Replaced with error-checking calls.
trx_rseg_t::get(mtr_t*): Replaces trx_rsegf_get().
trx_rseg_header_create(): Let the caller update the TRX_SYS page if needed.
trx_sys_create_sys_pages(): Merged with trx_sysf_create().
dict_check_tablespaces_and_store_max_id(): Do not access
DICT_HDR_MAX_SPACE_ID, because it was already recovered in dict_boot().
Merge dict_check_sys_tables() with this function.
dir_pathname(): Replaces os_file_make_new_pathname().
row_undo_ins_remove_sec(): Do not modify the undo page by adding
a terminating NUL byte to the record.
btr_decryption_failed(): Report decryption failures
dict_set_corrupted_by_space(), dict_set_encrypted_by_space(),
dict_set_corrupted_index_cache_only(): Remove.
dict_set_corrupted(): Remove the constant parameter dict_locked=false.
Never flag the clustered index corrupted in SYS_INDEXES, because
that would deny further access to the table. It might be possible to
repair the table by executing ALTER TABLE or OPTIMIZE TABLE, in case
no B-tree leaf page is corrupted.
dict_table_skip_corrupt_index(), dict_table_next_uncorrupted_index(),
row_purge_skip_uncommitted_virtual_index(): Remove, and refactor
the callers to read dict_index_t::type only once.
dict_table_is_corrupted(): Remove.
dict_index_t::is_btree(): Determine if the index is a valid B-tree.
BUF_GET_NO_LATCH, BUF_EVICT_IF_IN_POOL: Remove.
UNIV_BTR_DEBUG: Remove. Any inconsistency will no longer trigger
assertion failures, but error codes being returned.
buf_corrupt_page_release(): Replaced with a direct call to
buf_pool.corrupted_evict().
fil_invalid_page_access_msg(): Never crash on an invalid read;
let the caller of buf_page_get_gen() decide.
btr_pcur_t::restore_position(): Propagate failure status to the caller
by returning CORRUPTED.
opt_search_plan_for_table(): Simplify the code.
row_purge_del_mark(), row_purge_upd_exist_or_extern_func(),
row_undo_ins_remove_sec_rec(), row_undo_mod_upd_del_sec(),
row_undo_mod_del_mark_sec(): Avoid mem_heap_create()/mem_heap_free()
when no secondary indexes exist.
row_undo_mod_upd_exist_sec(): Simplify the code.
row_upd_clust_step(), dict_load_table_one(): Return DB_TABLE_CORRUPT
if the clustered index (and therefore the table) is corrupted, similar
to what we do in row_insert_for_mysql().
fut_get_ptr(): Replace with buf_page_get_gen() calls.
buf_page_get_gen(): Return nullptr and *err=DB_CORRUPTION
if the page is marked as freed. For other modes than
BUF_GET_POSSIBLY_FREED or BUF_PEEK_IF_IN_POOL this will
trigger a debug assertion failure. For BUF_GET_POSSIBLY_FREED,
we will return nullptr for freed pages, so that the callers
can be simplified. The purge of transaction history will be
a new user of BUF_GET_POSSIBLY_FREED, to avoid crashes on
corrupted data.
buf_page_get_low(): Never crash on a corrupted page, but simply
return nullptr.
fseg_page_is_allocated(): Replaces fseg_page_is_free().
fts_drop_common_tables(): Return an error if the transaction
was rolled back.
fil_space_t::set_corrupted(): Report a tablespace as corrupted if
it was not reported already.
fil_space_t::io(): Invoke fil_space_t::set_corrupted() to report
out-of-bounds page access or other errors.
Clean up mtr_t::page_lock()
buf_page_get_low(): Validate the page identifier (to check for
recently read corrupted pages) after acquiring the page latch.
buf_page_t::read_complete(): Flag uninitialized (all-zero) pages
with DB_FAIL. Return DB_PAGE_CORRUPTED on page number mismatch.
mtr_t::defer_drop_ahi(): Renamed from mtr_defer_drop_ahi().
recv_sys_t::free_corrupted_page(): Only set_corrupt_fs()
if any log records exist for the page. We do not mind if read-ahead
produces corrupted (or all-zero) pages that were not actually needed
during recovery.
recv_recover_page(): Return whether the operation succeeded.
recv_sys_t::recover_low(): Simplify the logic. Check for recovery error.
Thanks to Matthias Leich for testing this extensively and to the
authors of https://rr-project.org for making it easy to diagnose
and fix any failures that were found during the testing.
383 lines
14 KiB
C
383 lines
14 KiB
C
/*****************************************************************************
|
|
|
|
Copyright (c) 2005, 2016, Oracle and/or its affiliates. All Rights Reserved.
|
|
Copyright (c) 2012, Facebook Inc.
|
|
Copyright (c) 2017, 2022, MariaDB Corporation.
|
|
|
|
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, Fifth Floor, Boston, MA 02110-1335 USA
|
|
|
|
*****************************************************************************/
|
|
|
|
/**************************************************//**
|
|
@file include/page0zip.h
|
|
Compressed page interface
|
|
|
|
Created June 2005 by Marko Makela
|
|
*******************************************************/
|
|
|
|
#ifndef page0zip_h
|
|
#define page0zip_h
|
|
|
|
#include "buf0types.h"
|
|
|
|
#ifndef UNIV_INNOCHECKSUM
|
|
#include "mtr0types.h"
|
|
#include "page0types.h"
|
|
#include "dict0types.h"
|
|
#include "srv0srv.h"
|
|
#include "trx0types.h"
|
|
#include "mem0mem.h"
|
|
|
|
/* Compression level to be used by zlib. Settable by user. */
|
|
extern uint page_zip_level;
|
|
|
|
/* Default compression level. */
|
|
#define DEFAULT_COMPRESSION_LEVEL 6
|
|
/** Start offset of the area that will be compressed */
|
|
#define PAGE_ZIP_START PAGE_NEW_SUPREMUM_END
|
|
/** Size of an compressed page directory entry */
|
|
#define PAGE_ZIP_DIR_SLOT_SIZE 2
|
|
/** Predefine the sum of DIR_SLOT, TRX_ID & ROLL_PTR */
|
|
#define PAGE_ZIP_CLUST_LEAF_SLOT_SIZE \
|
|
(PAGE_ZIP_DIR_SLOT_SIZE \
|
|
+ DATA_TRX_ID_LEN \
|
|
+ DATA_ROLL_PTR_LEN)
|
|
/** Mask of record offsets */
|
|
#define PAGE_ZIP_DIR_SLOT_MASK 0x3fffU
|
|
/** 'owned' flag */
|
|
#define PAGE_ZIP_DIR_SLOT_OWNED 0x4000U
|
|
/** 'deleted' flag */
|
|
#define PAGE_ZIP_DIR_SLOT_DEL 0x8000U
|
|
|
|
/**********************************************************************//**
|
|
Determine the size of a compressed page in bytes.
|
|
@return size in bytes */
|
|
UNIV_INLINE
|
|
ulint
|
|
page_zip_get_size(
|
|
/*==============*/
|
|
const page_zip_des_t* page_zip) /*!< in: compressed page */
|
|
MY_ATTRIBUTE((warn_unused_result));
|
|
/**********************************************************************//**
|
|
Set the size of a compressed page in bytes. */
|
|
UNIV_INLINE
|
|
void
|
|
page_zip_set_size(
|
|
/*==============*/
|
|
page_zip_des_t* page_zip, /*!< in/out: compressed page */
|
|
ulint size); /*!< in: size in bytes */
|
|
|
|
/** Determine if a record is so big that it needs to be stored externally.
|
|
@param[in] rec_size length of the record in bytes
|
|
@param[in] comp nonzero=compact format
|
|
@param[in] n_fields number of fields in the record; ignored if
|
|
tablespace is not compressed
|
|
@param[in] zip_size ROW_FORMAT=COMPRESSED page size, or 0
|
|
@return false if the entire record can be stored locally on the page */
|
|
inline bool page_zip_rec_needs_ext(ulint rec_size, ulint comp, ulint n_fields,
|
|
ulint zip_size)
|
|
MY_ATTRIBUTE((warn_unused_result));
|
|
|
|
/**********************************************************************//**
|
|
Determine the guaranteed free space on an empty page.
|
|
@return minimum payload size on the page */
|
|
ulint
|
|
page_zip_empty_size(
|
|
/*================*/
|
|
ulint n_fields, /*!< in: number of columns in the index */
|
|
ulint zip_size) /*!< in: compressed page size in bytes */
|
|
MY_ATTRIBUTE((const));
|
|
|
|
/** Check whether a tuple is too big for compressed table
|
|
@param[in] index dict index object
|
|
@param[in] entry entry for the index
|
|
@return true if it's too big, otherwise false */
|
|
bool
|
|
page_zip_is_too_big(
|
|
const dict_index_t* index,
|
|
const dtuple_t* entry);
|
|
|
|
/**********************************************************************//**
|
|
Initialize a compressed page descriptor. */
|
|
#define page_zip_des_init(page_zip) (page_zip)->clear()
|
|
|
|
/**********************************************************************//**
|
|
Configure the zlib allocator to use the given memory heap. */
|
|
void
|
|
page_zip_set_alloc(
|
|
/*===============*/
|
|
void* stream, /*!< in/out: zlib stream */
|
|
mem_heap_t* heap); /*!< in: memory heap to use */
|
|
|
|
/** Attempt to compress a ROW_FORMAT=COMPRESSED page.
|
|
@retval true on success
|
|
@retval false on failure; block->page.zip will be left intact. */
|
|
bool
|
|
page_zip_compress(
|
|
buf_block_t* block, /*!< in/out: buffer block */
|
|
dict_index_t* index, /*!< in: index of the B-tree node */
|
|
ulint level, /*!< in: commpression level */
|
|
mtr_t* mtr) /*!< in/out: mini-transaction */
|
|
MY_ATTRIBUTE((nonnull));
|
|
|
|
/**********************************************************************//**
|
|
Write the index information for the compressed page.
|
|
@return used size of buf */
|
|
ulint
|
|
page_zip_fields_encode(
|
|
/*===================*/
|
|
ulint n, /*!< in: number of fields
|
|
to compress */
|
|
const dict_index_t* index, /*!< in: index comprising
|
|
at least n fields */
|
|
ulint trx_id_pos,
|
|
/*!< in: position of the trx_id column
|
|
in the index, or ULINT_UNDEFINED if
|
|
this is a non-leaf page */
|
|
byte* buf); /*!< out: buffer of (n + 1) * 2 bytes */
|
|
|
|
/**********************************************************************//**
|
|
Decompress a page. This function should tolerate errors on the compressed
|
|
page. Instead of letting assertions fail, it will return FALSE if an
|
|
inconsistency is detected.
|
|
@return TRUE on success, FALSE on failure */
|
|
ibool
|
|
page_zip_decompress(
|
|
/*================*/
|
|
page_zip_des_t* page_zip,/*!< in: data, ssize;
|
|
out: m_start, m_end, m_nonempty, n_blobs */
|
|
page_t* page, /*!< out: uncompressed page, may be trashed */
|
|
ibool all) /*!< in: TRUE=decompress the whole page;
|
|
FALSE=verify but do not copy some
|
|
page header fields that should not change
|
|
after page creation */
|
|
MY_ATTRIBUTE((nonnull(1,2)));
|
|
|
|
#ifdef UNIV_DEBUG
|
|
/**********************************************************************//**
|
|
Validate a compressed page descriptor.
|
|
@return TRUE if ok */
|
|
UNIV_INLINE
|
|
ibool
|
|
page_zip_simple_validate(
|
|
/*=====================*/
|
|
const page_zip_des_t* page_zip); /*!< in: compressed page
|
|
descriptor */
|
|
#endif /* UNIV_DEBUG */
|
|
|
|
#ifdef UNIV_ZIP_DEBUG
|
|
/**********************************************************************//**
|
|
Check that the compressed and decompressed pages match.
|
|
@return TRUE if valid, FALSE if not */
|
|
ibool
|
|
page_zip_validate_low(
|
|
/*==================*/
|
|
const page_zip_des_t* page_zip,/*!< in: compressed page */
|
|
const page_t* page, /*!< in: uncompressed page */
|
|
const dict_index_t* index, /*!< in: index of the page, if known */
|
|
ibool sloppy) /*!< in: FALSE=strict,
|
|
TRUE=ignore the MIN_REC_FLAG */
|
|
MY_ATTRIBUTE((nonnull(1,2)));
|
|
/**********************************************************************//**
|
|
Check that the compressed and decompressed pages match. */
|
|
ibool
|
|
page_zip_validate(
|
|
/*==============*/
|
|
const page_zip_des_t* page_zip,/*!< in: compressed page */
|
|
const page_t* page, /*!< in: uncompressed page */
|
|
const dict_index_t* index) /*!< in: index of the page, if known */
|
|
MY_ATTRIBUTE((nonnull(1,2)));
|
|
#endif /* UNIV_ZIP_DEBUG */
|
|
|
|
/**********************************************************************//**
|
|
Determine how big record can be inserted without recompressing the page.
|
|
@return a positive number indicating the maximum size of a record
|
|
whose insertion is guaranteed to succeed, or zero or negative */
|
|
UNIV_INLINE
|
|
lint
|
|
page_zip_max_ins_size(
|
|
/*==================*/
|
|
const page_zip_des_t* page_zip,/*!< in: compressed page */
|
|
ibool is_clust)/*!< in: TRUE if clustered index */
|
|
MY_ATTRIBUTE((warn_unused_result));
|
|
|
|
/**********************************************************************//**
|
|
Determine if enough space is available in the modification log.
|
|
@return TRUE if page_zip_write_rec() will succeed */
|
|
UNIV_INLINE
|
|
ibool
|
|
page_zip_available(
|
|
/*===============*/
|
|
const page_zip_des_t* page_zip,/*!< in: compressed page */
|
|
ibool is_clust,/*!< in: TRUE if clustered index */
|
|
ulint length, /*!< in: combined size of the record */
|
|
ulint create) /*!< in: nonzero=add the record to
|
|
the heap */
|
|
MY_ATTRIBUTE((warn_unused_result));
|
|
|
|
/** Write an entire record to the ROW_FORMAT=COMPRESSED page.
|
|
The data must already have been written to the uncompressed page.
|
|
@param[in,out] block ROW_FORMAT=COMPRESSED page
|
|
@param[in] rec record in the uncompressed page
|
|
@param[in] index the index that the page belongs to
|
|
@param[in] offsets rec_get_offsets(rec, index)
|
|
@param[in] create nonzero=insert, zero=update
|
|
@param[in,out] mtr mini-transaction */
|
|
void page_zip_write_rec(buf_block_t *block, const byte *rec,
|
|
const dict_index_t *index, const rec_offs *offsets,
|
|
ulint create, mtr_t *mtr)
|
|
MY_ATTRIBUTE((nonnull));
|
|
|
|
/**********************************************************************//**
|
|
Write a BLOB pointer of a record on the leaf page of a clustered index.
|
|
The information must already have been updated on the uncompressed page. */
|
|
void
|
|
page_zip_write_blob_ptr(
|
|
/*====================*/
|
|
buf_block_t* block, /*!< in/out: ROW_FORMAT=COMPRESSED page */
|
|
const byte* rec, /*!< in/out: record whose data is being
|
|
written */
|
|
dict_index_t* index, /*!< in: index of the page */
|
|
const rec_offs* offsets,/*!< in: rec_get_offsets(rec, index) */
|
|
ulint n, /*!< in: column index */
|
|
mtr_t* mtr) /*!< in/out: mini-transaction */
|
|
MY_ATTRIBUTE((nonnull));
|
|
|
|
/**********************************************************************//**
|
|
Write the node pointer of a record on a non-leaf compressed page. */
|
|
void
|
|
page_zip_write_node_ptr(
|
|
/*====================*/
|
|
buf_block_t* block, /*!< in/out: compressed page */
|
|
byte* rec, /*!< in/out: record */
|
|
ulint size, /*!< in: data size of rec */
|
|
ulint ptr, /*!< in: node pointer */
|
|
mtr_t* mtr) /*!< in/out: mini-transaction */
|
|
MY_ATTRIBUTE((nonnull));
|
|
|
|
/** Write the DB_TRX_ID,DB_ROLL_PTR into a clustered index leaf page record.
|
|
@param[in,out] block ROW_FORMAT=COMPRESSED page
|
|
@param[in,out] rec record
|
|
@param[in] offsets rec_get_offsets(rec, index)
|
|
@param[in] trx_id_field field number of DB_TRX_ID (number of PK fields)
|
|
@param[in] trx_id DB_TRX_ID value (transaction identifier)
|
|
@param[in] roll_ptr DB_ROLL_PTR value (undo log pointer)
|
|
@param[in,out] mtr mini-transaction */
|
|
void
|
|
page_zip_write_trx_id_and_roll_ptr(
|
|
buf_block_t* block,
|
|
byte* rec,
|
|
const rec_offs* offsets,
|
|
ulint trx_id_col,
|
|
trx_id_t trx_id,
|
|
roll_ptr_t roll_ptr,
|
|
mtr_t* mtr)
|
|
MY_ATTRIBUTE((nonnull));
|
|
|
|
/** Modify the delete-mark flag of a ROW_FORMAT=COMPRESSED record.
|
|
@param[in,out] block buffer block
|
|
@param[in,out] rec record on a physical index page
|
|
@param[in] flag the value of the delete-mark flag
|
|
@param[in,out] mtr mini-transaction */
|
|
void page_zip_rec_set_deleted(buf_block_t *block, rec_t *rec, bool flag,
|
|
mtr_t *mtr)
|
|
MY_ATTRIBUTE((nonnull));
|
|
|
|
/**********************************************************************//**
|
|
Insert a record to the dense page directory. */
|
|
void
|
|
page_zip_dir_insert(
|
|
/*================*/
|
|
page_cur_t* cursor, /*!< in/out: page cursor */
|
|
uint16_t free_rec,/*!< in: record from which rec was
|
|
allocated, or 0 */
|
|
byte* rec, /*!< in: record to insert */
|
|
mtr_t* mtr) /*!< in/out: mini-transaction */
|
|
MY_ATTRIBUTE((nonnull(1,3,4)));
|
|
|
|
/** Shift the dense page directory and the array of BLOB pointers
|
|
when a record is deleted.
|
|
@param[in,out] block index page
|
|
@param[in,out] rec record being deleted
|
|
@param[in] index the index that the page belongs to
|
|
@param[in] offsets rec_get_offsets(rec, index)
|
|
@param[in] free previous start of the free list
|
|
@param[in,out] mtr mini-transaction */
|
|
void page_zip_dir_delete(buf_block_t *block, byte *rec,
|
|
const dict_index_t *index, const rec_offs *offsets,
|
|
const byte *free, mtr_t *mtr)
|
|
MY_ATTRIBUTE((nonnull(1,2,3,4,6)));
|
|
|
|
/**********************************************************************//**
|
|
Reorganize and compress a page. This is a low-level operation for
|
|
compressed pages, to be used when page_zip_compress() fails.
|
|
On success, redo log will be written.
|
|
The function btr_page_reorganize() should be preferred whenever possible.
|
|
IMPORTANT: if page_zip_reorganize() is invoked on a leaf page of a
|
|
non-clustered index, the caller must update the insert buffer free
|
|
bits in the same mini-transaction in such a way that the modification
|
|
will be redo-logged.
|
|
@return error code
|
|
@retval DB_FAIL on overflow; the block_zip will be left intact */
|
|
dberr_t
|
|
page_zip_reorganize(
|
|
buf_block_t* block, /*!< in/out: page with compressed page;
|
|
on the compressed page, in: size;
|
|
out: data, n_blobs,
|
|
m_start, m_end, m_nonempty */
|
|
dict_index_t* index, /*!< in: index of the B-tree node */
|
|
ulint z_level,/*!< in: compression level */
|
|
mtr_t* mtr, /*!< in: mini-transaction */
|
|
bool restore = false)/*!< whether to restore on failure */
|
|
MY_ATTRIBUTE((nonnull, warn_unused_result));
|
|
|
|
/**********************************************************************//**
|
|
Copy the records of a page byte for byte. Do not copy the page header
|
|
or trailer, except those B-tree header fields that are directly
|
|
related to the storage of records. Also copy PAGE_MAX_TRX_ID.
|
|
NOTE: The caller must update the lock table and the adaptive hash index. */
|
|
void
|
|
page_zip_copy_recs(
|
|
buf_block_t* block, /*!< in/out: buffer block */
|
|
const page_zip_des_t* src_zip, /*!< in: compressed page */
|
|
const page_t* src, /*!< in: page */
|
|
dict_index_t* index, /*!< in: index of the B-tree */
|
|
mtr_t* mtr); /*!< in: mini-transaction */
|
|
#endif /* !UNIV_INNOCHECKSUM */
|
|
|
|
/** Calculate the compressed page checksum.
|
|
@param data compressed page
|
|
@param size size of compressed page
|
|
@param use_adler whether to use Adler32 instead of a XOR of 3 CRC-32C
|
|
@return page checksum */
|
|
uint32_t page_zip_calc_checksum(const void *data, size_t size, bool use_adler);
|
|
|
|
/** Validate the checksum on a ROW_FORMAT=COMPRESSED page.
|
|
@param data ROW_FORMAT=COMPRESSED page
|
|
@param size size of the page, in bytes
|
|
@return whether the stored checksum matches innodb_checksum_algorithm */
|
|
bool page_zip_verify_checksum(const byte *data, size_t size);
|
|
|
|
#ifndef UNIV_INNOCHECKSUM
|
|
/**********************************************************************//**
|
|
Reset the counters used for filling
|
|
INFORMATION_SCHEMA.innodb_cmp_per_index. */
|
|
UNIV_INLINE
|
|
void
|
|
page_zip_reset_stat_per_index();
|
|
/*===========================*/
|
|
|
|
#include "page0zip.inl"
|
|
#endif /* !UNIV_INNOCHECKSUM */
|
|
|
|
#endif /* page0zip_h */
|