Skip to content

Commit a79ed51

Browse files
Nisha Gopalakrishnandahlerlend
authored andcommitted
Bug#35869747: Cannot drop index from upgraded instance
Analysis Tables of redundant row format having indexes of size greater than 767 bytes cannot be accessed after a successful upgrade to 8.0 An index with size greater than 767 bytes was permitted to be created on a table with redundant row format prior to 5.7.35 version. When such tables are upgraded to 8.0, they became inaccessible. During open table i.e dd_open_table(), the index size was validated resulting in an error since the redundant row format does not support indexes greater than 767 bytes. Fix Mark such indexes of invalid size as corrupt during dd_open_table(). This involved moving the index size check to later point in time in dd_open_table_one() i.e after dict_table_add_to_cache(). All operations which involves using the index will error out as index corrupted until the index is dropped. Change-Id: Iff3915028f1d3631af2fe85669891144ab048856
1 parent 7158b81 commit a79ed51

File tree

7 files changed

+283
-59
lines changed

7 files changed

+283
-59
lines changed
13.3 MB
Binary file not shown.

mysql-test/suite/innodb/r/row_format.result

Lines changed: 93 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -298,3 +298,96 @@ TABLE_ID = @TID
298298
# CLEANUP
299299
SET GLOBAL innodb_file_per_table = @orig_innodb_file_per_table;
300300
DROP TABLE t_compressed;
301+
#
302+
# Bug#35869747: Cannot drop index from upgraded instance
303+
#
304+
# Upgrade from 8.4.1 having tables with redundant row format and
305+
# index longer than supported 767 bytes.
306+
# Stop the running the server.
307+
# Unzip the datadir.
308+
# Restart the server against the unzipped datadir.
309+
# restart: --datadir=DATADIR --lower_case_table_names=1
310+
# Verify tables after upgrade.
311+
# Table with single index of invalid length.
312+
SHOW CREATE TABLE test.t1;
313+
Table Create Table
314+
t1 CREATE TABLE `t1` (
315+
`fld1` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci DEFAULT NULL,
316+
KEY `idx1` (`fld1`)
317+
) ENGINE=InnoDB DEFAULT CHARSET=latin1
318+
# Table with composite index of invalid length.
319+
SHOW CREATE TABLE test.t2;
320+
Table Create Table
321+
t2 CREATE TABLE `t2` (
322+
`fld1` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci DEFAULT NULL,
323+
`fld2` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci DEFAULT NULL,
324+
KEY `idx1` (`fld1`,`fld2`)
325+
) ENGINE=InnoDB DEFAULT CHARSET=latin1
326+
# Table with two indexes of invalid length.
327+
SHOW CREATE TABLE test.t3;
328+
Table Create Table
329+
t3 CREATE TABLE `t3` (
330+
`fld1` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci DEFAULT NULL,
331+
`fld2` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci DEFAULT NULL,
332+
KEY `idx1` (`fld1`),
333+
KEY `idx2` (`fld2`)
334+
) ENGINE=InnoDB DEFAULT CHARSET=latin1
335+
# Table with prefix index of invalid length.
336+
SHOW CREATE TABLE test.t4;
337+
Table Create Table
338+
t4 CREATE TABLE `t4` (
339+
`fld1` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci DEFAULT NULL,
340+
KEY `idx1` (`fld1`(250))
341+
) ENGINE=InnoDB DEFAULT CHARSET=latin1
342+
# CHECK TABLE should flag indexes as corrupt after fix.
343+
CHECK TABLE test.t1;
344+
Table Op Msg_type Msg_text
345+
test.t1 check Warning InnoDB: Index idx1 is marked as corrupted
346+
test.t1 check error Corrupt
347+
CHECK TABLE test.t2;
348+
Table Op Msg_type Msg_text
349+
test.t2 check Warning InnoDB: Index idx1 is marked as corrupted
350+
test.t2 check error Corrupt
351+
CHECK TABLE test.t3;
352+
Table Op Msg_type Msg_text
353+
test.t3 check Warning InnoDB: Index idx1 is marked as corrupted
354+
test.t3 check Warning InnoDB: Index idx2 is marked as corrupted
355+
test.t3 check error Corrupt
356+
CHECK TABLE test.t4;
357+
Table Op Msg_type Msg_text
358+
test.t4 check Warning InnoDB: Index idx1 is marked as corrupted
359+
test.t4 check error Corrupt
360+
# TRUNCATE TABLE reports an index too long error since it is DROP + CREATE.
361+
TRUNCATE TABLE test.t1;
362+
ERROR HY000: Index column size too large. The maximum column size is 767 bytes.
363+
TRUNCATE TABLE test.t2;
364+
ERROR HY000: Index column size too large. The maximum column size is 767 bytes.
365+
TRUNCATE TABLE test.t3;
366+
ERROR HY000: Index column size too large. The maximum column size is 767 bytes.
367+
TRUNCATE TABLE test.t4;
368+
ERROR HY000: Index column size too large. The maximum column size is 767 bytes.
369+
# SELECT statement which uses the index errors out flagging the corruption.
370+
SELECT * FROM test.t1 FORCE INDEX (idx1);
371+
ERROR HY000: Index idx1 is corrupted
372+
SELECT * FROM test.t2 FORCE INDEX (idx1);
373+
ERROR HY000: Index idx1 is corrupted
374+
# SELECT statement which does not use the corrupted index succeeds.
375+
SELECT * FROM test.t3;
376+
fld1 fld2
377+
t3abc t3efg
378+
SELECT * FROM test.t4;
379+
fld1
380+
t4abc
381+
# DROP INDEX succeeds after fix.
382+
ALTER TABLE test.t1 DROP INDEX idx1;
383+
ALTER TABLE test.t2 DROP INDEX idx1;
384+
# DROP TABLE succeeds after fix.
385+
DROP TABLE test.t3;
386+
DROP TABLE test.t4;
387+
# Cleanup
388+
DROP TABLE test.t1;
389+
DROP TABLE test.t2;
390+
# Shutdown server
391+
# Clean up data dir
392+
# Restarting server to restore server state
393+
# restart:

mysql-test/suite/innodb/t/row_format.test

Lines changed: 101 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -239,3 +239,104 @@ SELECT TABLE_ID = @TID FROM INFORMATION_SCHEMA.INNODB_TABLES WHERE NAME LIKE "%t
239239
--echo # CLEANUP
240240
SET GLOBAL innodb_file_per_table = @orig_innodb_file_per_table;
241241
DROP TABLE t_compressed;
242+
243+
--echo #
244+
--echo # Bug#35869747: Cannot drop index from upgraded instance
245+
--echo #
246+
247+
--disable_query_log
248+
call mtr.add_suppression("\\[ERROR\\] \\[MY-014073\\] \\[InnoDB\\] Index idx[0-9] of test\\.t[0-9] exceeds limit of 767 bytes per column.");
249+
--enable_query_log
250+
251+
--echo # Upgrade from 8.4.1 having tables with redundant row format and
252+
--echo # index longer than supported 767 bytes.
253+
# The data directory was created by using the following steps:
254+
# 1. Using mysql-5.7.31 version:
255+
# SET GLOBAL innodb_default_row_format=redundant;
256+
# CREATE TABLE t1 ( fld1 varchar(255) CHARACTER SET utf8mb4);
257+
# CREATE TABLE `t2` (`fld1` varchar(255) CHARACTER SET utf8mb4 default null,
258+
# `fld2` varchar(255) CHARACTER SET utf8mb4 default null);
259+
# CREATE TABLE `t3` (`fld1` varchar(255) CHARACTER SET utf8mb4 default null,
260+
# `fld2` varchar(255) CHARACTER SET utf8mb4 default null);
261+
# CREATE TABLE `t4` (`fld1` varchar(255) CHARACTER SET utf8mb4 default null);
262+
# SET GLOBAL innodb_default_row_format=dynamic;
263+
# CREATE INDEX idx1 ON t1(fld1);
264+
# CREATE INDEX idx1 ON t2(fld1,fld2);
265+
# CREATE INDEX idx1 ON t3(fld1);
266+
# CREATE INDEX idx2 ON t3(fld2);
267+
# CREATE INDEX idx1 ON t4(fld1(250));
268+
# 2. Inplace upgrade to mysql-8.0.38.
269+
# 3. Inplace upgrade to mysql-8.4.1.
270+
# 4. Zip the data directory.
271+
272+
--echo # Stop the running the server.
273+
--source include/shutdown_mysqld.inc
274+
275+
--echo # Unzip the datadir.
276+
--exec unzip -qo $MYSQLTEST_VARDIR/std_data/data841_long_index.zip -d $MYSQL_TMP_DIR
277+
let $DATADIR = $MYSQL_TMP_DIR/data;
278+
279+
--echo # Restart the server against the unzipped datadir.
280+
--replace_result $DATADIR DATADIR
281+
--let $restart_parameters = restart: --datadir=$DATADIR --lower_case_table_names=1
282+
--let $wait_counter=3000
283+
--source include/start_mysqld.inc
284+
285+
--echo # Verify tables after upgrade.
286+
287+
--echo # Table with single index of invalid length.
288+
SHOW CREATE TABLE test.t1;
289+
--echo # Table with composite index of invalid length.
290+
SHOW CREATE TABLE test.t2;
291+
--echo # Table with two indexes of invalid length.
292+
SHOW CREATE TABLE test.t3;
293+
--echo # Table with prefix index of invalid length.
294+
SHOW CREATE TABLE test.t4;
295+
296+
--echo # CHECK TABLE should flag indexes as corrupt after fix.
297+
CHECK TABLE test.t1;
298+
CHECK TABLE test.t2;
299+
CHECK TABLE test.t3;
300+
CHECK TABLE test.t4;
301+
302+
--echo # TRUNCATE TABLE reports an index too long error since it is DROP + CREATE.
303+
--error ER_INDEX_COLUMN_TOO_LONG
304+
TRUNCATE TABLE test.t1;
305+
--error ER_INDEX_COLUMN_TOO_LONG
306+
TRUNCATE TABLE test.t2;
307+
--error ER_INDEX_COLUMN_TOO_LONG
308+
TRUNCATE TABLE test.t3;
309+
--error ER_INDEX_COLUMN_TOO_LONG
310+
TRUNCATE TABLE test.t4;
311+
312+
--echo # SELECT statement which uses the index errors out flagging the corruption.
313+
--error ER_INDEX_CORRUPT
314+
SELECT * FROM test.t1 FORCE INDEX (idx1);
315+
--error ER_INDEX_CORRUPT
316+
SELECT * FROM test.t2 FORCE INDEX (idx1);
317+
318+
--echo # SELECT statement which does not use the corrupted index succeeds.
319+
SELECT * FROM test.t3;
320+
SELECT * FROM test.t4;
321+
322+
--echo # DROP INDEX succeeds after fix.
323+
ALTER TABLE test.t1 DROP INDEX idx1;
324+
ALTER TABLE test.t2 DROP INDEX idx1;
325+
326+
--echo # DROP TABLE succeeds after fix.
327+
DROP TABLE test.t3;
328+
DROP TABLE test.t4;
329+
330+
--echo # Cleanup
331+
DROP TABLE test.t1;
332+
DROP TABLE test.t2;
333+
334+
--echo # Shutdown server
335+
--source include/shutdown_mysqld.inc
336+
337+
--echo # Clean up data dir
338+
--force-rmdir $MYSQL_TMP_DIR/data
339+
340+
--echo # Restarting server to restore server state
341+
--let $restart_parameters = "restart:"
342+
--source include/start_mysqld.inc

share/messages_to_error_log.txt

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12295,6 +12295,9 @@ ER_WAITING_FOR_NO_CONNECTIONS
1229512295
ER_WAITING_FOR_NO_THDS
1229612296
eng "Waiting for THDs in partition %u to be closed, %u still left. %u THDs left in total for all partitions."
1229712297

12298+
ER_IB_INDEX_PART_TOO_LONG
12299+
eng "Index %s of %s.%s exceeds limit of %lu bytes per column."
12300+
1229812301
#
1229912302
# End of 8.0 error messages intended to be written to the server error log.
1230012303
#

storage/innobase/dict/dict0dd.cc

Lines changed: 68 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -1862,6 +1862,23 @@ bool dd_instant_columns_consistent(const dd::Table &dd_table) {
18621862
}
18631863
#endif /* UNIV_DEBUG */
18641864

1865+
void dd_visit_keys_with_too_long_parts(
1866+
const TABLE *table, const size_t max_part_len,
1867+
std::function<void(const KEY &)> visitor) {
1868+
for (uint key_num = 0; key_num < table->s->keys; key_num++) {
1869+
const KEY &key = table->key_info[key_num];
1870+
if (!(key.flags & (HA_SPATIAL | HA_FULLTEXT))) {
1871+
for (unsigned i = 0; i < key.user_defined_key_parts; i++) {
1872+
const KEY_PART_INFO *key_part = &key.key_part[i];
1873+
if (max_part_len < key_part->length) {
1874+
visitor(key);
1875+
continue;
1876+
}
1877+
}
1878+
}
1879+
}
1880+
}
1881+
18651882
static void instant_update_table_cols_count(dict_table_t *dict_table,
18661883
uint32_t n_added_column,
18671884
uint32_t n_dropped_column) {
@@ -2768,6 +2785,34 @@ const dict_index_t *dd_find_index(const dict_table_t *table, Index *dd_index) {
27682785
}
27692786
MY_COMPILER_DIAGNOSTIC_POP()
27702787

2788+
/** Return the prefix length of the key.
2789+
@param[in] key Key information.
2790+
@param[in] key_part Key part information.
2791+
@return Key prefix length or 0 if whole column is indexed.
2792+
*/
2793+
static inline uint16_t get_index_prefix_len(const KEY &key,
2794+
const KEY_PART_INFO *key_part) {
2795+
if (key.flags & (HA_SPATIAL | HA_FULLTEXT)) {
2796+
return 0;
2797+
}
2798+
2799+
if (key_part->key_part_flag & HA_PART_KEY_SEG) {
2800+
ut_ad(key_part->length > 0);
2801+
return key_part->length;
2802+
}
2803+
2804+
#ifdef UNIV_DEBUG
2805+
auto field = key_part->field;
2806+
ut_ad((!is_blob(field->real_type()) &&
2807+
field->real_type() != MYSQL_TYPE_GEOMETRY) ||
2808+
key_part->length >= (field->type() == MYSQL_TYPE_VARCHAR
2809+
? field->key_length()
2810+
: field->pack_length()));
2811+
#endif
2812+
2813+
return 0;
2814+
}
2815+
27712816
template const dict_index_t *dd_find_index<dd::Index>(const dict_table_t *,
27722817
dd::Index *);
27732818
template const dict_index_t *dd_find_index<dd::Partition_index>(
@@ -2780,7 +2825,6 @@ template const dict_index_t *dd_find_index<dd::Partition_index>(
27802825
@param[in] key_num key_info[] offset
27812826
@return error code
27822827
@retval 0 on success
2783-
@retval HA_ERR_INDEX_COL_TOO_LONG if a column is too long
27842828
@retval HA_ERR_TOO_BIG_ROW if the record is too long */
27852829
[[nodiscard]] static int dd_fill_one_dict_index(const dd::Index *dd_index,
27862830
dict_table_t *table,
@@ -2831,7 +2875,6 @@ template const dict_index_t *dd_find_index<dd::Partition_index>(
28312875

28322876
for (unsigned i = 0; i < key.user_defined_key_parts; i++) {
28332877
const KEY_PART_INFO *key_part = &key.key_part[i];
2834-
unsigned prefix_len = 0;
28352878
const Field *field = key_part->field;
28362879
ut_ad(field == form->field[key_part->fieldnr - 1]);
28372880
ut_ad(field == form->field[field->field_index()]);
@@ -2853,32 +2896,7 @@ template const dict_index_t *dd_find_index<dd::Partition_index>(
28532896
is_asc = false;
28542897
}
28552898

2856-
if (key.flags & HA_SPATIAL) {
2857-
prefix_len = 0;
2858-
} else if (key.flags & HA_FULLTEXT) {
2859-
prefix_len = 0;
2860-
} else if (key_part->key_part_flag & HA_PART_KEY_SEG) {
2861-
/* SPATIAL and FULLTEXT index always are on
2862-
full columns. */
2863-
ut_ad(!(key.flags & (HA_SPATIAL | HA_FULLTEXT)));
2864-
prefix_len = key_part->length;
2865-
ut_ad(prefix_len > 0);
2866-
} else {
2867-
ut_ad(key.flags & (HA_SPATIAL | HA_FULLTEXT) ||
2868-
(!is_blob(field->real_type()) &&
2869-
field->real_type() != MYSQL_TYPE_GEOMETRY) ||
2870-
key_part->length >= (field->type() == MYSQL_TYPE_VARCHAR
2871-
? field->key_length()
2872-
: field->pack_length()));
2873-
prefix_len = 0;
2874-
}
2875-
2876-
if ((key_part->length > max_len || prefix_len > max_len) &&
2877-
!(key.flags & (HA_FULLTEXT))) {
2878-
dict_mem_index_free(index);
2879-
my_error(ER_INDEX_COLUMN_TOO_LONG, MYF(0), max_len);
2880-
return HA_ERR_INDEX_COL_TOO_LONG;
2881-
}
2899+
const auto prefix_len = get_index_prefix_len(key, key_part);
28822900

28832901
dict_col_t *col = nullptr;
28842902

@@ -3453,6 +3471,26 @@ void get_field_types(const dd::Table *dd_tab, const dict_table_t *m_table,
34533471
}
34543472
}
34553473

3474+
/** Check if the individual parts of the composite index does not exceed the
3475+
limit based on the table row format. If yes, mark the index as corrupt.
3476+
@param[in] m_table InnoDB table handle
3477+
@param[in] table MySQL table definition */
3478+
static void validate_index_len(dict_table_t *m_table, const TABLE *table) {
3479+
const uint32_t max_part_len = DICT_MAX_FIELD_LEN_BY_FORMAT(m_table);
3480+
dd_visit_keys_with_too_long_parts(table, max_part_len, [&](const KEY &key) {
3481+
dict_index_t *index = dict_table_get_index_on_name(m_table, key.name, true);
3482+
if (index != nullptr) {
3483+
std::string schema_name;
3484+
std::string table_name;
3485+
3486+
dict_set_corrupted(index);
3487+
m_table->get_table_name(schema_name, table_name);
3488+
ib::error(ER_IB_INDEX_PART_TOO_LONG, key.name, schema_name.c_str(),
3489+
table_name.c_str(), ulong{max_part_len});
3490+
}
3491+
});
3492+
}
3493+
34563494
template <typename Table>
34573495
static inline void fill_dict_existing_column(
34583496
const Table *dd_tab, const TABLE *m_form, dict_table_t *m_table,
@@ -5096,6 +5134,8 @@ dict_table_t *dd_open_table_one(dd::cache::Dictionary_client *client,
50965134
} else {
50975135
dict_table_add_to_cache(m_table, true);
50985136

5137+
validate_index_len(m_table, table);
5138+
50995139
if (m_table->fts && dict_table_has_fts_index(m_table)) {
51005140
fts_optimize_add_table(m_table);
51015141
}

0 commit comments

Comments
 (0)