Skip to content

Commit 177d8b0

Browse files
author
Jimmy Yang
committed
Fix bug #11830883, SUPPORT "CORRUPTED" BIT FOR INNODB TABLES AND INDEXES.
Also addressed issues in bug #11745133, where we could mark a table corrupted instead of crashing the server when found a corrupted buffer/page if the table created with innodb_file_per_table on.
1 parent 049225e commit 177d8b0

30 files changed

Lines changed: 915 additions & 55 deletions

include/my_base.h

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -446,8 +446,9 @@ enum ha_base_keytype {
446446
#define HA_ERR_FILE_TOO_SHORT 175 /* File too short */
447447
#define HA_ERR_WRONG_CRC 176 /* Wrong CRC on page */
448448
#define HA_ERR_TOO_MANY_CONCURRENT_TRXS 177 /*Too many active concurrent transactions */
449-
#define HA_ERR_INDEX_COL_TOO_LONG 178 /* Index column length exceeds limit */
450-
#define HA_ERR_LAST 178 /* Copy of last error nr */
449+
#define HA_ERR_INDEX_COL_TOO_LONG 178 /* Index column length exceeds limit */
450+
#define HA_ERR_INDEX_CORRUPT 179 /* Index corrupted */
451+
#define HA_ERR_LAST 179 /* Copy of last error nr */
451452

452453
/* Number of different errors */
453454
#define HA_ERR_ERRORS (HA_ERR_LAST - HA_ERR_FIRST + 1)
Lines changed: 81 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,81 @@
1+
set names utf8;
2+
CREATE TABLE corrupt_bit_test_ā(
3+
a INT AUTO_INCREMENT PRIMARY KEY,
4+
b CHAR(100),
5+
c INT,
6+
z INT,
7+
INDEX(b))
8+
ENGINE=InnoDB;
9+
INSERT INTO corrupt_bit_test_ā VALUES(0,'x',1, 1);
10+
CREATE UNIQUE INDEX idxā ON corrupt_bit_test_ā(c, b);
11+
CREATE UNIQUE INDEX idxē ON corrupt_bit_test_ā(z, b);
12+
SELECT * FROM corrupt_bit_test_ā;
13+
a b c z
14+
1 x 1 1
15+
select @@unique_checks;
16+
@@unique_checks
17+
0
18+
select @@innodb_change_buffering_debug;
19+
@@innodb_change_buffering_debug
20+
1
21+
INSERT INTO corrupt_bit_test_ā SELECT 0,b,c+1,z+1 FROM corrupt_bit_test_ā;
22+
INSERT INTO corrupt_bit_test_ā SELECT 0,b,c+10,z+10 FROM corrupt_bit_test_ā;
23+
INSERT INTO corrupt_bit_test_ā SELECT 0,b,c+20,z+20 FROM corrupt_bit_test_ā;
24+
INSERT INTO corrupt_bit_test_ā SELECT 0,b,c+50,z+50 FROM corrupt_bit_test_ā;
25+
INSERT INTO corrupt_bit_test_ā SELECT 0,b,c+100,z+100 FROM corrupt_bit_test_ā;
26+
INSERT INTO corrupt_bit_test_ā SELECT 0,b,c+200,z+200 FROM corrupt_bit_test_ā;
27+
INSERT INTO corrupt_bit_test_ā SELECT 0,b,c+400,z+400 FROM corrupt_bit_test_ā;
28+
INSERT INTO corrupt_bit_test_ā SELECT 0,b,c+800,z+800 FROM corrupt_bit_test_ā;
29+
INSERT INTO corrupt_bit_test_ā SELECT 0,b,c+1600,z+1600 FROM corrupt_bit_test_ā;
30+
INSERT INTO corrupt_bit_test_ā SELECT 0,b,c+4000,z+4000 FROM corrupt_bit_test_ā;
31+
select count(*) from corrupt_bit_test_ā;
32+
count(*)
33+
1024
34+
CREATE INDEX idx3 ON corrupt_bit_test_ā(b, c);
35+
INSERT INTO corrupt_bit_test_ā VALUES(13000,'x',1,1);
36+
CREATE INDEX idx4 ON corrupt_bit_test_ā(b, z);
37+
check table corrupt_bit_test_ā;
38+
Table Op Msg_type Msg_text
39+
test.corrupt_bit_test_ā check Warning InnoDB: The B-tree of index "idxā" is corrupted.
40+
test.corrupt_bit_test_ā check Warning InnoDB: The B-tree of index "idxē" is corrupted.
41+
test.corrupt_bit_test_ā check error Corrupt
42+
select c from corrupt_bit_test_ā;
43+
ERROR HY000: Incorrect key file for table 'corrupt_bit_test_ā'; try to repair it
44+
select z from corrupt_bit_test_ā;
45+
ERROR HY000: Incorrect key file for table 'corrupt_bit_test_ā'; try to repair it
46+
show warnings;
47+
Level Code Message
48+
Warning 179 InnoDB: Index "idxē" for table "test/corrupt_bit_test_@1s" is marked as corrupted
49+
Error 1034 Incorrect key file for table 'corrupt_bit_test_ā'; try to repair it
50+
insert into corrupt_bit_test_ā values (10001, "a", 20001, 20001);
51+
select * from corrupt_bit_test_ā use index(primary) where a = 10001;
52+
a b c z
53+
10001 a 20001 20001
54+
begin;
55+
insert into corrupt_bit_test_ā values (10002, "a", 20002, 20002);
56+
delete from corrupt_bit_test_ā where a = 10001;
57+
insert into corrupt_bit_test_ā values (10001, "a", 20001, 20001);
58+
rollback;
59+
drop index idxā on corrupt_bit_test_ā;
60+
check table corrupt_bit_test_ā;
61+
Table Op Msg_type Msg_text
62+
test.corrupt_bit_test_ā check Warning InnoDB: Index "idxē" is marked as corrupted
63+
test.corrupt_bit_test_ā check error Corrupt
64+
set names utf8;
65+
select z from corrupt_bit_test_ā;
66+
ERROR HY000: Incorrect key file for table 'corrupt_bit_test_ā'; try to repair it
67+
drop index idxē on corrupt_bit_test_ā;
68+
select z from corrupt_bit_test_ā limit 10;
69+
z
70+
20001
71+
1
72+
1
73+
2
74+
11
75+
12
76+
21
77+
22
78+
31
79+
32
80+
drop table corrupt_bit_test_ā;
81+
SET GLOBAL innodb_change_buffering_debug = 0;
Lines changed: 135 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,135 @@
1+
#
2+
# Test for persistent corrupt bit for corrupted index and table
3+
#
4+
-- source include/have_innodb.inc
5+
6+
# This test needs debug server
7+
--source include/have_debug.inc
8+
9+
-- disable_query_log
10+
# This test setup is extracted from bug56680.test:
11+
# The flag innodb_change_buffering_debug is only available in debug builds.
12+
# It instructs InnoDB to try to evict pages from the buffer pool when
13+
# change buffering is possible, so that the change buffer will be used
14+
# whenever possible.
15+
-- error 0,ER_UNKNOWN_SYSTEM_VARIABLE
16+
SET @innodb_change_buffering_debug_orig = @@innodb_change_buffering_debug;
17+
-- error 0,ER_UNKNOWN_SYSTEM_VARIABLE
18+
SET GLOBAL innodb_change_buffering_debug = 1;
19+
20+
# Turn off Unique Check to create corrupted index with dup key
21+
SET UNIQUE_CHECKS=0;
22+
23+
-- enable_query_log
24+
25+
set names utf8;
26+
27+
CREATE TABLE corrupt_bit_test_ā(
28+
a INT AUTO_INCREMENT PRIMARY KEY,
29+
b CHAR(100),
30+
c INT,
31+
z INT,
32+
INDEX(b))
33+
ENGINE=InnoDB;
34+
35+
INSERT INTO corrupt_bit_test_ā VALUES(0,'x',1, 1);
36+
37+
# This is the first unique index we intend to corrupt
38+
CREATE UNIQUE INDEX idxā ON corrupt_bit_test_ā(c, b);
39+
40+
# This is the second unique index we intend to corrupt
41+
CREATE UNIQUE INDEX idxē ON corrupt_bit_test_ā(z, b);
42+
43+
SELECT * FROM corrupt_bit_test_ā;
44+
45+
select @@unique_checks;
46+
select @@innodb_change_buffering_debug;
47+
48+
# Create enough rows for the table, so that the insert buffer will be
49+
# used for modifying the secondary index page. There must be multiple
50+
# index pages, because changes to the root page are never buffered.
51+
52+
INSERT INTO corrupt_bit_test_ā SELECT 0,b,c+1,z+1 FROM corrupt_bit_test_ā;
53+
INSERT INTO corrupt_bit_test_ā SELECT 0,b,c+10,z+10 FROM corrupt_bit_test_ā;
54+
INSERT INTO corrupt_bit_test_ā SELECT 0,b,c+20,z+20 FROM corrupt_bit_test_ā;
55+
INSERT INTO corrupt_bit_test_ā SELECT 0,b,c+50,z+50 FROM corrupt_bit_test_ā;
56+
INSERT INTO corrupt_bit_test_ā SELECT 0,b,c+100,z+100 FROM corrupt_bit_test_ā;
57+
INSERT INTO corrupt_bit_test_ā SELECT 0,b,c+200,z+200 FROM corrupt_bit_test_ā;
58+
INSERT INTO corrupt_bit_test_ā SELECT 0,b,c+400,z+400 FROM corrupt_bit_test_ā;
59+
INSERT INTO corrupt_bit_test_ā SELECT 0,b,c+800,z+800 FROM corrupt_bit_test_ā;
60+
INSERT INTO corrupt_bit_test_ā SELECT 0,b,c+1600,z+1600 FROM corrupt_bit_test_ā;
61+
INSERT INTO corrupt_bit_test_ā SELECT 0,b,c+4000,z+4000 FROM corrupt_bit_test_ā;
62+
63+
select count(*) from corrupt_bit_test_ā;
64+
65+
CREATE INDEX idx3 ON corrupt_bit_test_ā(b, c);
66+
67+
# Create a dup key error on index "idxē" and "idxā" by inserting a dup value
68+
INSERT INTO corrupt_bit_test_ā VALUES(13000,'x',1,1);
69+
70+
# creating an index should succeed even if other secondary indexes are corrupted
71+
CREATE INDEX idx4 ON corrupt_bit_test_ā(b, z);
72+
73+
# Check table will find the unique indexes corrupted
74+
# with dup key
75+
check table corrupt_bit_test_ā;
76+
77+
# This selection intend to use the corrupted index. Expect to fail
78+
-- error ER_NOT_KEYFILE
79+
select c from corrupt_bit_test_ā;
80+
81+
-- error ER_NOT_KEYFILE
82+
select z from corrupt_bit_test_ā;
83+
84+
show warnings;
85+
86+
# Since corrupted index is a secondary index, we only disable such
87+
# index and allow other DML to proceed
88+
insert into corrupt_bit_test_ā values (10001, "a", 20001, 20001);
89+
90+
# This does not use the corrupted index, expect to succeed
91+
select * from corrupt_bit_test_ā use index(primary) where a = 10001;
92+
93+
# Some more DMLs
94+
begin;
95+
insert into corrupt_bit_test_ā values (10002, "a", 20002, 20002);
96+
delete from corrupt_bit_test_ā where a = 10001;
97+
insert into corrupt_bit_test_ā values (10001, "a", 20001, 20001);
98+
rollback;
99+
100+
# Drop one corrupted index before reboot
101+
drop index idxā on corrupt_bit_test_ā;
102+
103+
check table corrupt_bit_test_ā;
104+
105+
# Shut down the server
106+
-- exec echo "wait" > $MYSQLTEST_VARDIR/tmp/mysqld.1.expect
107+
-- shutdown_server 20
108+
-- source include/wait_until_disconnected.inc
109+
110+
# Restart the server
111+
-- disable_query_log
112+
--exec echo "restart" > $MYSQLTEST_VARDIR/tmp/mysqld.1.expect
113+
--enable_reconnect
114+
--source include/wait_until_connected_again.inc
115+
--disable_reconnect
116+
-- enable_query_log
117+
118+
set names utf8;
119+
120+
# The index is marked as suspect in Sys_indexes too, so after server
121+
# reboot, the attempt to use the index will fail too.
122+
-- error ER_NOT_KEYFILE
123+
select z from corrupt_bit_test_ā;
124+
125+
# Drop the corrupted index
126+
drop index idxē on corrupt_bit_test_ā;
127+
128+
# Now select back to normal
129+
select z from corrupt_bit_test_ā limit 10;
130+
131+
# Drop table
132+
drop table corrupt_bit_test_ā;
133+
134+
-- error 0, ER_UNKNOWN_SYSTEM_VARIABLE
135+
SET GLOBAL innodb_change_buffering_debug = 0;

mysql-test/suite/sys_vars/r/all_vars.result

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,9 @@ There should be *no* long test name listed below:
1111
select variable_name as `There should be *no* variables listed below:` from t2
1212
left join t1 on variable_name=test_name where test_name is null;
1313
There should be *no* variables listed below:
14+
INNODB_FORCE_LOAD_CORRUPTED
1415
INNODB_LARGE_PREFIX
16+
INNODB_FORCE_LOAD_CORRUPTED
1517
INNODB_LARGE_PREFIX
1618
drop table t1;
1719
drop table t2;

mysys/my_handler_errors.h

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -81,7 +81,8 @@ static const char *handler_error_messages[]=
8181
"File to short; Expected more data in file",
8282
"Read page with wrong checksum",
8383
"Too many active concurrent transactions",
84-
"Index column length exceeds limit"
84+
"Index column length exceeds limit",
85+
"Index corrupted"
8586
};
8687

8788
extern void my_handler_error_register(void);

sql/handler.cc

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -358,6 +358,7 @@ int ha_init_errors(void)
358358
SETMSG(HA_ERR_AUTOINC_ERANGE, ER_DEFAULT(ER_WARN_DATA_OUT_OF_RANGE));
359359
SETMSG(HA_ERR_TOO_MANY_CONCURRENT_TRXS, ER_DEFAULT(ER_TOO_MANY_CONCURRENT_TRXS));
360360
SETMSG(HA_ERR_INDEX_COL_TOO_LONG, ER_DEFAULT(ER_INDEX_COLUMN_TOO_LONG));
361+
SETMSG(HA_ERR_INDEX_CORRUPT, ER_DEFAULT(ER_INDEX_CORRUPT));
361362

362363
/* Register the error messages for use with my_error(). */
363364
return my_error_register(get_handler_errmsgs, HA_ERR_FIRST, HA_ERR_LAST);
@@ -2865,6 +2866,9 @@ void handler::print_error(int error, myf errflag)
28652866
case HA_ERR_INDEX_COL_TOO_LONG:
28662867
textno= ER_INDEX_COLUMN_TOO_LONG;
28672868
break;
2869+
case HA_ERR_INDEX_CORRUPT:
2870+
textno= ER_INDEX_CORRUPT;
2871+
break;
28682872
default:
28692873
{
28702874
/* The error was "unknown" to this function.

sql/share/errmsg-utf8.txt

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6415,3 +6415,5 @@ ER_ERROR_IN_TRIGGER_BODY
64156415
ER_ERROR_IN_UNKNOWN_TRIGGER_BODY
64166416
eng "Unknown trigger has an error in its body: '%-.256s'"
64176417

6418+
ER_INDEX_CORRUPT
6419+
eng "Index %s is corrupted"

storage/innobase/buf/buf0buf.c

Lines changed: 62 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -3473,6 +3473,55 @@ buf_page_create(
34733473
return(block);
34743474
}
34753475

3476+
/********************************************************************//**
3477+
Mark a table with the specified space pointed by bpage->space corrupted.
3478+
Also remove the bpage from LRU list.
3479+
@return TRUE if successful */
3480+
static
3481+
ibool
3482+
buf_mark_space_corrupt(
3483+
/*===================*/
3484+
buf_page_t* bpage) /*!< in: pointer to the block in question */
3485+
{
3486+
buf_pool_t* buf_pool = buf_pool_from_bpage(bpage);
3487+
const ibool uncompressed = (buf_page_get_state(bpage)
3488+
== BUF_BLOCK_FILE_PAGE);
3489+
ulint space = bpage->space;
3490+
ulint offset = bpage->offset;
3491+
ibool ret = TRUE;
3492+
3493+
/* First unfix and release lock on the bpage */
3494+
buf_pool_mutex_enter(buf_pool);
3495+
mutex_enter(buf_page_get_mutex(bpage));
3496+
ut_ad(buf_page_get_io_fix(bpage) == BUF_IO_READ);
3497+
ut_ad(bpage->buf_fix_count == 0);
3498+
3499+
/* Set BUF_IO_NONE before we remove the block from LRU list */
3500+
buf_page_set_io_fix(bpage, BUF_IO_NONE);
3501+
3502+
if (uncompressed) {
3503+
rw_lock_x_unlock_gen(
3504+
&((buf_block_t*) bpage)->lock,
3505+
BUF_IO_READ);
3506+
}
3507+
3508+
/* Find the table with specified space id, and mark it corrupted */
3509+
if (dict_set_corrupted_by_space(space)) {
3510+
ut_ad(bpage->space == space && bpage->offset == offset);
3511+
buf_LRU_free_one_page(bpage);
3512+
} else {
3513+
ret = FALSE;
3514+
}
3515+
3516+
ut_ad(buf_pool->n_pend_reads > 0);
3517+
buf_pool->n_pend_reads--;
3518+
3519+
mutex_exit(buf_page_get_mutex(bpage));
3520+
buf_pool_mutex_exit(buf_pool);
3521+
3522+
return(ret);
3523+
}
3524+
34763525
/********************************************************************//**
34773526
Completes an asynchronous read or write request of a file page to or from
34783527
the buffer pool. */
@@ -3598,10 +3647,19 @@ buf_page_io_complete(
35983647
"InnoDB: about forcing recovery.\n", stderr);
35993648

36003649
if (srv_force_recovery < SRV_FORCE_IGNORE_CORRUPT) {
3601-
fputs("InnoDB: Ending processing because of"
3602-
" a corrupt database page.\n",
3603-
stderr);
3604-
exit(1);
3650+
/* If page space id is larger than TRX_SYS_SPACE
3651+
(0), we will attempt to mark the corresponding
3652+
table as corrupted instead of crashing server */
3653+
if (bpage->space > TRX_SYS_SPACE
3654+
&& buf_mark_space_corrupt(bpage)) {
3655+
return;
3656+
} else {
3657+
fputs("InnoDB: Ending processing"
3658+
" because of"
3659+
" a corrupt database page.\n",
3660+
stderr);
3661+
ut_error;
3662+
}
36053663
}
36063664
}
36073665

storage/innobase/buf/buf0lru.c

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1885,6 +1885,22 @@ buf_LRU_block_free_hashed_page(
18851885
buf_LRU_block_free_non_file_page(block);
18861886
}
18871887

1888+
/******************************************************************//**
1889+
Remove one page from LRU list and put it to free list */
1890+
UNIV_INTERN
1891+
void
1892+
buf_LRU_free_one_page(
1893+
/*==================*/
1894+
buf_page_t* bpage) /*!< in/out: block, must contain a file page and
1895+
be in a state where it can be freed; there
1896+
may or may not be a hash index to the page */
1897+
{
1898+
if (buf_LRU_block_remove_hashed_page(bpage, TRUE)
1899+
!= BUF_BLOCK_ZIP_FREE) {
1900+
buf_LRU_block_free_hashed_page((buf_block_t*) bpage);
1901+
}
1902+
}
1903+
18881904
/**********************************************************************//**
18891905
Updates buf_pool->LRU_old_ratio for one buffer pool instance.
18901906
@return updated old_pct */

0 commit comments

Comments
 (0)