Commit de469a2f authored by Marko Mäkelä's avatar Marko Mäkelä

MDEV-14637: Fix hang due to persistent statistics

Similar to the tables SYS_FOREIGN and SYS_FOREIGN_COLS,
the tables mysql.innodb_table_stats and mysql.innodb_index_stats
are updated by the InnoDB internal SQL parser, which fails to
enforce the size limits of the data. Due to this, it is possible
for InnoDB to hang when there are persistent statistics defined on
partitioned tables where the total length of table name,
partition name and subpartition name exceeds the incorrectly
defined limit VARCHAR(64). That column should have been defined
as VARCHAR(199).

btr_node_ptr_max_size(): Interpret the VARCHAR(64) as VARCHAR(199),
to prevent a hang in the case that the upgrade script has not been
run.

dict_table_schema_check(): Ignore difference in the length of the
table_name column.

ha_innobase::max_supported_key_length(): For innodb_page_size=4k,
return a larger value so that the table mysql.innodb_index_stats
can be created. This could allow "impossible" tables to be created,
such that it is not possible to insert anything into a secondary
index when both the secondary key and the primary key are long,
but this is the easiest and most consistent way. The Oracle fix
would only ignore the maximum length violation for the two
statistics tables.

os_file_get_status_posix(), os_file_get_status_win32(): Handle
ENAMETOOLONG as well.

This patch is based on the following change in MySQL 5.7.23.
Not all changes were applied, and our variant allows persistent
statistics to work without hangs even if the table definitions
were not upgraded.

From fdbdce701ab8145ae234c9d401109dff4e4106cb Mon Sep 17 00:00:00 2001
From: Aditya A <aditya.a@oracle.com>
Date: Thu, 17 May 2018 16:11:43 +0530
Subject: [PATCH] Bug #26390736 THE FIELD TABLE_NAME (VARCHAR(64)) FROM
           MYSQL.INNODB_TABLE_STATS CAN OVERFLOW.

    In mysql.innodb_index_stats and mysql.innodb_table_stats
    tables the table name column didn't take into consideration
    partition names which can be more than varchar(64).
parent 20460897
......@@ -124,13 +124,13 @@ def mysql innodb_index_stats sample_size 7 NULL YES bigint NULL NULL 20 0 NULL N
def mysql innodb_index_stats stat_description 8 NULL NO varchar 1024 3072 NULL NULL NULL utf8 utf8_bin varchar(1024) select,insert,update,references NEVER NULL
def mysql innodb_index_stats stat_name 5 NULL NO varchar 64 192 NULL NULL NULL utf8 utf8_bin varchar(64) PRI select,insert,update,references NEVER NULL
def mysql innodb_index_stats stat_value 6 NULL NO bigint NULL NULL 20 0 NULL NULL NULL bigint(20) unsigned select,insert,update,references NEVER NULL
def mysql innodb_index_stats table_name 2 NULL NO varchar 64 192 NULL NULL NULL utf8 utf8_bin varchar(64) PRI select,insert,update,references NEVER NULL
def mysql innodb_index_stats table_name 2 NULL NO varchar 199 597 NULL NULL NULL utf8 utf8_bin varchar(199) PRI select,insert,update,references NEVER NULL
def mysql innodb_table_stats clustered_index_size 5 NULL NO bigint NULL NULL 20 0 NULL NULL NULL bigint(20) unsigned select,insert,update,references NEVER NULL
def mysql innodb_table_stats database_name 1 NULL NO varchar 64 192 NULL NULL NULL utf8 utf8_bin varchar(64) PRI select,insert,update,references NEVER NULL
def mysql innodb_table_stats last_update 3 current_timestamp() NO timestamp NULL NULL NULL NULL 0 NULL NULL timestamp on update current_timestamp() select,insert,update,references NEVER NULL
def mysql innodb_table_stats n_rows 4 NULL NO bigint NULL NULL 20 0 NULL NULL NULL bigint(20) unsigned select,insert,update,references NEVER NULL
def mysql innodb_table_stats sum_of_other_index_sizes 6 NULL NO bigint NULL NULL 20 0 NULL NULL NULL bigint(20) unsigned select,insert,update,references NEVER NULL
def mysql innodb_table_stats table_name 2 NULL NO varchar 64 192 NULL NULL NULL utf8 utf8_bin varchar(64) PRI select,insert,update,references NEVER NULL
def mysql innodb_table_stats table_name 2 NULL NO varchar 199 597 NULL NULL NULL utf8 utf8_bin varchar(199) PRI select,insert,update,references NEVER NULL
def mysql plugin dl 2 '' NO varchar 128 384 NULL NULL NULL utf8 utf8_general_ci varchar(128) select,insert,update,references NEVER NULL
def mysql plugin name 1 '' NO varchar 64 192 NULL NULL NULL utf8 utf8_general_ci varchar(64) PRI select,insert,update,references NEVER NULL
def mysql proc body 11 NULL NO longblob 4294967295 4294967295 NULL NULL NULL NULL NULL longblob select,insert,update,references NEVER NULL
......@@ -453,7 +453,7 @@ NULL mysql help_topic help_category_id smallint NULL NULL NULL NULL smallint(5)
NULL mysql index_stats prefix_arity int NULL NULL NULL NULL int(11) unsigned
NULL mysql index_stats avg_frequency decimal NULL NULL NULL NULL decimal(12,4)
3.0000 mysql innodb_index_stats database_name varchar 64 192 utf8 utf8_bin varchar(64)
3.0000 mysql innodb_index_stats table_name varchar 64 192 utf8 utf8_bin varchar(64)
3.0000 mysql innodb_index_stats table_name varchar 199 597 utf8 utf8_bin varchar(199)
3.0000 mysql innodb_index_stats index_name varchar 64 192 utf8 utf8_bin varchar(64)
NULL mysql innodb_index_stats last_update timestamp NULL NULL NULL NULL timestamp
3.0000 mysql innodb_index_stats stat_name varchar 64 192 utf8 utf8_bin varchar(64)
......@@ -461,7 +461,7 @@ NULL mysql innodb_index_stats stat_value bigint NULL NULL NULL NULL bigint(20) u
NULL mysql innodb_index_stats sample_size bigint NULL NULL NULL NULL bigint(20) unsigned
3.0000 mysql innodb_index_stats stat_description varchar 1024 3072 utf8 utf8_bin varchar(1024)
3.0000 mysql innodb_table_stats database_name varchar 64 192 utf8 utf8_bin varchar(64)
3.0000 mysql innodb_table_stats table_name varchar 64 192 utf8 utf8_bin varchar(64)
3.0000 mysql innodb_table_stats table_name varchar 199 597 utf8 utf8_bin varchar(199)
NULL mysql innodb_table_stats last_update timestamp NULL NULL NULL NULL timestamp
NULL mysql innodb_table_stats n_rows bigint NULL NULL NULL NULL bigint(20) unsigned
NULL mysql innodb_table_stats clustered_index_size bigint NULL NULL NULL NULL bigint(20) unsigned
......
......@@ -1284,7 +1284,7 @@ CREATE SPATIAL INDEX idx2 ON t1(c1);
DROP TABLE t1;
CREATE TABLE mysql.innodb_table_stats (
database_name varchar(64) COLLATE utf8_bin NOT NULL,
table_name varchar(64) COLLATE utf8_bin NOT NULL,
table_name varchar(199) COLLATE utf8_bin NOT NULL,
last_update timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
n_rows bigint(20) unsigned NOT NULL,
clustered_index_size bigint(20) unsigned NOT NULL,
......
......@@ -1166,7 +1166,7 @@ CREATE SPATIAL INDEX idx2 ON t1(c1);
DROP TABLE t1;
CREATE TABLE mysql.innodb_table_stats (
database_name varchar(64) COLLATE utf8_bin NOT NULL,
table_name varchar(64) COLLATE utf8_bin NOT NULL,
table_name varchar(199) COLLATE utf8_bin NOT NULL,
last_update timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
n_rows bigint(20) unsigned NOT NULL,
clustered_index_size bigint(20) unsigned NOT NULL,
......
-- Copyright (c) 2007, 2013, Oracle and/or its affiliates.
-- Copyright (c) 2007, 2018, Oracle and/or its affiliates.
-- Copyright (c) 2008, 2014, Monty Program Ab & SkySQL Ab
--
-- This program is free software; you can redistribute it and/or modify
......@@ -105,7 +105,7 @@ CREATE TABLE IF NOT EXISTS event ( db char(64) CHARACTER SET utf8 COLLATE utf8_b
SET @create_innodb_table_stats="CREATE TABLE IF NOT EXISTS innodb_table_stats (
database_name VARCHAR(64) NOT NULL,
table_name VARCHAR(64) NOT NULL,
table_name VARCHAR(199) NOT NULL,
last_update TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
n_rows BIGINT UNSIGNED NOT NULL,
clustered_index_size BIGINT UNSIGNED NOT NULL,
......@@ -115,7 +115,7 @@ SET @create_innodb_table_stats="CREATE TABLE IF NOT EXISTS innodb_table_stats (
SET @create_innodb_index_stats="CREATE TABLE IF NOT EXISTS innodb_index_stats (
database_name VARCHAR(64) NOT NULL,
table_name VARCHAR(64) NOT NULL,
table_name VARCHAR(199) NOT NULL,
index_name VARCHAR(64) NOT NULL,
last_update TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
/* there are at least:
......
......@@ -697,13 +697,13 @@ set @str=replace(@str, "innodb_index_stats", "innodb_table_stats");
prepare stmt from @str;
execute stmt;
# update timestamp fields in the innodb stat tables
set @str="alter table mysql.innodb_index_stats modify last_update timestamp not null default current_timestamp on update current_timestamp";
# update table_name and timestamp fields in the innodb stat tables
set @str="alter table mysql.innodb_index_stats modify last_update timestamp not null default current_timestamp on update current_timestamp, modify table_name varchar(199)";
set @str=if(@have_innodb <> 0, @str, "set @dummy = 0");
prepare stmt from @str;
execute stmt;
set @str="alter table mysql.innodb_table_stats modify last_update timestamp not null default current_timestamp on update current_timestamp";
set @str="alter table mysql.innodb_table_stats modify last_update timestamp not null default current_timestamp on update current_timestamp, modify table_name varchar(199)";
set @str=if(@have_innodb <> 0, @str, "set @dummy = 0");
prepare stmt from @str;
execute stmt;
......
......@@ -65,6 +65,8 @@ Created 10/16/1994 Heikki Tuuri
#include "lock0lock.h"
#include "zlib.h"
#include "srv0start.h"
#include "mysql_com.h"
#include "dict0stats.h"
/** Buffered B-tree operation types, introduced as part of delete buffering. */
enum btr_op_t {
......@@ -771,6 +773,22 @@ static ulint btr_node_ptr_max_size(const dict_index_t* index)
rec_max_size += (srv_page_size == UNIV_PAGE_SIZE_MAX)
? REDUNDANT_REC_MAX_DATA_SIZE
: page_get_free_space_of_empty(FALSE) / 2;
} else if (field_max_size == NAME_LEN && i == 1
&& (!strcmp(index->table->name.m_name,
TABLE_STATS_NAME)
|| !strcmp(index->table->name.m_name,
INDEX_STATS_NAME))) {
ut_ad(!strcmp(field->name, "table_name"));
/* Interpret "table_name" as VARCHAR(199) even
if it was incorrectly defined as VARCHAR(64).
While the caller of ha_innobase enforces the
maximum length on any data written, the InnoDB
internal SQL parser will happily write as much
data as is provided. The purpose of this hack
is to avoid InnoDB hangs after persistent
statistics on partitioned tables are
deleted. */
field_max_size = 199 * SYSTEM_CHARSET_MBMAXLEN;
}
field_ext_max_size = field_max_size < 256 ? 1 : 2;
......
......@@ -6490,8 +6490,17 @@ dict_table_schema_check(
compare column types and flags */
/* check length for exact match */
if (req_schema->columns[i].len != table->cols[j].len) {
if (req_schema->columns[i].len == table->cols[j].len) {
} else if (!strcmp(req_schema->table_name, TABLE_STATS_NAME)
|| !strcmp(req_schema->table_name,
INDEX_STATS_NAME)) {
ut_ad(table->cols[j].len < req_schema->columns[i].len);
ib::warn() << "Table " << req_schema->table_name
<< " has length mismatch in the"
<< " column name "
<< req_schema->columns[i].name
<< ". Please run mysql_upgrade";
} else {
CREATE_TYPES_NAMES();
snprintf(errstr, errstr_sz,
......
/*****************************************************************************
Copyright (c) 2009, 2017, Oracle and/or its affiliates. All Rights Reserved.
Copyright (c) 2009, 2018, Oracle and/or its affiliates. All Rights Reserved.
Copyright (c) 2015, 2018, MariaDB Corporation.
This program is free software; you can redistribute it and/or modify it under
......@@ -108,9 +108,7 @@ where n=1..n_uniq.
@} */
/* names of the tables from the persistent statistics storage */
#define TABLE_STATS_NAME "mysql/innodb_table_stats"
#define TABLE_STATS_NAME_PRINT "mysql.innodb_table_stats"
#define INDEX_STATS_NAME "mysql/innodb_index_stats"
#define INDEX_STATS_NAME_PRINT "mysql.innodb_index_stats"
#ifdef UNIV_STATS_DEBUG
......@@ -181,7 +179,7 @@ dict_stats_persistent_storage_check(
DATA_NOT_NULL, 192},
{"table_name", DATA_VARMYSQL,
DATA_NOT_NULL, 192},
DATA_NOT_NULL, 597},
{"last_update", DATA_FIXBINARY,
DATA_NOT_NULL, 4},
......@@ -209,7 +207,7 @@ dict_stats_persistent_storage_check(
DATA_NOT_NULL, 192},
{"table_name", DATA_VARMYSQL,
DATA_NOT_NULL, 192},
DATA_NOT_NULL, 597},
{"index_name", DATA_VARMYSQL,
DATA_NOT_NULL, 192},
......
......@@ -5459,15 +5459,16 @@ ha_innobase::max_supported_key_length() const
switch (UNIV_PAGE_SIZE) {
case 4096:
return(768);
/* Hack: allow mysql.innodb_index_stats to be created. */
/* FIXME: rewrite this API, and in sql_table.cc consider
that in index-organized tables (such as InnoDB), secondary
index records will be padded with the PRIMARY KEY, instead
of some short ROWID or record heap address. */
return(1173);
case 8192:
return(1536);
default:
#ifdef WITH_WSREP
return(3500);
#else
return(3500);
#endif
}
}
......
/*****************************************************************************
Copyright (c) 2009, 2016, Oracle and/or its affiliates. All Rights Reserved.
Copyright (c) 2009, 2018, Oracle and/or its affiliates. All Rights Reserved.
Copyright (c) 2017, 2018, MariaDB Corporation.
This program is free software; you can redistribute it and/or modify it under
......@@ -32,6 +32,9 @@ Created Jan 06, 2010 Vasil Dimov
#include "dict0types.h"
#include "trx0types.h"
#define TABLE_STATS_NAME "mysql/innodb_table_stats"
#define INDEX_STATS_NAME "mysql/innodb_index_stats"
enum dict_stats_upd_option_t {
DICT_STATS_RECALC_PERSISTENT,/* (re) calculate the
statistics using a precise and slow
......
......@@ -3307,7 +3307,8 @@ os_file_get_status_posix(
{
int ret = stat(path, statinfo);
if (ret && (errno == ENOENT || errno == ENOTDIR)) {
if (ret && (errno == ENOENT || errno == ENOTDIR
|| errno == ENAMETOOLONG)) {
/* file does not exist */
return(DB_NOT_FOUND);
......@@ -4697,7 +4698,8 @@ os_file_get_status_win32(
{
int ret = _stat64(path, statinfo);
if (ret && (errno == ENOENT || errno == ENOTDIR)) {
if (ret && (errno == ENOENT || errno == ENOTDIR
|| errno == ENAMETOOLONG)) {
/* file does not exist */
return(DB_NOT_FOUND);
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment