MDEV-33277 In-place upgrade causes invalid AUTO_INCREMENT values

MDEV-33308 CHECK TABLE is modifying .frm file even if --read-only

As noted in commit d0ef1aaf61,
MySQL as well as older versions of MariaDB server would during
ALTER TABLE ... IMPORT TABLESPACE write bogus values to the
PAGE_MAX_TRX_ID field to pages of the clustered index, instead of
letting that field remain 0.
In commit 8777458a6e this field
was repurposed for PAGE_ROOT_AUTO_INC in the clustered index root page.

To avoid trouble when upgrading from MySQL or older versions of MariaDB,
we will try to detect and correct bogus values of PAGE_ROOT_AUTO_INC
when opening a table for the first time from the SQL layer.

btr_read_autoinc_with_fallback(): Add the parameters to mysql_version,max
to indicate the TABLE_SHARE::mysql_version of the .frm file and the
maximum value allowed for the type of the AUTO_INCREMENT column.
In case the table was originally created in MySQL or an older version of
MariaDB, read also the maximum value of the AUTO_INCREMENT column from
the table and reset the PAGE_ROOT_AUTO_INC if it is above the limit.

dict_table_t::get_index(const dict_col_t &) const: Find an index that
starts with the specified column.

ha_innobase::check_for_upgrade(): Return HA_ADMIN_FAILED if InnoDB
needs upgrading but is in read-only mode. In this way, the call to
update_frm_version() will be skipped.

row_import_autoinc(): Adjust the AUTO_INCREMENT column at the end of
ALTER TABLE...IMPORT TABLESPACE. This refinement was suggested by
Debarun Banerjee.

The changes outside InnoDB were developed by Michael 'Monty' Widenius:

Added print_check_msg() service for easy reporting of check/repair messages
in ENGINE=Aria and ENGINE=InnoDB.
Fixed that CHECK TABLE do not update the .frm file under --read-only.
Added 'handler_flags' to HA_CHECK_OPT as a way for storage engines to
store state from handler::check_for_upgrade().

Reviewed by: Debarun Banerjee
This commit is contained in:
Marko Mäkelä 2024-02-08 10:35:45 +02:00
parent 915d951431
commit 0381921e26
20 changed files with 723 additions and 144 deletions

View file

@ -0,0 +1,44 @@
/* Copyright (c) 2019, 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 Foundation; version 2 of the License.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program; if not, write to the Free Software
Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1335 USA */
#pragma once
/**
@file include/mysql/service_print_check_msg.h
This service provides functions to write messages for check or repair
*/
#ifdef __cplusplus
extern "C" {
#endif
extern struct print_check_msg_service_st {
void (*print_check_msg)(MYSQL_THD, const char *db_name, const char *table_name,
const char *op, const char *msg_type, const char *message,
my_bool print_to_log);
} *print_check_msg_service;
#ifdef MYSQL_DYNAMIC_PLUGIN
# define print_check_msg_context(_THD) print_check_msg_service->print_check_msg
#else
extern void print_check_msg(MYSQL_THD, const char *db_name, const char *table_name,
const char *op, const char *msg_type, const char *message,
my_bool print_to_log);
#endif
#ifdef __cplusplus
}
#endif

View file

@ -45,3 +45,4 @@
#define VERSION_json 0x0100
#define VERSION_sql_service 0x0100
#define VERSION_thd_mdl 0x0100
#define VERSION_print_check_msg 0x0100

View file

@ -25,6 +25,7 @@ SET(MYSQLSERVICES_SOURCES
my_crypt_service.c
my_md5_service.c
my_print_error_service.c
print_check_msg_service.c
my_sha1_service.c
my_sha2_service.c
my_snprintf_service.c

View file

@ -0,0 +1,18 @@
/* Copyright (c) 2024, MariaDB Plc
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 Foundation; version 2 of the License.
This program is distributed in the hope that it will be useful, but
WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program; if not, write to the Free Software
Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
*/
#include <service_versions.h>
SERVICE_VERSION print_check_msg_context= (void*) VERSION_print_check_msg;

Binary file not shown.

Binary file not shown.

View file

@ -0,0 +1,232 @@
CREATE TABLE t1 (id INT UNSIGNED AUTO_INCREMENT PRIMARY KEY) ENGINE=InnoDB;
INSERT INTO t1 VALUES(42);
CREATE TABLE t1b LIKE t1;
INSERT INTO t1b VALUES(3);
CREATE TABLE t1z LIKE t1;
CREATE TABLE t1t (id TINYINT AUTO_INCREMENT PRIMARY KEY) ENGINE=InnoDB;
CREATE TABLE t0t LIKE t1t;
INSERT INTO t1t VALUES(123);
FLUSH TABLES t1,t1b,t1t FOR EXPORT;
UNLOCK TABLES;
CREATE TABLE t5_7 LIKE t1;
CREATE TABLE t5_7b LIKE t1b;
CREATE TABLE t10_1 LIKE t1;
CREATE TABLE t10_1b LIKE t1b;
ALTER TABLE t1 DISCARD TABLESPACE;
ALTER TABLE t1b DISCARD TABLESPACE;
ALTER TABLE t1z DISCARD TABLESPACE;
ALTER TABLE t1t DISCARD TABLESPACE;
ALTER TABLE t0t DISCARD TABLESPACE;
ALTER TABLE t5_7 DISCARD TABLESPACE;
ALTER TABLE t5_7b DISCARD TABLESPACE;
ALTER TABLE t10_1 DISCARD TABLESPACE;
ALTER TABLE t10_1b DISCARD TABLESPACE;
FLUSH TABLES;
ALTER TABLE t0t IMPORT TABLESPACE;
Warnings:
Warning 1810 IO Read error: (2, No such file or directory) Error opening './test/t0t.cfg', will attempt to import without schema verification
INSERT INTO t0t VALUES(NULL);
SELECT * FROM t0t;
id
123
124
DROP TABLE t0t;
ALTER TABLE t1 IMPORT TABLESPACE;
Warnings:
Warning 1810 IO Read error: (2, No such file or directory) Error opening './test/t1.cfg', will attempt to import without schema verification
ALTER TABLE t1b IMPORT TABLESPACE;
Warnings:
Warning 1810 IO Read error: (2, No such file or directory) Error opening './test/t1b.cfg', will attempt to import without schema verification
ALTER TABLE t1z IMPORT TABLESPACE;
Warnings:
Warning 1810 IO Read error: (2, No such file or directory) Error opening './test/t1z.cfg', will attempt to import without schema verification
ALTER TABLE t1t IMPORT TABLESPACE;
Warnings:
Warning 1810 IO Read error: (2, No such file or directory) Error opening './test/t1t.cfg', will attempt to import without schema verification
ALTER TABLE t5_7 IMPORT TABLESPACE;
Warnings:
Warning 1810 IO Read error: (2, No such file or directory) Error opening './test/t5_7.cfg', will attempt to import without schema verification
ALTER TABLE t5_7b IMPORT TABLESPACE;
Warnings:
Warning 1810 IO Read error: (2, No such file or directory) Error opening './test/t5_7b.cfg', will attempt to import without schema verification
ALTER TABLE t10_1 IMPORT TABLESPACE;
Warnings:
Warning 1810 IO Read error: (2, No such file or directory) Error opening './test/t10_1.cfg', will attempt to import without schema verification
ALTER TABLE t10_1b IMPORT TABLESPACE;
Warnings:
Warning 1810 IO Read error: (2, No such file or directory) Error opening './test/t10_1b.cfg', will attempt to import without schema verification
FOUND 1 /InnoDB: Resetting PAGE_ROOT_AUTO_INC from 128 to 123 on table `test`\.`t0t`/ in mysqld.1.err
FOUND 1 /InnoDB: Resetting PAGE_ROOT_AUTO_INC from 0 to 42 on table `test`\.`t1z`/ in mysqld.1.err
FOUND 1 /InnoDB: Resetting PAGE_ROOT_AUTO_INC from 128 to 123 on table `test`\.`t1t`/ in mysqld.1.err
FOUND 1 /InnoDB: Resetting PAGE_ROOT_AUTO_INC from 3 to 42 on table `test`\.`t5_7` \(created with version 50744\)/ in mysqld.1.err
FOUND 1 /InnoDB: Resetting PAGE_ROOT_AUTO_INC from 3 to 42 on table `test`\.`t10_1` \(created with version 100149\)/ in mysqld.1.err
FOUND 5 /InnoDB: Resetting PAGE_ROOT_AUTO_INC/ in mysqld.1.err
# restart: --read-only
CHECK TABLE t1, t1b, t1t, t1z, t5_7, t5_7b, t10_1, t10_1b;
Table Op Msg_type Msg_text
test.t1 check status OK
test.t1b check status OK
test.t1t check status OK
test.t1z check status OK
test.t5_7 check note Auto_increment will be checked on each open until CHECK TABLE FOR UPGRADE is executed
test.t5_7 check status OK
test.t5_7b check note Auto_increment will be checked on each open until CHECK TABLE FOR UPGRADE is executed
test.t5_7b check status OK
test.t10_1 check note Auto_increment will be checked on each open until CHECK TABLE FOR UPGRADE is executed
test.t10_1 check status OK
test.t10_1b check note Auto_increment will be checked on each open until CHECK TABLE FOR UPGRADE is executed
test.t10_1b check status OK
CHECK TABLE t1, t1b, t1t, t1z, t5_7, t5_7b, t10_1, t10_1b FOR UPGRADE;
Table Op Msg_type Msg_text
test.t1 check status OK
test.t1b check status OK
test.t1t check status OK
test.t1z check status OK
test.t5_7 check note Auto_increment will be checked on each open until CHECK TABLE FOR UPGRADE is executed
test.t5_7 check status OK
test.t5_7b check note Auto_increment will be checked on each open until CHECK TABLE FOR UPGRADE is executed
test.t5_7b check status OK
test.t10_1 check note Auto_increment will be checked on each open until CHECK TABLE FOR UPGRADE is executed
test.t10_1 check status OK
test.t10_1b check note Auto_increment will be checked on each open until CHECK TABLE FOR UPGRADE is executed
test.t10_1b check status OK
# restart: --innodb-read-only --read-only
CHECK TABLE t1, t1b, t1t, t1z, t5_7, t5_7b, t10_1, t10_1b;
Table Op Msg_type Msg_text
test.t1 check status OK
test.t1b check status OK
test.t1t check status OK
test.t1z check status OK
test.t5_7 check note Auto_increment will be checked on each open until CHECK TABLE FOR UPGRADE is executed
test.t5_7 check status OK
test.t5_7b check note Auto_increment will be checked on each open until CHECK TABLE FOR UPGRADE is executed
test.t5_7b check status OK
test.t10_1 check note Auto_increment will be checked on each open until CHECK TABLE FOR UPGRADE is executed
test.t10_1 check status OK
test.t10_1b check note Auto_increment will be checked on each open until CHECK TABLE FOR UPGRADE is executed
test.t10_1b check status OK
CHECK TABLE t1, t1b, t1t, t1z, t5_7, t5_7b, t10_1, t10_1b FOR UPGRADE;
Table Op Msg_type Msg_text
test.t1 check status OK
test.t1b check status OK
test.t1t check status OK
test.t1z check status OK
test.t5_7 check note Auto_increment will be checked on each open until CHECK TABLE FOR UPGRADE is executed
test.t5_7 check status OK
test.t5_7b check note Auto_increment will be checked on each open until CHECK TABLE FOR UPGRADE is executed
test.t5_7b check status OK
test.t10_1 check note Auto_increment will be checked on each open until CHECK TABLE FOR UPGRADE is executed
test.t10_1 check status OK
test.t10_1b check note Auto_increment will be checked on each open until CHECK TABLE FOR UPGRADE is executed
test.t10_1b check status OK
# restart: --innodb-read-only
CHECK TABLE t1, t1b, t1t, t1z, t5_7, t5_7b, t10_1, t10_1b;
Table Op Msg_type Msg_text
test.t1 check status OK
test.t1b check status OK
test.t1t check status OK
test.t1z check status OK
test.t5_7 check status Operation failed
test.t5_7b check status Operation failed
test.t10_1 check status Operation failed
test.t10_1b check status Operation failed
CHECK TABLE t1, t1b, t1t, t1z, t5_7, t5_7b, t10_1, t10_1b FOR UPGRADE;
Table Op Msg_type Msg_text
test.t1 check status OK
test.t1b check status OK
test.t1t check status OK
test.t1z check status OK
test.t5_7 check status Operation failed
test.t5_7b check status Operation failed
test.t10_1 check status Operation failed
test.t10_1b check status Operation failed
SELECT COUNT(*) FROM t1;
COUNT(*)
1
SELECT COUNT(*) FROM t1b;
COUNT(*)
1
SELECT COUNT(*) FROM t1t;
COUNT(*)
1
SELECT COUNT(*) FROM t1z;
COUNT(*)
1
SELECT COUNT(*) FROM t5_7;
COUNT(*)
1
SELECT COUNT(*) FROM t5_7b;
COUNT(*)
1
SELECT COUNT(*) FROM t10_1;
COUNT(*)
1
SELECT COUNT(*) FROM t10_1b;
COUNT(*)
1
# restart
CHECK TABLE t1, t1b, t1t, t1z, t5_7, t5_7b, t10_1, t10_1b FOR UPGRADE;
Table Op Msg_type Msg_text
test.t1 check status OK
test.t1b check status OK
test.t1t check status OK
test.t1z check status OK
test.t5_7 check note Auto_increment checked and .frm file version updated
test.t5_7 check status OK
test.t5_7b check note Auto_increment checked and .frm file version updated
test.t5_7b check status OK
test.t10_1 check note Auto_increment checked and .frm file version updated
test.t10_1 check status OK
test.t10_1b check note Auto_increment checked and .frm file version updated
test.t10_1b check status OK
INSERT INTO t1 VALUES(NULL);
INSERT INTO t1b VALUES(NULL);
INSERT INTO t1t VALUES(NULL);
INSERT INTO t1z VALUES(NULL);
INSERT INTO t5_7 VALUES(NULL);
INSERT INTO t5_7b VALUES(NULL);
INSERT INTO t10_1 VALUES(NULL);
INSERT INTO t10_1b VALUES(NULL);
CHECK TABLE t1, t1b, t1t, t1z, t5_7, t5_7b, t10_1, t10_1b FOR UPGRADE;
Table Op Msg_type Msg_text
test.t1 check status OK
test.t1b check status OK
test.t1t check status OK
test.t1z check status OK
test.t5_7 check status OK
test.t5_7b check status OK
test.t10_1 check status OK
test.t10_1b check status OK
SELECT * FROM t1;
id
4
42
SELECT * FROM t1b;
id
3
347
SELECT * FROM t1t;
id
123
124
SELECT * FROM t1z;
id
42
43
SELECT * FROM t5_7;
id
42
43
SELECT * FROM t5_7b;
id
3
347
SELECT * FROM t10_1;
id
42
43
SELECT * FROM t10_1b;
id
3
347
DROP TABLE t1, t1b, t1t, t1z, t5_7, t5_7b, t10_1, t10_1b;

View file

@ -0,0 +1,168 @@
--source include/have_innodb.inc
CREATE TABLE t1 (id INT UNSIGNED AUTO_INCREMENT PRIMARY KEY) ENGINE=InnoDB;
INSERT INTO t1 VALUES(42);
CREATE TABLE t1b LIKE t1;
INSERT INTO t1b VALUES(3);
CREATE TABLE t1z LIKE t1;
CREATE TABLE t1t (id TINYINT AUTO_INCREMENT PRIMARY KEY) ENGINE=InnoDB;
CREATE TABLE t0t LIKE t1t;
INSERT INTO t1t VALUES(123);
--let DATADIR=`select @@datadir`
--let PAGE_SIZE=`select @@innodb_page_size`
FLUSH TABLES t1,t1b,t1t FOR EXPORT;
--copy_file $DATADIR/test/t1.ibd $DATADIR/test/t.ibd
--copy_file $DATADIR/test/t1.ibd $DATADIR/test/tz.ibd
--copy_file $DATADIR/test/t1b.ibd $DATADIR/test/tb.ibd
--copy_file $DATADIR/test/t1t.ibd $DATADIR/test/tt.ibd
UNLOCK TABLES;
CREATE TABLE t5_7 LIKE t1;
CREATE TABLE t5_7b LIKE t1b;
CREATE TABLE t10_1 LIKE t1;
CREATE TABLE t10_1b LIKE t1b;
ALTER TABLE t1 DISCARD TABLESPACE;
ALTER TABLE t1b DISCARD TABLESPACE;
ALTER TABLE t1z DISCARD TABLESPACE;
ALTER TABLE t1t DISCARD TABLESPACE;
ALTER TABLE t0t DISCARD TABLESPACE;
ALTER TABLE t5_7 DISCARD TABLESPACE;
ALTER TABLE t5_7b DISCARD TABLESPACE;
ALTER TABLE t10_1 DISCARD TABLESPACE;
ALTER TABLE t10_1b DISCARD TABLESPACE;
FLUSH TABLES;
# Update the PAGE_ROOT_AUTO_INC field of a few files.
perl;
do "$ENV{MTR_SUITE_DIR}/include/crc32.pl";
sub update_autoinc
{
my ($file, $value) = @_;
open(FILE, "+<$file") || die "Unable to open $file";
binmode FILE;
my $ps= $ENV{PAGE_SIZE};
my $page;
die "Unable to read $file" unless sysread(FILE, $page, $ps) == $ps;
my $full_crc32 = unpack("N",substr($page,54,4)) & 0x10; # FIL_SPACE_FLAGS
sysseek(FILE, 3*$ps, 0) || die "Unable to seek $file\n";
die "Unable to read $file" unless sysread(FILE, $page, $ps) == $ps;
substr($page,56,8)=pack("NN",0,$value);
my $polynomial = 0x82f63b78; # CRC-32C
if ($full_crc32) {
my $ck = mycrc32(substr($page, 0, $ps-4), 0, $polynomial);
substr($page, $ps-4, 4) = pack("N", $ck);
}
else
{
my $ck= pack("N",mycrc32(substr($page, 4, 22), 0, $polynomial) ^
mycrc32(substr($page, 38, $ps - 38 - 8), 0, $polynomial));
substr($page,0,4)=$ck;
substr($page,$ps-8,4)=$ck;
}
sysseek(FILE, 3*$ps, 0) || die "Unable to rewind $file\n";
syswrite(FILE, $page, $ps)==$ps || die "Unable to write $file\n";
close(FILE) || die "Unable to close $file";
}
update_autoinc("$ENV{DATADIR}/test/tz.ibd", 0);
update_autoinc("$ENV{DATADIR}/test/t.ibd", 3);
update_autoinc("$ENV{DATADIR}/test/tb.ibd", 346);
update_autoinc("$ENV{DATADIR}/test/tt.ibd", 128);
EOF
--remove_file $DATADIR/test/t5_7.frm
--remove_file $DATADIR/test/t5_7b.frm
--copy_file $MYSQL_TEST_DIR/std_data/autoinc_import_57.frm $DATADIR/test/t5_7.frm
--copy_file $MYSQL_TEST_DIR/std_data/autoinc_import_57.frm $DATADIR/test/t5_7b.frm
--remove_file $DATADIR/test/t10_1.frm
--remove_file $DATADIR/test/t10_1b.frm
--copy_file $MYSQL_TEST_DIR/std_data/autoinc_import_101.frm $DATADIR/test/t10_1.frm
--copy_file $MYSQL_TEST_DIR/std_data/autoinc_import_101.frm $DATADIR/test/t10_1b.frm
--copy_file $DATADIR/test/t.ibd $DATADIR/test/t5_7.ibd
--copy_file $DATADIR/test/tb.ibd $DATADIR/test/t5_7b.ibd
--copy_file $DATADIR/test/t.ibd $DATADIR/test/t10_1.ibd
--copy_file $DATADIR/test/tb.ibd $DATADIR/test/t10_1b.ibd
--move_file $DATADIR/test/t.ibd $DATADIR/test/t1.ibd
--move_file $DATADIR/test/tb.ibd $DATADIR/test/t1b.ibd
--copy_file $DATADIR/test/tt.ibd $DATADIR/test/t0t.ibd
--move_file $DATADIR/test/tt.ibd $DATADIR/test/t1t.ibd
--move_file $DATADIR/test/tz.ibd $DATADIR/test/t1z.ibd
ALTER TABLE t0t IMPORT TABLESPACE;
INSERT INTO t0t VALUES(NULL);
SELECT * FROM t0t;
DROP TABLE t0t;
ALTER TABLE t1 IMPORT TABLESPACE;
ALTER TABLE t1b IMPORT TABLESPACE;
ALTER TABLE t1z IMPORT TABLESPACE;
ALTER TABLE t1t IMPORT TABLESPACE;
ALTER TABLE t5_7 IMPORT TABLESPACE;
ALTER TABLE t5_7b IMPORT TABLESPACE;
ALTER TABLE t10_1 IMPORT TABLESPACE;
ALTER TABLE t10_1b IMPORT TABLESPACE;
--let SEARCH_FILE= $MYSQLTEST_VARDIR/log/mysqld.1.err
--let SEARCH_PATTERN= InnoDB: Resetting PAGE_ROOT_AUTO_INC from 128 to 123 on table `test`\.`t0t`
--source include/search_pattern_in_file.inc
--let SEARCH_PATTERN= InnoDB: Resetting PAGE_ROOT_AUTO_INC from 0 to 42 on table `test`\.`t1z`
--source include/search_pattern_in_file.inc
--let SEARCH_PATTERN= InnoDB: Resetting PAGE_ROOT_AUTO_INC from 128 to 123 on table `test`\.`t1t`
--source include/search_pattern_in_file.inc
--let SEARCH_PATTERN= InnoDB: Resetting PAGE_ROOT_AUTO_INC from 3 to 42 on table `test`\.`t5_7` \(created with version 50744\)
--source include/search_pattern_in_file.inc
--let SEARCH_PATTERN= InnoDB: Resetting PAGE_ROOT_AUTO_INC from 3 to 42 on table `test`\.`t10_1` \(created with version 100149\)
--source include/search_pattern_in_file.inc
--let SEARCH_PATTERN= InnoDB: Resetting PAGE_ROOT_AUTO_INC
--source include/search_pattern_in_file.inc
# Restart, so that the InnoDB tables will be loaded into the data dictionary.
--let $restart_parameters=--read-only
--source include/restart_mysqld.inc
CHECK TABLE t1, t1b, t1t, t1z, t5_7, t5_7b, t10_1, t10_1b;
CHECK TABLE t1, t1b, t1t, t1z, t5_7, t5_7b, t10_1, t10_1b FOR UPGRADE;
--let $restart_parameters=--innodb-read-only --read-only
--source include/restart_mysqld.inc
CHECK TABLE t1, t1b, t1t, t1z, t5_7, t5_7b, t10_1, t10_1b;
CHECK TABLE t1, t1b, t1t, t1z, t5_7, t5_7b, t10_1, t10_1b FOR UPGRADE;
--let $restart_parameters=--innodb-read-only
--source include/restart_mysqld.inc
CHECK TABLE t1, t1b, t1t, t1z, t5_7, t5_7b, t10_1, t10_1b;
CHECK TABLE t1, t1b, t1t, t1z, t5_7, t5_7b, t10_1, t10_1b FOR UPGRADE;
SELECT COUNT(*) FROM t1;
SELECT COUNT(*) FROM t1b;
SELECT COUNT(*) FROM t1t;
SELECT COUNT(*) FROM t1z;
SELECT COUNT(*) FROM t5_7;
SELECT COUNT(*) FROM t5_7b;
SELECT COUNT(*) FROM t10_1;
SELECT COUNT(*) FROM t10_1b;
--let $restart_parameters=
--source include/restart_mysqld.inc
CHECK TABLE t1, t1b, t1t, t1z, t5_7, t5_7b, t10_1, t10_1b FOR UPGRADE;
INSERT INTO t1 VALUES(NULL);
INSERT INTO t1b VALUES(NULL);
INSERT INTO t1t VALUES(NULL);
INSERT INTO t1z VALUES(NULL);
INSERT INTO t5_7 VALUES(NULL);
INSERT INTO t5_7b VALUES(NULL);
INSERT INTO t10_1 VALUES(NULL);
INSERT INTO t10_1b VALUES(NULL);
CHECK TABLE t1, t1b, t1t, t1z, t5_7, t5_7b, t10_1, t10_1b FOR UPGRADE;
SELECT * FROM t1;
SELECT * FROM t1b;
SELECT * FROM t1t;
SELECT * FROM t1z;
SELECT * FROM t5_7;
SELECT * FROM t5_7b;
SELECT * FROM t10_1;
SELECT * FROM t10_1b;
DROP TABLE t1, t1b, t1t, t1z, t5_7, t5_7b, t10_1, t10_1b;

View file

@ -4669,7 +4669,7 @@ int handler::ha_check(THD *thd, HA_CHECK_OPT *check_opt)
if (unlikely((error= check(thd, check_opt))))
return error;
/* Skip updating frm version if not main handler. */
if (table->file != this)
if (table->file != this || opt_readonly)
return error;
return update_frm_version(table);
}
@ -4718,7 +4718,7 @@ int handler::ha_repair(THD* thd, HA_CHECK_OPT* check_opt)
DBUG_ASSERT(result == HA_ADMIN_NOT_IMPLEMENTED ||
ha_table_flags() & HA_CAN_REPAIR);
if (result == HA_ADMIN_OK)
if (result == HA_ADMIN_OK && !opt_readonly)
result= update_frm_version(table);
return result;
}
@ -5659,7 +5659,7 @@ err:
void st_ha_check_opt::init()
{
flags= sql_flags= 0;
flags= sql_flags= handler_flags= 0;
start_time= my_time(0);
}

View file

@ -2615,6 +2615,7 @@ typedef struct st_ha_check_opt
st_ha_check_opt() = default; /* Remove gcc warning */
uint flags; /* isam layer flags (e.g. for myisamchk) */
uint sql_flags; /* sql layer flags - for something myisamchk cannot do */
uint handler_flags; /* Reserved for handler usage */
time_t start_time; /* When check/repair starts */
KEY_CACHE *key_cache; /* new key cache when changing key cache */
void init();

View file

@ -5665,6 +5665,40 @@ extern "C" void *thd_mdl_context(MYSQL_THD thd)
return &thd->mdl_context;
}
/**
Send check/repair message to the user
@param op one of check or repair
@param msg_type one of info, warning or error
@param print_to_log <> 0 if we should also print the message to error log.
*/
extern "C" void
print_check_msg(THD *thd, const char *db_name, const char *table_name, const char *op,
const char *msg_type, const char *message, my_bool print_to_log)
{
char name[NAME_LEN * 2 + 2];
Protocol *protocol= thd->protocol;
DBUG_ASSERT(strlen(db_name) <= NAME_LEN);
DBUG_ASSERT(strlen(table_name) <= NAME_LEN);
size_t length= size_t(strxnmov(name, sizeof name - 1,
db_name, ".", table_name, NullS) -
name);
protocol->prepare_for_resend();
protocol->store(name, length, system_charset_info);
protocol->store(op, system_charset_info);
protocol->store(msg_type, system_charset_info);
protocol->store(message, system_charset_info);
if (protocol->write())
sql_print_error("Failed on my_net_write, writing to stderr instead: %s: %s\n",
table_name, message);
else if (thd->variables.log_warnings > 2 && print_to_log)
sql_print_error("%s: table '%s' got '%s' during %s",
msg_type, table_name, message, op);
}
/****************************************************************************
Handling of statement states in functions and triggers.

View file

@ -18,6 +18,7 @@
#include <service_versions.h>
#include <mysql/service_wsrep.h>
#include <mysql/service_thd_mdl.h>
#include <mysql/service_print_check_msg.h>
struct st_service_ref {
const char *name;
@ -217,6 +218,11 @@ static struct my_print_error_service_st my_print_error_handler=
my_printv_error
};
static struct print_check_msg_service_st print_check_msg_handler=
{
print_check_msg
};
struct json_service_st json_handler=
{
json_type,
@ -264,6 +270,7 @@ static struct st_service_ref list_of_services[]=
{ "my_crypt_service", VERSION_my_crypt, &crypt_handler},
{ "my_md5_service", VERSION_my_md5, &my_md5_handler},
{ "my_print_error_service", VERSION_my_print_error, &my_print_error_handler},
{ "print_check_msg_service", VERSION_print_check_msg, &print_check_msg_handler},
{ "my_sha1_service", VERSION_my_sha1, &my_sha1_handler},
{ "my_sha2_service", VERSION_my_sha2, &my_sha2_handler},
{ "my_snprintf_service", VERSION_my_snprintf, &my_snprintf_handler },

View file

@ -43,6 +43,7 @@ Created 6/2/1994 Heikki Tuuri
#include "gis0geo.h"
#include "dict0boot.h"
#include "row0sel.h" /* row_search_max_autoinc() */
#include "log.h"
Atomic_counter<uint32_t> btr_validate_index_running;
@ -1267,53 +1268,71 @@ btr_read_autoinc(dict_index_t* index)
return autoinc;
}
dict_index_t *dict_table_t::get_index(const dict_col_t &col) const
{
dict_index_t *index= dict_table_get_first_index(this);
while (index && (index->fields[0].col != &col || index->is_corrupted()))
index= dict_table_get_next_index(index);
return index;
}
/** Read the last used AUTO_INCREMENT value from PAGE_ROOT_AUTO_INC,
or fall back to MAX(auto_increment_column).
@param[in] table table containing an AUTO_INCREMENT column
@param[in] col_no index of the AUTO_INCREMENT column
@return the AUTO_INCREMENT value
@retval 0 on error or if no AUTO_INCREMENT value was used yet */
ib_uint64_t
btr_read_autoinc_with_fallback(const dict_table_t* table, unsigned col_no)
@param table table containing an AUTO_INCREMENT column
@param col_no index of the AUTO_INCREMENT column
@param mysql_version TABLE_SHARE::mysql_version
@param max the maximum value of the AUTO_INCREMENT column
@return the AUTO_INCREMENT value
@retval 0 on error or if no AUTO_INCREMENT value was used yet */
uint64_t btr_read_autoinc_with_fallback(const dict_table_t *table,
unsigned col_no, ulong mysql_version,
uint64_t max)
{
ut_ad(table->persistent_autoinc);
ut_ad(!table->is_temporary());
ut_ad(table->persistent_autoinc);
ut_ad(!table->is_temporary());
dict_index_t* index = dict_table_get_first_index(table);
uint64_t autoinc= 0;
mtr_t mtr;
mtr.start();
if (index == NULL) {
return 0;
}
if (buf_block_t *block=
buf_page_get(page_id_t(table->space_id,
dict_table_get_first_index(table)->page),
table->space->zip_size(), RW_SX_LATCH, &mtr))
{
autoinc= page_get_autoinc(block->frame);
mtr_t mtr;
mtr.start();
buf_block_t* block = buf_page_get(
page_id_t(index->table->space_id, index->page),
index->table->space->zip_size(),
RW_S_LATCH, &mtr);
if (autoinc > 0 && autoinc <= max && mysql_version >= 100210);
else if (dict_index_t *index=
table->get_index(*dict_table_get_nth_col(table, col_no)))
{
/* Read MAX(autoinc_col), in case this table had originally been
created before MariaDB 10.2.4 introduced persistent AUTO_INCREMENT
and MariaDB 10.2.10 fixed MDEV-12123, and there could be a garbage
value in the PAGE_ROOT_AUTO_INC field. */
const uint64_t max_autoinc= row_search_max_autoinc(index);
const bool need_adjust{autoinc > max || autoinc < max_autoinc};
ut_ad(max_autoinc <= max);
ib_uint64_t autoinc = block ? page_get_autoinc(block->frame) : 0;
const bool retry = block && autoinc == 0
&& !page_is_empty(block->frame);
mtr.commit();
if (UNIV_UNLIKELY(need_adjust) && !high_level_read_only && !opt_readonly)
{
sql_print_information("InnoDB: Resetting PAGE_ROOT_AUTO_INC from "
UINT64PF " to " UINT64PF
" on table %`.*s.%`s (created with version %lu)",
autoinc, max_autoinc,
int(table->name.dblen()), table->name.m_name,
table->name.basename(), mysql_version);
autoinc= max_autoinc;
index->set_modified(mtr);
page_set_autoinc(block, max_autoinc, &mtr, true);
}
}
}
if (retry) {
/* This should be an old data file where
PAGE_ROOT_AUTO_INC was initialized to 0.
Fall back to reading MAX(autoinc_col).
There should be an index on it. */
const dict_col_t* autoinc_col
= dict_table_get_nth_col(table, col_no);
while (index && index->fields[0].col != autoinc_col) {
index = dict_table_get_next_index(index);
}
if (index) {
autoinc = row_search_max_autoinc(index);
}
}
return autoinc;
mtr.commit();
return autoinc;
}
/** Write the next available AUTO_INCREMENT value to PAGE_ROOT_AUTO_INC.

View file

@ -54,10 +54,13 @@ this program; if not, write to the Free Software Foundation, Inc.,
#include <my_bitmap.h>
#include <mysql/service_thd_alloc.h>
#include <mysql/service_thd_wait.h>
#include <mysql/service_print_check_msg.h>
#include "sql_type_geom.h"
#include "scope.h"
#include "srv0srv.h"
extern my_bool opt_readonly;
// MYSQL_PLUGIN_IMPORT extern my_bool lower_case_file_system;
// MYSQL_PLUGIN_IMPORT extern char mysql_unpacked_real_data_home[];
@ -117,6 +120,7 @@ this program; if not, write to the Free Software Foundation, Inc.,
#include "row0ext.h"
#include <limits>
#include <myisamchk.h> // TT_FOR_UPGRADE
#define thd_get_trx_isolation(X) ((enum_tx_isolation)thd_tx_isolation(X))
@ -5620,14 +5624,10 @@ func_exit:
return ret;
}
/********************************************************************//**
Get the upper limit of the MySQL integral and floating-point type.
@return maximum allowed value for the field */
UNIV_INTERN
ulonglong
innobase_get_int_col_max_value(
/*===========================*/
const Field* field) /*!< in: MySQL field */
/** Get the maximum integer value of a numeric column.
@param field column definition
@return maximum allowed integer value */
ulonglong innobase_get_int_col_max_value(const Field *field)
{
ulonglong max_value = 0;
@ -5691,46 +5691,45 @@ ha_innobase::open().
@param[in,out] table persistent table
@param[in] field the AUTO_INCREMENT column */
static
void
initialize_auto_increment(dict_table_t* table, const Field* field)
static void initialize_auto_increment(dict_table_t *table, const Field& field,
const TABLE_SHARE &s)
{
ut_ad(!table->is_temporary());
ut_ad(!table->is_temporary());
const unsigned col_no= innodb_col_no(&field);
table->autoinc_mutex.lock();
table->persistent_autoinc=
uint16_t(dict_table_get_nth_col_pos(table, col_no, nullptr) + 1) &
dict_index_t::MAX_N_FIELDS;
if (table->autoinc)
/* Already initialized. Our caller checked
table->persistent_autoinc without
autoinc_mutex protection, and there might be multiple
ha_innobase::open() executing concurrently. */;
else if (srv_force_recovery >= SRV_FORCE_NO_UNDO_LOG_SCAN)
/* If innodb_force_recovery is set so high that writes
are disabled we force the AUTOINC counter to 0
value effectively disabling writes to the table.
Secondly, we avoid reading the table in case the read
results in failure due to a corrupted table/index.
const unsigned col_no = innodb_col_no(field);
We will not return an error to the client, so that the
tables can be dumped with minimal hassle. If an error
were returned in this case, the first attempt to read
the table would fail and subsequent SELECTs would succeed. */;
else if (table->persistent_autoinc)
{
uint64_t max_value= innobase_get_int_col_max_value(&field);
table->autoinc=
innobase_next_autoinc(btr_read_autoinc_with_fallback(table, col_no,
s.mysql_version,
max_value),
1 /* need */,
1 /* auto_increment_increment */,
0 /* auto_increment_offset */,
max_value);
}
table->autoinc_mutex.lock();
table->persistent_autoinc = static_cast<uint16_t>(
dict_table_get_nth_col_pos(table, col_no, NULL) + 1)
& dict_index_t::MAX_N_FIELDS;
if (table->autoinc) {
/* Already initialized. Our caller checked
table->persistent_autoinc without
autoinc_mutex protection, and there might be multiple
ha_innobase::open() executing concurrently. */
} else if (srv_force_recovery > SRV_FORCE_NO_IBUF_MERGE) {
/* If the recovery level is set so high that writes
are disabled we force the AUTOINC counter to 0
value effectively disabling writes to the table.
Secondly, we avoid reading the table in case the read
results in failure due to a corrupted table/index.
We will not return an error to the client, so that the
tables can be dumped with minimal hassle. If an error
were returned in this case, the first attempt to read
the table would fail and subsequent SELECTs would succeed. */
} else if (table->persistent_autoinc) {
table->autoinc = innobase_next_autoinc(
btr_read_autoinc_with_fallback(table, col_no),
1 /* need */,
1 /* auto_increment_increment */,
0 /* auto_increment_offset */,
innobase_get_int_col_max_value(field));
}
table->autoinc_mutex.unlock();
table->autoinc_mutex.unlock();
}
/** Open an InnoDB table
@ -5966,7 +5965,7 @@ ha_innobase::open(const char* name, int, uint)
|| m_prebuilt->table->persistent_autoinc
|| !m_prebuilt->table->is_readable()) {
} else if (const Field* ai = table->found_next_number_field) {
initialize_auto_increment(m_prebuilt->table, ai);
initialize_auto_increment(m_prebuilt->table, *ai, *table->s);
}
/* Set plugin parser for fulltext index */
@ -14822,12 +14821,34 @@ ha_innobase::check(
ulint n_rows_in_table = ULINT_UNDEFINED;
bool is_ok = true;
dberr_t ret;
uint handler_flags= check_opt->handler_flags;
DBUG_ENTER("ha_innobase::check");
DBUG_ASSERT(thd == ha_thd());
ut_a(m_prebuilt->trx->magic_n == TRX_MAGIC_N);
ut_a(m_prebuilt->trx == thd_to_trx(thd));
if (handler_flags || check_for_upgrade(check_opt)) {
/* The file was already checked and fixed as part of open */
print_check_msg(thd, table->s->db.str, table->s->table_name.str,
"check", "note",
(opt_readonly || high_level_read_only
|| !(check_opt->sql_flags & TT_FOR_UPGRADE))
? "Auto_increment will be"
" checked on each open until"
" CHECK TABLE FOR UPGRADE is executed"
: "Auto_increment checked and"
" .frm file version updated", 1);
if (handler_flags && (check_opt->sql_flags & TT_FOR_UPGRADE)) {
/*
No other issues found (as handler_flags was only
set if there as not other problems with the table
than auto_increment).
*/
DBUG_RETURN(HA_ADMIN_OK);
}
}
if (m_prebuilt->mysql_template == NULL) {
/* Build the template; we will use a dummy template
in index scans done in checking */
@ -15053,6 +15074,35 @@ ha_innobase::check(
DBUG_RETURN(is_ok ? HA_ADMIN_OK : HA_ADMIN_CORRUPT);
}
/**
Check if we there is a problem with the InnoDB table.
@param check_opt check options
@retval HA_ADMIN_OK if Table is ok
@retval HA_ADMIN_NEEDS_ALTER User should run ALTER TABLE FOR UPGRADE
@retval HA_ADMIN_NEEDS_CHECK User should run CHECK TABLE FOR UPGRADE
@retval HA_ADMIN_FAILED if InnoDB is in read-only mode */
int ha_innobase::check_for_upgrade(HA_CHECK_OPT *check_opt)
{
/*
Check if there is a possibility that the auto increment value
stored in PAGE_ROOT_AUTO_INC could be corrupt.
*/
if (table->s->mysql_version >= 100210);
else if (const Field *auto_increment= table->found_next_number_field)
{
uint col_no= innodb_col_no(auto_increment);
const dict_col_t *autoinc_col=
dict_table_get_nth_col(m_prebuilt->table, col_no);
if (m_prebuilt->table->get_index(*autoinc_col))
{
check_opt->handler_flags= 1;
return (high_level_read_only && !opt_readonly)
? HA_ADMIN_FAILED : HA_ADMIN_NEEDS_CHECK;
}
}
return HA_ADMIN_OK;
}
/*******************************************************************//**
Gets the foreign key create info for a table stored in InnoDB.
@return own: character string in the form which can be inserted to the

View file

@ -207,6 +207,7 @@ public:
int rename_table(const char* from, const char* to) override;
inline int defragment_table(const char* name);
int check(THD* thd, HA_CHECK_OPT* check_opt) override;
int check_for_upgrade(HA_CHECK_OPT* check_opt) override;
inline void reload_statistics();
@ -944,6 +945,12 @@ unsigned
innodb_col_no(const Field* field)
MY_ATTRIBUTE((nonnull, warn_unused_result));
/** Get the maximum integer value of a numeric column.
@param field column definition
@return maximum allowed integer value */
ulonglong innobase_get_int_col_max_value(const Field *field)
MY_ATTRIBUTE((nonnull, warn_unused_result));
/********************************************************************//**
Helper function to push frm mismatch error to error log and
if needed to sql-layer. */

View file

@ -1179,15 +1179,6 @@ private:
ha_innobase_inplace_ctx& operator=(const ha_innobase_inplace_ctx&);
};
/********************************************************************//**
Get the upper limit of the MySQL integral and floating-point type.
@return maximum allowed value for the field */
UNIV_INTERN
ulonglong
innobase_get_int_col_max_value(
/*===========================*/
const Field* field); /*!< in: MySQL field */
/* Report an InnoDB error to the client by invoking my_error(). */
static ATTRIBUTE_COLD __attribute__((nonnull))
void
@ -9736,13 +9727,7 @@ commit_set_autoinc(
const dict_col_t* autoinc_col
= dict_table_get_nth_col(ctx->old_table,
innodb_col_no(ai));
dict_index_t* index
= dict_table_get_first_index(ctx->old_table);
while (index != NULL
&& index->fields[0].col != autoinc_col) {
index = dict_table_get_next_index(index);
}
auto index = ctx->old_table->get_index(*autoinc_col);
ut_ad(index);
ib_uint64_t max_in_table = index

View file

@ -372,13 +372,16 @@ btr_read_autoinc(dict_index_t* index)
/** Read the last used AUTO_INCREMENT value from PAGE_ROOT_AUTO_INC,
or fall back to MAX(auto_increment_column).
@param[in] table table containing an AUTO_INCREMENT column
@param[in] col_no index of the AUTO_INCREMENT column
@return the AUTO_INCREMENT value
@retval 0 on error or if no AUTO_INCREMENT value was used yet */
ib_uint64_t
btr_read_autoinc_with_fallback(const dict_table_t* table, unsigned col_no)
MY_ATTRIBUTE((nonnull, warn_unused_result));
@param table table containing an AUTO_INCREMENT column
@param col_no index of the AUTO_INCREMENT column
@param mysql_version TABLE_SHARE::mysql_version
@param max the maximum value of the AUTO_INCREMENT column
@return the AUTO_INCREMENT value
@retval 0 on error or if no AUTO_INCREMENT value was used yet */
uint64_t btr_read_autoinc_with_fallback(const dict_table_t *table,
unsigned col_no, ulong mysql_version,
uint64_t max)
MY_ATTRIBUTE((nonnull, warn_unused_result));
/** Write the next available AUTO_INCREMENT value to PAGE_ROOT_AUTO_INC.
@param[in,out] index clustered index

View file

@ -2366,6 +2366,9 @@ public:
/** @return number of unique columns in FTS_DOC_ID index */
unsigned fts_n_uniq() const { return versioned() ? 2 : 1; }
/** @return the index for that starts with a specific column */
dict_index_t *get_index(const dict_col_t &col) const;
};
inline void dict_index_t::set_modified(mtr_t& mtr) const

View file

@ -48,6 +48,8 @@ Created 2012-02-08 by Sunny Bains.
#include "snappy-c.h"
#endif
#include "log.h"
#include "table.h"
#include "ha_innodb.h"
#include "scope.h"
@ -4333,6 +4335,37 @@ fil_tablespace_iterate(
return(err);
}
static void row_import_autoinc(dict_table_t *table, row_prebuilt_t *prebuilt,
uint64_t autoinc)
{
if (!table->persistent_autoinc)
{
ut_ad(!autoinc);
return;
}
if (autoinc)
{
btr_write_autoinc(dict_table_get_first_index(table), autoinc - 1);
autoinc_set:
table->autoinc= autoinc;
sql_print_information("InnoDB: %`.*s.%`s autoinc value set to " UINT64PF,
int(table->name.dblen()), table->name.m_name,
table->name.basename(), autoinc);
}
else if (TABLE *t= prebuilt->m_mysql_table)
{
if (const Field *ai= t->found_next_number_field)
{
autoinc= 1 +
btr_read_autoinc_with_fallback(table, innodb_col_no(ai),
t->s->mysql_version,
innobase_get_int_col_max_value(ai));
goto autoinc_set;
}
}
}
/*****************************************************************//**
Imports a tablespace. The space id in the .ibd file must match the space id
of the table in the data dictionary.
@ -4719,16 +4752,7 @@ row_import_for_mysql(
/* Set autoinc value read from .cfg file, if one was specified.
Otherwise, read the PAGE_ROOT_AUTO_INC and set it to table autoinc. */
if (autoinc) {
ib::info() << table->name << " autoinc value set to "
<< autoinc;
table->autoinc = autoinc--;
btr_write_autoinc(dict_table_get_first_index(table), autoinc);
} else if (table->persistent_autoinc) {
autoinc = btr_read_autoinc(dict_table_get_first_index(table));
table->autoinc = ++autoinc;
}
row_import_autoinc(table, prebuilt, autoinc);
return(row_import_cleanup(prebuilt, trx, err));
}

View file

@ -45,6 +45,7 @@ C_MODE_END
#include "key.h"
#include "log.h"
#include "sql_parse.h"
#include "mysql/service_print_check_msg.h"
/*
Note that in future versions, only *transactional* Maria tables can
@ -427,10 +428,8 @@ static void _ma_check_print_msg(HA_CHECK *param, const char *msg_type,
const char *fmt, va_list args)
{
THD *thd= (THD *) param->thd;
Protocol *protocol= thd->protocol;
size_t length, msg_length;
size_t msg_length __attribute__((unused));
char msgbuf[MYSQL_ERRMSG_SIZE];
char name[NAME_LEN * 2 + 2];
if (param->testflag & T_SUPPRESS_ERR_HANDLING)
return;
@ -459,27 +458,10 @@ static void _ma_check_print_msg(HA_CHECK *param, const char *msg_type,
_ma_check_print(param, msg_type, msgbuf);
return;
}
length= (uint) (strxmov(name, param->db_name, ".", param->table_name,
NullS) - name);
/*
TODO: switch from protocol to push_warning here. The main reason we didn't
it yet is parallel repair, which threads have no THD object accessible via
current_thd.
Also we likely need to lock mutex here (in both cases with protocol and
push_warning).
*/
protocol->prepare_for_resend();
protocol->store(name, (uint)length, system_charset_info);
protocol->store(param->op_name, system_charset_info);
protocol->store(msg_type, system_charset_info);
protocol->store(msgbuf, (uint)msg_length, system_charset_info);
if (protocol->write())
sql_print_error("Failed on my_net_write, writing to stderr instead: %s.%s: %s\n",
param->db_name, param->table_name, msgbuf);
else if (thd->variables.log_warnings > 2)
print_check_msg(thd, param->db_name, param->table_name,
param->op_name, msg_type, msgbuf, 0);
if (thd->variables.log_warnings > 2)
_ma_check_print(param, msg_type, msgbuf);
return;
}