Commit 247ada63 authored by Marko Mäkelä's avatar Marko Mäkelä

Bug#12547647 UPDATE LOGGING COULD EXCEED LOG PAGE SIZE

This fix was accidentally pushed to mysql-5.1 after the 5.1.59 clone-off in
bzr revision id marko.makela@oracle.com-20110829081642-z0w992a0mrc62s6w
with the fix of Bug#12704861 Corruption after a crash during BLOB update
but not merged to mysql-5.5 and upwards.

In the Barracuda formats, the clustered index record no longer
contains a prefix of off-page columns. Because of this, the undo log
must contain these prefixes, so that purge and multi-versioning will
continue to work. However, this also means that an undo log record can
become too big to fit in an undo log page. (It is a limitation of the
undo log that undo records cannot span across multiple pages.)

In case the checks for undo log size fail when CREATE TABLE or CREATE
INDEX is executed, we need a fallback that blocks a modification
operation when the undo log record would exceed the maximum size.

trx_undo_free_last_page_func(): Renamed from trx_undo_free_page_in_rollback().
Define the trx_t parameter only in debug builds.

trx_undo_free_last_page(): Wrapper for trx_undo_free_last_page_func().
Pass the trx_t parameter only in debug builds.

trx_undo_truncate_end_func(): Renamed from trx_undo_truncate_end().
Define the trx_t parameter only in debug builds. Rewrite a for(;;) loop
as a while loop for clarity.

trx_undo_truncate_end(): Wrapper for from trx_undo_truncate_end_func().
Pass the trx_t parameter only in debug builds.

trx_undo_erase_page_end(): Return TRUE if the page was non-empty
to begin with. Refuse to erase empty pages.

trx_undo_report_row_operation(): If the page for which the undo log
was too big was empty, free the undo page and return DB_TOO_BIG_RECORD.

rb:749 approved by Inaam Rana
parent 6e3cee78
......@@ -448,7 +448,8 @@ enum ha_base_keytype {
#define HA_ERR_TOO_MANY_CONCURRENT_TRXS 177 /*Too many active concurrent transactions */
#define HA_ERR_INDEX_COL_TOO_LONG 178 /* Index column length exceeds limit */
#define HA_ERR_INDEX_CORRUPT 179 /* Index corrupted */
#define HA_ERR_LAST 179 /* Copy of last error nr */
#define HA_ERR_UNDO_REC_TOO_BIG 180 /* Undo log record too big */
#define HA_ERR_LAST 180 /* Copy of last error nr */
/* Number of different errors */
#define HA_ERR_ERRORS (HA_ERR_LAST - HA_ERR_FIRST + 1)
......
......@@ -975,6 +975,15 @@ INSERT INTO t1 VALUES(9,@r,@r,@r,@r,@r,@r,@r,@r,@r,@r,@r,@r,@r,@r,@r,@r,@r,@r);
UPDATE t1 SET a=1000;
DELETE FROM t1;
DROP TABLE t1;
CREATE TABLE bug12547647(
a INT NOT NULL, b BLOB NOT NULL, c TEXT,
PRIMARY KEY (b(10), a), INDEX (c(767)), INDEX(b(767))
) ENGINE=InnoDB ROW_FORMAT=DYNAMIC;
INSERT INTO bug12547647 VALUES (5,repeat('khdfo5AlOq',1900),repeat('g',7751));
COMMIT;
UPDATE bug12547647 SET c = REPEAT('b',16928);
ERROR HY000: Undo log record is too big.
DROP TABLE bug12547647;
set global innodb_file_per_table=0;
set global innodb_file_format=Antelope;
set global innodb_file_format_max=Antelope;
......
......@@ -477,6 +477,19 @@ DELETE FROM t1;
-- sleep 10
DROP TABLE t1;
# Bug#12547647 UPDATE LOGGING COULD EXCEED LOG PAGE SIZE
CREATE TABLE bug12547647(
a INT NOT NULL, b BLOB NOT NULL, c TEXT,
PRIMARY KEY (b(10), a), INDEX (c(767)), INDEX(b(767))
) ENGINE=InnoDB ROW_FORMAT=DYNAMIC;
INSERT INTO bug12547647 VALUES (5,repeat('khdfo5AlOq',1900),repeat('g',7751));
COMMIT;
# The following used to cause infinite undo log allocation.
--error ER_UNDO_RECORD_TOO_BIG
UPDATE bug12547647 SET c = REPEAT('b',16928);
DROP TABLE bug12547647;
eval set global innodb_file_per_table=$per_table;
eval set global innodb_file_format=$format;
eval set global innodb_file_format_max=$format;
......
......@@ -82,7 +82,8 @@ static const char *handler_error_messages[]=
"Read page with wrong checksum",
"Too many active concurrent transactions",
"Index column length exceeds limit",
"Index corrupted"
"Index corrupted",
"Undo record too big"
};
extern void my_handler_error_register(void);
......
......@@ -2869,6 +2869,9 @@ void handler::print_error(int error, myf errflag)
case HA_ERR_INDEX_CORRUPT:
textno= ER_INDEX_CORRUPT;
break;
case HA_ERR_UNDO_REC_TOO_BIG:
textno= ER_UNDO_RECORD_TOO_BIG;
break;
default:
{
/* The error was "unknown" to this function.
......
......@@ -6417,3 +6417,6 @@ ER_ERROR_IN_UNKNOWN_TRIGGER_BODY
ER_INDEX_CORRUPT
eng "Index %s is corrupted"
ER_UNDO_RECORD_TOO_BIG
eng "Undo log record is too big."
......@@ -1045,6 +1045,8 @@ convert_error_code_to_mysql(
return(HA_ERR_UNSUPPORTED);
case DB_INDEX_CORRUPT:
return(HA_ERR_INDEX_CORRUPT);
case DB_UNDO_RECORD_TOO_BIG:
return(HA_ERR_UNDO_REC_TOO_BIG);
}
}
......
/*****************************************************************************
Copyright (c) 1996, 2009, Innobase Oy. All Rights Reserved.
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
......@@ -111,6 +111,7 @@ enum db_err {
DB_TOO_BIG_INDEX_COL, /* index column size exceeds maximum
limit */
DB_INDEX_CORRUPT, /* we have corrupted index */
DB_UNDO_RECORD_TOO_BIG, /* the undo log record is too big */
/* The following are partial failure codes */
DB_FAIL = 1000,
......
/*****************************************************************************
Copyright (c) 1996, 2009, Innobase Oy. All Rights Reserved.
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
......@@ -204,17 +204,51 @@ trx_undo_add_page(
mtr_t* mtr); /*!< in: mtr which does not have a latch to any
undo log page; the caller must have reserved
the rollback segment mutex */
/********************************************************************//**
Frees the last undo log page.
The caller must hold the rollback segment mutex. */
UNIV_INTERN
void
trx_undo_free_last_page_func(
/*==========================*/
#ifdef UNIV_DEBUG
const trx_t* trx, /*!< in: transaction */
#endif /* UNIV_DEBUG */
trx_undo_t* undo, /*!< in/out: undo log memory copy */
mtr_t* mtr) /*!< in/out: mini-transaction which does not
have a latch to any undo log page or which
has allocated the undo log page */
__attribute__((nonnull));
#ifdef UNIV_DEBUG
# define trx_undo_free_last_page(trx,undo,mtr) \
trx_undo_free_last_page_func(trx,undo,mtr)
#else /* UNIV_DEBUG */
# define trx_undo_free_last_page(trx,undo,mtr) \
trx_undo_free_last_page_func(undo,mtr)
#endif /* UNIV_DEBUG */
/***********************************************************************//**
Truncates an undo log from the end. This function is used during a rollback
to free space from an undo log. */
UNIV_INTERN
void
trx_undo_truncate_end(
/*==================*/
trx_t* trx, /*!< in: transaction whose undo log it is */
trx_undo_t* undo, /*!< in: undo log */
undo_no_t limit); /*!< in: all undo records with undo number
trx_undo_truncate_end_func(
/*=======================*/
#ifdef UNIV_DEBUG
const trx_t* trx, /*!< in: transaction whose undo log it is */
#endif /* UNIV_DEBUG */
trx_undo_t* undo, /*!< in/out: undo log */
undo_no_t limit) /*!< in: all undo records with undo number
>= this value should be truncated */
__attribute__((nonnull));
#ifdef UNIV_DEBUG
# define trx_undo_truncate_end(trx,undo,limit) \
trx_undo_truncate_end_func(trx,undo,limit)
#else /* UNIV_DEBUG */
# define trx_undo_truncate_end(trx,undo,limit) \
trx_undo_truncate_end_func(undo,limit)
#endif /* UNIV_DEBUG */
/***********************************************************************//**
Truncates an undo log from the start. This function is used during a purge
operation. */
......
......@@ -576,6 +576,7 @@ handle_new_error:
case DB_DUPLICATE_KEY:
case DB_FOREIGN_DUPLICATE_KEY:
case DB_TOO_BIG_RECORD:
case DB_UNDO_RECORD_TOO_BIG:
case DB_ROW_IS_REFERENCED:
case DB_NO_REFERENCED_ROW:
case DB_CANNOT_ADD_CONSTRAINT:
......
......@@ -669,7 +669,6 @@ trx_undo_page_report_modify(
/* Save to the undo log the old values of the columns to be updated. */
if (update) {
if (trx_undo_left(undo_page, ptr) < 5) {
return(0);
......@@ -1119,22 +1118,29 @@ trx_undo_rec_get_partial_row(
#endif /* !UNIV_HOTBACKUP */
/***********************************************************************//**
Erases the unused undo log page end. */
static
void
Erases the unused undo log page end.
@return TRUE if the page contained something, FALSE if it was empty */
static __attribute__((nonnull, warn_unused_result))
ibool
trx_undo_erase_page_end(
/*====================*/
page_t* undo_page, /*!< in: undo page whose end to erase */
mtr_t* mtr) /*!< in: mtr */
page_t* undo_page, /*!< in/out: undo page whose end to erase */
mtr_t* mtr) /*!< in/out: mini-transaction */
{
ulint first_free;
first_free = mach_read_from_2(undo_page + TRX_UNDO_PAGE_HDR
+ TRX_UNDO_PAGE_FREE);
if (first_free == TRX_UNDO_PAGE_HDR + TRX_UNDO_PAGE_HDR_SIZE) {
/* This was an empty page to begin with.
Do nothing here; the caller should free the page. */
return(FALSE);
}
memset(undo_page + first_free, 0xff,
(UNIV_PAGE_SIZE - FIL_PAGE_DATA_END) - first_free);
mlog_write_initial_log_record(undo_page, MLOG_UNDO_ERASE_END, mtr);
return(TRUE);
}
/***********************************************************//**
......@@ -1156,7 +1162,11 @@ trx_undo_parse_erase_page_end(
return(ptr);
}
trx_undo_erase_page_end(page, mtr);
if (!trx_undo_erase_page_end(page, mtr)) {
/* The function trx_undo_erase_page_end() should not
have done anything to an empty page. */
ut_ad(0);
}
return(ptr);
}
......@@ -1202,6 +1212,9 @@ trx_undo_report_row_operation(
mem_heap_t* heap = NULL;
ulint offsets_[REC_OFFS_NORMAL_SIZE];
ulint* offsets = offsets_;
#ifdef UNIV_DEBUG
int loop_count = 0;
#endif /* UNIV_DEBUG */
rec_offs_init(offsets_);
ut_a(dict_index_is_clust(index));
......@@ -1264,7 +1277,7 @@ trx_undo_report_row_operation(
mtr_start(&mtr);
for (;;) {
do {
buf_block_t* undo_block;
page_t* undo_page;
ulint offset;
......@@ -1293,7 +1306,19 @@ trx_undo_report_row_operation(
version the replicate page constructed using the log
records stays identical to the original page */
trx_undo_erase_page_end(undo_page, &mtr);
if (!trx_undo_erase_page_end(undo_page, &mtr)) {
/* The record did not fit on an empty
undo page. Discard the freshly allocated
page and return an error. */
mutex_enter(&rseg->mutex);
trx_undo_free_last_page(trx, undo, &mtr);
mutex_exit(&rseg->mutex);
err = DB_UNDO_RECORD_TOO_BIG;
goto err_exit;
}
mtr_commit(&mtr);
} else {
/* Success */
......@@ -1313,16 +1338,15 @@ trx_undo_report_row_operation(
*roll_ptr = trx_undo_build_roll_ptr(
op_type == TRX_UNDO_INSERT_OP,
rseg->id, page_no, offset);
if (UNIV_LIKELY_NULL(heap)) {
mem_heap_free(heap);
}
return(DB_SUCCESS);
err = DB_SUCCESS;
goto func_exit;
}
ut_ad(page_no == undo->last_page_no);
/* We have to extend the undo log by one page */
ut_ad(++loop_count < 2);
mtr_start(&mtr);
/* When we add a page to an undo log, this is analogous to
......@@ -1334,18 +1358,19 @@ trx_undo_report_row_operation(
page_no = trx_undo_add_page(trx, undo, &mtr);
mutex_exit(&(rseg->mutex));
} while (UNIV_LIKELY(page_no != FIL_NULL));
if (UNIV_UNLIKELY(page_no == FIL_NULL)) {
/* Did not succeed: out of space */
err = DB_OUT_OF_FILE_SPACE;
mutex_exit(&(trx->undo_mutex));
err_exit:
mutex_exit(&trx->undo_mutex);
mtr_commit(&mtr);
func_exit:
if (UNIV_LIKELY_NULL(heap)) {
mem_heap_free(heap);
}
return(DB_OUT_OF_FILE_SPACE);
}
}
return(err);
}
/*============== BUILDING PREVIOUS VERSION OF A RECORD ===============*/
......
......@@ -1004,29 +1004,28 @@ trx_undo_free_page(
}
/********************************************************************//**
Frees an undo log page when there is also the memory object for the undo
log. */
static
Frees the last undo log page.
The caller must hold the rollback segment mutex. */
UNIV_INTERN
void
trx_undo_free_page_in_rollback(
/*===========================*/
trx_t* trx __attribute__((unused)), /*!< in: transaction */
trx_undo_t* undo, /*!< in: undo log memory copy */
ulint page_no,/*!< in: page number to free: must not be the
header page */
mtr_t* mtr) /*!< in: mtr which does not have a latch to any
undo log page; the caller must have reserved
the rollback segment mutex */
trx_undo_free_last_page_func(
/*==========================*/
#ifdef UNIV_DEBUG
const trx_t* trx, /*!< in: transaction */
#endif /* UNIV_DEBUG */
trx_undo_t* undo, /*!< in/out: undo log memory copy */
mtr_t* mtr) /*!< in/out: mini-transaction which does not
have a latch to any undo log page or which
has allocated the undo log page */
{
ulint last_page_no;
ut_ad(undo->hdr_page_no != page_no);
ut_ad(mutex_own(&(trx->undo_mutex)));
ut_ad(mutex_own(&trx->undo_mutex));
ut_ad(undo->hdr_page_no != undo->last_page_no);
ut_ad(undo->size > 0);
last_page_no = trx_undo_free_page(undo->rseg, FALSE, undo->space,
undo->hdr_page_no, page_no, mtr);
undo->last_page_no = trx_undo_free_page(
undo->rseg, FALSE, undo->space,
undo->hdr_page_no, undo->last_page_no, mtr);
undo->last_page_no = last_page_no;
undo->size--;
}
......@@ -1062,9 +1061,11 @@ Truncates an undo log from the end. This function is used during a rollback
to free space from an undo log. */
UNIV_INTERN
void
trx_undo_truncate_end(
/*==================*/
trx_t* trx, /*!< in: transaction whose undo log it is */
trx_undo_truncate_end_func(
/*=======================*/
#ifdef UNIV_DEBUG
const trx_t* trx, /*!< in: transaction whose undo log it is */
#endif /* UNIV_DEBUG */
trx_undo_t* undo, /*!< in: undo log */
undo_no_t limit) /*!< in: all undo records with undo number
>= this value should be truncated */
......@@ -1090,18 +1091,7 @@ trx_undo_truncate_end(
rec = trx_undo_page_get_last_rec(undo_page, undo->hdr_page_no,
undo->hdr_offset);
for (;;) {
if (rec == NULL) {
if (last_page_no == undo->hdr_page_no) {
goto function_exit;
}
trx_undo_free_page_in_rollback(
trx, undo, last_page_no, &mtr);
break;
}
while (rec) {
if (trx_undo_rec_get_undo_no(rec) >= limit) {
/* Truncate at least this record off, maybe
more */
......@@ -1115,6 +1105,14 @@ trx_undo_truncate_end(
undo->hdr_offset);
}
if (last_page_no == undo->hdr_page_no) {
goto function_exit;
}
ut_ad(last_page_no == undo->last_page_no);
trx_undo_free_last_page(trx, undo, &mtr);
mtr_commit(&mtr);
}
......
......@@ -714,6 +714,8 @@ ut_strerr(
return("No index on referenced keys in referenced table");
case DB_INDEX_CORRUPT:
return("Index corrupted");
case DB_UNDO_RECORD_TOO_BIG:
return("Undo record too big");
case DB_END_OF_INDEX:
return("End of index");
/* do not add default: in order to produce a warning if new code
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment