summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--include/my_base.h3
-rw-r--r--mysql-test/suite/innodb/r/innodb-index.result9
-rw-r--r--mysql-test/suite/innodb/t/innodb-index.test13
-rw-r--r--mysys/my_handler_errors.h3
-rw-r--r--sql/handler.cc3
-rw-r--r--sql/share/errmsg-utf8.txt3
-rw-r--r--storage/innobase/handler/ha_innodb.cc2
-rw-r--r--storage/innobase/include/db0err.h3
-rw-r--r--storage/innobase/include/trx0undo.h46
-rw-r--r--storage/innobase/row/row0mysql.c1
-rw-r--r--storage/innobase/trx/trx0rec.c69
-rw-r--r--storage/innobase/trx/trx0undo.c66
-rw-r--r--storage/innobase/ut/ut0ut.c2
13 files changed, 158 insertions, 65 deletions
diff --git a/include/my_base.h b/include/my_base.h
index cc02b0080d9..d9f08a3c467 100644
--- a/include/my_base.h
+++ b/include/my_base.h
@@ -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)
diff --git a/mysql-test/suite/innodb/r/innodb-index.result b/mysql-test/suite/innodb/r/innodb-index.result
index aa03c72022b..2e9ee652a76 100644
--- a/mysql-test/suite/innodb/r/innodb-index.result
+++ b/mysql-test/suite/innodb/r/innodb-index.result
@@ -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;
diff --git a/mysql-test/suite/innodb/t/innodb-index.test b/mysql-test/suite/innodb/t/innodb-index.test
index fad90e280fc..1df65afe94c 100644
--- a/mysql-test/suite/innodb/t/innodb-index.test
+++ b/mysql-test/suite/innodb/t/innodb-index.test
@@ -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;
diff --git a/mysys/my_handler_errors.h b/mysys/my_handler_errors.h
index 428a58b0767..3533b633960 100644
--- a/mysys/my_handler_errors.h
+++ b/mysys/my_handler_errors.h
@@ -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);
diff --git a/sql/handler.cc b/sql/handler.cc
index db1eea6484b..6f7cf2c3456 100644
--- a/sql/handler.cc
+++ b/sql/handler.cc
@@ -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.
diff --git a/sql/share/errmsg-utf8.txt b/sql/share/errmsg-utf8.txt
index 519d693f96d..58dea797dda 100644
--- a/sql/share/errmsg-utf8.txt
+++ b/sql/share/errmsg-utf8.txt
@@ -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."
diff --git a/storage/innobase/handler/ha_innodb.cc b/storage/innobase/handler/ha_innodb.cc
index c4283d65a2a..7592b201ceb 100644
--- a/storage/innobase/handler/ha_innodb.cc
+++ b/storage/innobase/handler/ha_innodb.cc
@@ -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);
}
}
diff --git a/storage/innobase/include/db0err.h b/storage/innobase/include/db0err.h
index 415470b61b4..e0952f0709d 100644
--- a/storage/innobase/include/db0err.h
+++ b/storage/innobase/include/db0err.h
@@ -1,6 +1,6 @@
/*****************************************************************************
-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,
diff --git a/storage/innobase/include/trx0undo.h b/storage/innobase/include/trx0undo.h
index df16c939070..50aa6d0ac09 100644
--- a/storage/innobase/include/trx0undo.h
+++ b/storage/innobase/include/trx0undo.h
@@ -1,6 +1,6 @@
/*****************************************************************************
-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. */
diff --git a/storage/innobase/row/row0mysql.c b/storage/innobase/row/row0mysql.c
index d98d47a5da6..dd3aeef78ac 100644
--- a/storage/innobase/row/row0mysql.c
+++ b/storage/innobase/row/row0mysql.c
@@ -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:
diff --git a/storage/innobase/trx/trx0rec.c b/storage/innobase/trx/trx0rec.c
index 5f83a9f5fd9..80c3d274e7c 100644
--- a/storage/innobase/trx/trx0rec.c
+++ b/storage/innobase/trx/trx0rec.c
@@ -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 */
+ /* Did not succeed: out of space */
+ err = DB_OUT_OF_FILE_SPACE;
- mutex_exit(&(trx->undo_mutex));
- mtr_commit(&mtr);
- if (UNIV_LIKELY_NULL(heap)) {
- mem_heap_free(heap);
- }
- return(DB_OUT_OF_FILE_SPACE);
- }
+err_exit:
+ mutex_exit(&trx->undo_mutex);
+ mtr_commit(&mtr);
+func_exit:
+ if (UNIV_LIKELY_NULL(heap)) {
+ mem_heap_free(heap);
}
+ return(err);
}
/*============== BUILDING PREVIOUS VERSION OF A RECORD ===============*/
diff --git a/storage/innobase/trx/trx0undo.c b/storage/innobase/trx/trx0undo.c
index 060a537c472..805fdcee242 100644
--- a/storage/innobase/trx/trx0undo.c
+++ b/storage/innobase/trx/trx0undo.c
@@ -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);
}
diff --git a/storage/innobase/ut/ut0ut.c b/storage/innobase/ut/ut0ut.c
index bd009f1fd32..f6dfb3ba0b3 100644
--- a/storage/innobase/ut/ut0ut.c
+++ b/storage/innobase/ut/ut0ut.c
@@ -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