If I define UNIQUE(a,b), the check will pass. As soon as I define a prefix index on the column b, the check will fail.
Internally, the unique index is defined as INDEX(DB_ROW_HASH_1), with the value 0x55a75aa8493a53ce for the only row.
It is notable that a non-EXTENDED check reports no failure.
I am debugging the following variant of the test:
--source include/have_innodb.inc
|
CREATE TABLE t (a TINYBLOB, b CHAR(64), UNIQUE(a,b(8))) ENGINE=InnoDB
|
STATS_PERSISTENT=0;
|
INSERT INTO t VALUES ('foo','bar');
|
SET @save_freq=@@GLOBAL.innodb_purge_rseg_truncate_frequency;
|
SET GLOBAL innodb_max_purge_lag_wait= 0;
|
SET GLOBAL innodb_max_purge_lag_wait= @save_freq;
|
CHECK TABLE t QUICK EXTENDED;
|
SELECT b FROM t FORCE INDEX(a);
|
DROP TABLE t;
|
The wait for the purge of committed transaction history is there to give the MDEV-24402 EXTENDED more chances to catch errors.
When the value of the column is being computed from the clustered index record in row_check_index_match(), the function innobase_get_computed_value() will invoke row_sel_field_store_in_mysql_format_func() with the full length of column b, instead of using the column prefix length b(8). I think that this is correct: InnoDB is supposed to supply the full values of the base columns.
What seems incorrect is the way how the value of DB_ROW_HASH_1 is computed. I see that the vf.vcol_info.expr in TABLE::update_virtual_field() is defined as Item_func_hash on two expressions, the second one being Item_func_left). This subexpression would be created in parse_vcol_defs() when opening the table definition:
if (keypart->key_part_flag & HA_PART_KEY_SEG)
|
{
|
int length= keypart->length/keypart->field->charset()->mbmaxlen;
|
list_item= new (mem_root) Item_func_left(thd,
|
new (mem_root) Item_field(thd, keypart->field),
|
new (mem_root) Item_int(thd, length));
|
list_item->fix_fields(thd, NULL);
|
keypart->field->vcol_info=
|
table->field[keypart->field->field_index]->vcol_info;
|
}
|
else
|
list_item= new (mem_root) Item_field(thd, keypart->field);
|
We must ensure that the full column b from the clustered index will be attached to the Item_field when the expression is being evaluated.
During the invocation of innobase_get_computed_value(), Item_field::val_str() will be invoked twice: First for on the full column a, with str->Ptr="foo", then on the column prefix b, with the incorrect value str->Ptr=nullptr. For the full column, the str->Ptr was initialized already during the execution of the INSERT statement. Curiously, even during the INSERT, the column prefix index is not being taken account in the calculation, but apparently the evaluation error will be ignored, unlike in innobase_get_computed_value():
10.6 18a0f0c17839e3662ac86a22975ae45886ae4cf6
|
(rr) display *str
|
1: *str = {<Charset> = {m_charset = 0x563948b170e0 <my_charset_bin>}, <Binary_string> = {<Sql_alloc> = {<No data fields>}, Ptr = 0x0, str_length = 0, Alloced_length = 0, extra_alloc = 0, alloced = false,
|
thread_specific = false}, <No data fields>}
|
(rr) backtrace
|
#0 Item_field::val_str (this=0x7fe0a80266d8, str=0x7fe0a80265c0) at /mariadb/10.6m/sql/item.cc:3331
|
#1 0x00005639481d513f in Item_func_left::val_str (this=0x7fe0a8026598, str=0x7fe0a80265c0) at /mariadb/10.6m/sql/item_strfunc.cc:1620
|
#2 0x000056394811a2c4 in Item::val_str (this=<optimized out>) at /mariadb/10.6m/sql/item.h:1014
|
#3 Item_func_hash::val_int (this=0x7fe0a80ad0b0) at /mariadb/10.6m/sql/item_func.cc:1772
|
#4 0x000056394814719e in Item::save_int_in_field (this=0x7fe0a80ad0b0, field=0x7fe0a8026080, no_conversions=<optimized out>) at /mariadb/10.6m/sql/item.cc:6809
|
#5 0x0000563948509db0 in Type_handler_int_result::Item_save_in_field (this=<optimized out>, item=<optimized out>, field=<optimized out>, no_conversions=<optimized out>) at /mariadb/10.6m/sql/sql_type.cc:4353
|
#6 0x00005639481376c7 in Item::save_in_field (this=0x7fe0a80ad0b0, field=0x7fe0a8026080, no_conversions=<optimized out>) at /mariadb/10.6m/sql/item.cc:6819
|
#7 0x000056394845769a in TABLE::update_virtual_fields (this=this@entry=0x7fe0a80e2f38, h=<optimized out>, update_mode=update_mode@entry=VCOL_UPDATE_FOR_WRITE) at /mariadb/10.6m/sql/table.cc:8855
|
#8 0x00005639482c1019 in fill_record (thd=thd@entry=0x7fe0a80020d8, table=table@entry=0x7fe0a80e2f38, ptr=0x7fe0a818beb8, ptr@entry=0x7fe0a818be98,
|
values=@0x7fe0a802ed58: {<base_list> = {<Sql_alloc> = {<No data fields>}, first = 0x7fe0a802edf8, last = 0x7fe0a802ee90, elements = 2}, <No data fields>}, ignore_errors=ignore_errors@entry=false,
|
use_value=use_value@entry=false) at /mariadb/10.6m/sql/sql_base.cc:8946
|
#9 0x00005639482c108b in fill_record_n_invoke_before_triggers (thd=thd@entry=0x7fe0a80020d8, table=table@entry=0x7fe0a80e2f38, ptr=0x7fe0a818be98,
|
values=@0x7fe0a802ed58: {<base_list> = {<Sql_alloc> = {<No data fields>}, first = 0x7fe0a802edf8, last = 0x7fe0a802ee90, elements = 2}, <No data fields>}, ignore_errors=ignore_errors@entry=false,
|
event=event@entry=TRG_EVENT_INSERT) at /mariadb/10.6m/sql/sql_base.cc:8988
|
#10 0x000056394834aa38 in mysql_insert (thd=thd@entry=0x7fe0a80020d8, table_list=0x7fe0a802e1d0,
|
Adding a sleep before CHECK causes appearance of additional warnings in the CHECK and different errors in the error log;
--source include/have_innodb.inc
--sleep 2
# Cleanup
10.6 79dc3989f
CHECK TABLE t EXTENDED;
Table Op Msg_type Msg_text
test.t check Warning InnoDB: Clustered index record not found for index `a` of table `test`.`t`: COMPACT RECORD(info_bits=0, 2 fields): {[8]U Z I:S (0x55A75AA8493A53CE),[6] (0x000000000200)}
test.t check Warning InnoDB: The B-tree of index a is corrupted.
test.t check Warning InnoDB: Index 'a' contains 0 entries, should be 1.
test.t check error Corrupt
DROP TABLE t;
bug.15436 'innodb' [ fail ] Found warnings/errors in server log file!
Test ended at 2022-11-06 17:10:33
line
2022-11-06 17:10:32 4 [ERROR] InnoDB: Clustered index record not found for index `a` of table `test`.`t`: COMPACT RECORD(info_bits=0, 2 fields): {[8]U Z I:S (0x55A75AA8493A53CE),[6] (0x000000000200)}
2022-11-06 17:10:32 4 [ERROR] InnoDB: Flagged corruption of `a` in table `test`.`t` in CHECK TABLE-check index