List:Commits« Previous MessageNext Message »
From:marko.makela Date:September 1 2011 7:01pm
Subject:bzr push into mysql-5.5 branch (marko.makela:3516 to 3517)
Bug#12547647
View as plain text  
 3517 Marko Mäkelä	2011-09-01
      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@stripped20110829081642-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

    modified:
      include/my_base.h
      mysql-test/suite/innodb/r/innodb-index.result
      mysql-test/suite/innodb/t/innodb-index.test
      mysys/my_handler_errors.h
      sql/handler.cc
      sql/share/errmsg-utf8.txt
      storage/innobase/handler/ha_innodb.cc
      storage/innobase/include/db0err.h
      storage/innobase/include/trx0undo.h
      storage/innobase/row/row0mysql.c
      storage/innobase/trx/trx0rec.c
      storage/innobase/trx/trx0undo.c
      storage/innobase/ut/ut0ut.c
 3516 Tor Didriksen	2011-08-31
      Bug#12856915 VALGRIND FAILURE IN FILESORT/CREATE_SORT_INDEX
      
      Post-push fix:
      Enable filesort pattern two, perfschema.selects failed.

    modified:
      mysql-test/valgrind.supp
=== modified file 'include/my_base.h'
--- a/include/my_base.h	revid:tor.didriksen@strippeds1mclgmh6jena
+++ b/include/my_base.h	revid:marko.makela@oracle.com-20110901184804-2901f6qmuro3jas8
@@ -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)

=== modified file 'mysql-test/suite/innodb/r/innodb-index.result'
--- a/mysql-test/suite/innodb/r/innodb-index.result	revid:tor.didriksen@stripped1h7s1mclgmh6jena
+++ b/mysql-test/suite/innodb/r/innodb-index.result	revid:marko.makela@stripped2901f6qmuro3jas8
@@ -975,6 +975,15 @@ INSERT INTO t1 VALUES(9,@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;

=== modified file 'mysql-test/suite/innodb/t/innodb-index.test'
--- a/mysql-test/suite/innodb/t/innodb-index.test	revid:tor.didriksen@strippeda
+++ b/mysql-test/suite/innodb/t/innodb-index.test	revid:marko.makela@stripped
@@ -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;

=== modified file 'mysys/my_handler_errors.h'
--- a/mysys/my_handler_errors.h	revid:tor.didriksen@strippedm-20110831144552-1h7s1mclgmh6jena
+++ b/mysys/my_handler_errors.h	revid:marko.makela@stripped1f6qmuro3jas8
@@ -82,7 +82,8 @@ static const char *handler_error_message
   "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);

=== modified file 'sql/handler.cc'
--- a/sql/handler.cc	revid:tor.didriksen@oracle.com-20110831144552-1h7s1mclgmh6jena
+++ b/sql/handler.cc	revid:marko.makela@strippedjas8
@@ -2869,6 +2869,9 @@ void handler::print_error(int error, myf
   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.

=== modified file 'sql/share/errmsg-utf8.txt'
--- a/sql/share/errmsg-utf8.txt	revid:tor.didriksen@oracle.com-20110831144552-1h7s1mclgmh6jena
+++ b/sql/share/errmsg-utf8.txt	revid:marko.makela@stripped2901f6qmuro3jas8
@@ -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."

=== modified file 'storage/innobase/handler/ha_innodb.cc'
--- a/storage/innobase/handler/ha_innodb.cc	revid:tor.didriksen@strippedlgmh6jena
+++ b/storage/innobase/handler/ha_innodb.cc	revid:marko.makela@stripped8
@@ -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);
 	}
 }
 

=== modified file 'storage/innobase/include/db0err.h'
--- a/storage/innobase/include/db0err.h	revid:tor.didriksen@strippedm-20110831144552-1h7s1mclgmh6jena
+++ b/storage/innobase/include/db0err.h	revid:marko.makela@stripped4804-2901f6qmuro3jas8
@@ -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,

=== modified file 'storage/innobase/include/trx0undo.h'
--- a/storage/innobase/include/trx0undo.h	revid:tor.didriksen@stripped52-1h7s1mclgmh6jena
+++ b/storage/innobase/include/trx0undo.h	revid:marko.makela@strippedmuro3jas8
@@ -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. */

=== modified file 'storage/innobase/row/row0mysql.c'
--- a/storage/innobase/row/row0mysql.c	revid:tor.didriksen@stripped0831144552-1h7s1mclgmh6jena
+++ b/storage/innobase/row/row0mysql.c	revid:marko.makela@stripped01f6qmuro3jas8
@@ -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:

=== modified file 'storage/innobase/trx/trx0rec.c'
--- a/storage/innobase/trx/trx0rec.c	revid:tor.didriksen@strippedmclgmh6jena
+++ b/storage/innobase/trx/trx0rec.c	revid:marko.makela@stripped
@@ -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 ===============*/

=== modified file 'storage/innobase/trx/trx0undo.c'
--- a/storage/innobase/trx/trx0undo.c	revid:tor.didriksen@strippedh7s1mclgmh6jena
+++ b/storage/innobase/trx/trx0undo.c	revid:marko.makela@stripped8
@@ -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)));
-
-	last_page_no = trx_undo_free_page(undo->rseg, FALSE, undo->space,
-					  undo->hdr_page_no, page_no, mtr);
+	ut_ad(mutex_own(&trx->undo_mutex));
+	ut_ad(undo->hdr_page_no != undo->last_page_no);
+	ut_ad(undo->size > 0);
+
+	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
 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);
 	}
 

=== modified file 'storage/innobase/ut/ut0ut.c'
--- a/storage/innobase/ut/ut0ut.c	revid:tor.didriksen@strippedom-20110831144552-1h7s1mclgmh6jena
+++ b/storage/innobase/ut/ut0ut.c	revid:marko.makela@stripped2901f6qmuro3jas8
@@ -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

No bundle (reason: useless for push emails).
Thread
bzr push into mysql-5.5 branch (marko.makela:3516 to 3517)Bug#12547647marko.makela2 Sep