Skip to content

Commit

Permalink
MDEV-9918: [ERROR] mysqld got signal 11 during ALTER TABLE name COL…
Browse files Browse the repository at this point in the history
…UMN ADD

Problem was that in-place online alter table was used on a table
that had mismatch between MySQL frm file and InnoDB data dictionary.
Fixed so that traditional "Copy" method is used if the MySQL frm
and InnoDB data dictionary is not consistent.
  • Loading branch information
Jan Lindström committed Apr 22, 2016
1 parent e5410da commit 628bc57
Show file tree
Hide file tree
Showing 10 changed files with 393 additions and 82 deletions.
49 changes: 49 additions & 0 deletions mysql-test/suite/innodb/r/innodb-corrupted-table.result
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
call mtr.add_suppression("Table .* has a primary key in InnoDB data dictionary, but not in MySQL.*");
call mtr.add_suppression("InnoDB: Table .* contains .* indexes inside InnoDB, which is different from the number of indexes .* defined in the MySQL.*");
create table t1 (pk int, i int, key(i)) engine=InnoDB;
insert into t1 values (1,1),(2,2);
flush tables;
# Save the .frm file without the PK
alter table t1 add primary key (pk);
# Stop the server, replace the frm with the old one and restart the server
show create table t1;
Table Create Table
t1 CREATE TABLE `t1` (
`pk` int(11) DEFAULT NULL,
`i` int(11) DEFAULT NULL,
KEY `i` (`i`)
) ENGINE=InnoDB DEFAULT CHARSET=latin1
Warnings:
Warning 1082 InnoDB: Table test/t1 has a primary key in InnoDB data dictionary, but not in MySQL!
Warning 1082 InnoDB: Table test/t1 contains 2 indexes inside InnoDB, which is different from the number of indexes 1 defined in the MySQL
select * from t1;
pk i
1 1
2 2
alter table t1 add j int;
Warnings:
Warning 1082 InnoDB: Table test/t1 contains 2 indexes inside InnoDB, which is different from the number of indexes 1 defined in the MySQL
show warnings;
Level Code Message
Warning 1082 InnoDB: Table test/t1 contains 2 indexes inside InnoDB, which is different from the number of indexes 1 defined in the MySQL
show create table t1;
Table Create Table
t1 CREATE TABLE `t1` (
`pk` int(11) DEFAULT NULL,
`i` int(11) DEFAULT NULL,
`j` int(11) DEFAULT NULL,
KEY `i` (`i`)
) ENGINE=InnoDB DEFAULT CHARSET=latin1
alter table t1 add primary key (pk);
show warnings;
Level Code Message
show create table t1;
Table Create Table
t1 CREATE TABLE `t1` (
`pk` int(11) NOT NULL DEFAULT '0',
`i` int(11) DEFAULT NULL,
`j` int(11) DEFAULT NULL,
PRIMARY KEY (`pk`),
KEY `i` (`i`)
) ENGINE=InnoDB DEFAULT CHARSET=latin1
drop table t1;
46 changes: 46 additions & 0 deletions mysql-test/suite/innodb/t/innodb-corrupted-table.test
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
--source include/have_innodb.inc
--source include/not_embedded.inc

#
# MDEV-9918: [ERROR] mysqld got signal 11 during ALTER TABLE `name` COLUMN ADD
#

call mtr.add_suppression("Table .* has a primary key in InnoDB data dictionary, but not in MySQL.*");
call mtr.add_suppression("InnoDB: Table .* contains .* indexes inside InnoDB, which is different from the number of indexes .* defined in the MySQL.*");

create table t1 (pk int, i int, key(i)) engine=InnoDB;
insert into t1 values (1,1),(2,2);

--let $datadir= `select @@datadir`

flush tables;

--echo # Save the .frm file without the PK

--copy_file $datadir/test/t1.frm $MYSQLTEST_VARDIR/tmp/t1.frm

alter table t1 add primary key (pk);

--echo # Stop the server, replace the frm with the old one and restart the server

--exec echo "wait" > $MYSQLTEST_VARDIR/tmp/mysqld.1.expect
--shutdown_server 10
--source include/wait_until_disconnected.inc

--remove_file $datadir/test/t1.frm
--copy_file $MYSQLTEST_VARDIR/tmp/t1.frm $datadir/test/t1.frm

--exec echo "restart" > $MYSQLTEST_VARDIR/tmp/mysqld.1.expect
--enable_reconnect
--source include/wait_until_connected_again.inc

show create table t1;
select * from t1;
alter table t1 add j int;
show warnings;
show create table t1;
alter table t1 add primary key (pk);
show warnings;
show create table t1;
# Cleanup
drop table t1;
145 changes: 104 additions & 41 deletions storage/innobase/handler/ha_innodb.cc
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ Copyright (c) 2000, 2015, Oracle and/or its affiliates. All Rights Reserved.
Copyright (c) 2008, 2009 Google Inc.
Copyright (c) 2009, Percona Inc.
Copyright (c) 2012, Facebook Inc.
Copyright (c) 2013, 2014 SkySQL Ab. All Rights Reserved.
Copyright (c) 2013, 2016, MariaDB Corporation.

Portions of this file contain modifications contributed and copyrighted by
Google, Inc. Those modifications are gratefully acknowledged and are described
Expand Down Expand Up @@ -5513,20 +5513,14 @@ ha_innobase::open(
prebuilt->clust_index_was_generated = FALSE;

if (UNIV_UNLIKELY(primary_key >= MAX_KEY)) {
sql_print_error("Table %s has a primary key in "
"InnoDB data dictionary, but not "
"in MySQL!", name);
ib_table->dict_frm_mismatch = DICT_FRM_NO_PK;

/* This mismatch could cause further problems
if not attended, bring this to the user's attention
by printing a warning in addition to log a message
in the errorlog */
push_warning_printf(thd, Sql_condition::WARN_LEVEL_WARN,
ER_NO_SUCH_INDEX,
"InnoDB: Table %s has a "
"primary key in InnoDB data "
"dictionary, but not in "
"MySQL!", name);

ib_push_frm_error(thd, ib_table, table, 0, true);

/* If primary_key >= MAX_KEY, its (primary_key)
value could be out of bound if continue to index
Expand Down Expand Up @@ -5573,27 +5567,14 @@ ha_innobase::open(
}
} else {
if (primary_key != MAX_KEY) {
sql_print_error(
"Table %s has no primary key in InnoDB data "
"dictionary, but has one in MySQL! If you "
"created the table with a MySQL version < "
"3.23.54 and did not define a primary key, "
"but defined a unique key with all non-NULL "
"columns, then MySQL internally treats that "
"key as the primary key. You can fix this "
"error by dump + DROP + CREATE + reimport "
"of the table.", name);

ib_table->dict_frm_mismatch = DICT_NO_PK_FRM_HAS;

/* This mismatch could cause further problems
if not attended, bring this to the user attention
by printing a warning in addition to log a message
in the errorlog */
push_warning_printf(thd, Sql_condition::WARN_LEVEL_WARN,
ER_NO_SUCH_INDEX,
"InnoDB: Table %s has no "
"primary key in InnoDB data "
"dictionary, but has one in "
"MySQL!", name);
ib_push_frm_error(thd, ib_table, table, 0, true);
}

prebuilt->clust_index_was_generated = TRUE;
Expand Down Expand Up @@ -11669,12 +11650,8 @@ ha_innobase::info_low(
}

if (table->s->keys != num_innodb_index) {
sql_print_error("InnoDB: Table %s contains %lu "
"indexes inside InnoDB, which "
"is different from the number of "
"indexes %u defined in the MySQL ",
ib_table->name, num_innodb_index,
table->s->keys);
ib_table->dict_frm_mismatch = DICT_FRM_INCONSISTENT_KEYS;
ib_push_frm_error(user_thd, ib_table, table, num_innodb_index, true);
}

if (!(flag & HA_STATUS_NO_LOCK)) {
Expand All @@ -11694,15 +11671,8 @@ ha_innobase::info_low(
dict_index_t* index = innobase_get_index(i);

if (index == NULL) {
sql_print_error("Table %s contains fewer "
"indexes inside InnoDB than "
"are defined in the MySQL "
".frm file. Have you mixed up "
".frm files from different "
"installations? See "
REFMAN
"innodb-troubleshooting.html\n",
ib_table->name);
ib_table->dict_frm_mismatch = DICT_FRM_INCONSISTENT_KEYS;
ib_push_frm_error(user_thd, ib_table, table, num_innodb_index, true);
break;
}

Expand Down Expand Up @@ -17874,3 +17844,96 @@ ib_push_warning(
my_free(buf);
va_end(args);
}

/********************************************************************//**
Helper function to push frm mismatch error to error log and
if needed to sql-layer. */
UNIV_INTERN
void
ib_push_frm_error(
/*==============*/
THD* thd, /*!< in: MySQL thd */
dict_table_t* ib_table, /*!< in: InnoDB table */
TABLE* table, /*!< in: MySQL table */
ulint n_keys, /*!< in: InnoDB #keys */
bool push_warning) /*!< in: print warning ? */
{
switch (ib_table->dict_frm_mismatch) {
case DICT_FRM_NO_PK:
sql_print_error("Table %s has a primary key in "
"InnoDB data dictionary, but not "
"in MySQL!"
" Have you mixed up "
".frm files from different "
"installations? See "
REFMAN
"innodb-troubleshooting.html\n",
ib_table->name);

if (push_warning) {
push_warning_printf(thd, Sql_condition::WARN_LEVEL_WARN,
ER_NO_SUCH_INDEX,
"InnoDB: Table %s has a "
"primary key in InnoDB data "
"dictionary, but not in "
"MySQL!", ib_table->name);
}
break;
case DICT_NO_PK_FRM_HAS:
sql_print_error(
"Table %s has no primary key in InnoDB data "
"dictionary, but has one in MySQL! If you "
"created the table with a MySQL version < "
"3.23.54 and did not define a primary key, "
"but defined a unique key with all non-NULL "
"columns, then MySQL internally treats that "
"key as the primary key. You can fix this "
"error by dump + DROP + CREATE + reimport "
"of the table.", ib_table->name);

if (push_warning) {
push_warning_printf(thd, Sql_condition::WARN_LEVEL_WARN,
ER_NO_SUCH_INDEX,
"InnoDB: Table %s has no "
"primary key in InnoDB data "
"dictionary, but has one in "
"MySQL!",
ib_table->name);
}
break;

case DICT_FRM_INCONSISTENT_KEYS:
sql_print_error("InnoDB: Table %s contains %lu "
"indexes inside InnoDB, which "
"is different from the number of "
"indexes %u defined in the MySQL "
" Have you mixed up "
".frm files from different "
"installations? See "
REFMAN
"innodb-troubleshooting.html\n",
ib_table->name, n_keys,
table->s->keys);

if (push_warning) {
push_warning_printf(thd, Sql_condition::WARN_LEVEL_WARN,
ER_NO_SUCH_INDEX,
"InnoDB: Table %s contains %lu "
"indexes inside InnoDB, which "
"is different from the number of "
"indexes %u defined in the MySQL ",
ib_table->name, n_keys,
table->s->keys);
}
break;

case DICT_FRM_CONSISTENT:
default:
sql_print_error("InnoDB: Table %s is consistent "
"on InnoDB data dictionary and MySQL "
" FRM file.",
ib_table->name);
ut_error;
break;
}
}
14 changes: 14 additions & 0 deletions storage/innobase/handler/ha_innodb.h
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
/*****************************************************************************
Copyright (c) 2000, 2012, Oracle and/or its affiliates. All Rights Reserved.
Copyright (c) 2013, 2016, 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
Expand Down Expand Up @@ -640,3 +641,16 @@ innobase_copy_frm_flags_from_table_share(
/*=====================================*/
dict_table_t* innodb_table, /*!< in/out: InnoDB table */
const TABLE_SHARE* table_share); /*!< in: table share */

/********************************************************************//**
Helper function to push frm mismatch error to error log and
if needed to sql-layer. */
UNIV_INTERN
void
ib_push_frm_error(
/*==============*/
THD* thd, /*!< in: MySQL thd */
dict_table_t* ib_table, /*!< in: InnoDB table */
TABLE* table, /*!< in: MySQL table */
ulint n_keys, /*!< in: InnoDB #keys */
bool push_warning); /*!< in: print warning ? */
14 changes: 14 additions & 0 deletions storage/innobase/handler/handler0alter.cc
Original file line number Diff line number Diff line change
Expand Up @@ -435,6 +435,20 @@ ha_innobase::check_if_supported_inplace_alter(
}
}

ulint n_indexes = UT_LIST_GET_LEN((prebuilt->table)->indexes);

/* If InnoDB dictionary and MySQL frm file are not consistent
use "Copy" method. */
if (prebuilt->table->dict_frm_mismatch) {

ha_alter_info->unsupported_reason = innobase_get_err_msg(
ER_NO_SUCH_INDEX);
ib_push_frm_error(user_thd, prebuilt->table, altered_table,
n_indexes, true);

DBUG_RETURN(HA_ALTER_INPLACE_NOT_SUPPORTED);
}

/* We should be able to do the operation in-place.
See if we can do it online (LOCK=NONE). */
bool online = true;
Expand Down
17 changes: 17 additions & 0 deletions storage/innobase/include/dict0mem.h
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
Copyright (c) 1996, 2015, Oracle and/or its affiliates. All Rights Reserved.
Copyright (c) 2012, Facebook Inc.
Copyright (c) 2015, 2016, 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
Expand Down Expand Up @@ -903,6 +904,18 @@ if table->memcached_sync_count == DICT_TABLE_IN_DDL means there's DDL running on
the table, DML from memcached will be blocked. */
#define DICT_TABLE_IN_DDL -1

/** These are used when MySQL FRM and InnoDB data dictionary are
in inconsistent state. */
typedef enum {
DICT_FRM_CONSISTENT = 0, /*!< Consistent state */
DICT_FRM_NO_PK = 1, /*!< MySQL has no primary key
but InnoDB dictionary has
non-generated one. */
DICT_NO_PK_FRM_HAS = 2, /*!< MySQL has primary key but
InnoDB dictionary has not. */
DICT_FRM_INCONSISTENT_KEYS = 3 /*!< Key count mismatch */
} dict_frm_t;

/** Data structure for a database table. Most fields will be
initialized to 0, NULL or FALSE in dict_mem_table_create(). */
struct dict_table_t{
Expand Down Expand Up @@ -961,6 +974,10 @@ struct dict_table_t{
/*!< True if the table belongs to a system
database (mysql, information_schema or
performance_schema) */
dict_frm_t dict_frm_mismatch;
/*!< !DICT_FRM_CONSISTENT==0 if data
dictionary information and
MySQL FRM information mismatch. */
#ifndef UNIV_HOTBACKUP
hash_node_t name_hash; /*!< hash chain node */
hash_node_t id_hash; /*!< hash chain node */
Expand Down
Loading

0 comments on commit 628bc57

Please sign in to comment.