Uploaded image for project: 'MariaDB Server'
  1. MariaDB Server
  2. MDEV-19916

Corruption after instant ADD/DROP and shrinking the index tree

    Details

      Description

      A wrong condition in the function btr_lift_page_up() causes a table to remain wrongly reset to "no instant ALTER was performed" state in any operation that involves reducing the B-tree height (such as UPDATE, DELETE, or purge of history):

      diff --git a/storage/innobase/btr/btr0btr.cc b/storage/innobase/btr/btr0btr.cc
      --- a/storage/innobase/btr/btr0btr.cc
      +++ b/storage/innobase/btr/btr0btr.cc
      @@ -3453,7 +3453,8 @@ btr_lift_page_up(
       	/* btr_page_empty() is supposed to zero-initialize the field. */
       	ut_ad(!page_get_instant(father_block->frame));
       
      -	if (page_level == 0 && index->is_instant()) {
      +	if (index->is_instant()
      +	    && father_block->page.id.page_no() == root_page_no) {
       		ut_ad(!father_page_zip);
       		byte* page_type = father_block->frame + FIL_PAGE_TYPE;
       		ut_ad(mach_read_from_2(page_type) == FIL_PAGE_INDEX);
      

      The problem can be repeated with a table whose clustered index consists of at least 2 levels of node pointer pages above the leaf level. The corruption would be noticed after the table is loaded to the InnoDB data dictionary cache (such as after server restart). Here is a test case with debug injection:

      diff --git a/mysql-test/suite/innodb/t/instant_alter_debug.test b/mysql-test/suite/innodb/t/instant_alter_debug.test
      index e54623b9cbd..1e2dbcd98bb 100644
      --- a/mysql-test/suite/innodb/t/instant_alter_debug.test
      +++ b/mysql-test/suite/innodb/t/instant_alter_debug.test
      @@ -267,4 +267,26 @@ SET DEBUG_SYNC = RESET;
       SELECT * FROM t1;
       DROP TABLE t1;
       
      +CREATE TABLE t1 (a INT PRIMARY KEY) ENGINE=InnoDB;
      +
      +# Create a tree with 2 levels of node pointers.
      +
      +SET @old_limit = @@innodb_limit_optimistic_insert_debug;
      +SET GLOBAL innodb_limit_optimistic_insert_debug = 2;
      +INSERT INTO t1 VALUES (1),(5),(4),(3),(2);
      +SET GLOBAL innodb_limit_optimistic_insert_debug = @old_limit;
      +
      +ALTER TABLE t1 ADD COLUMN b INT, ALGORITHM=INSTANT;
      +
      +SET @old_defragment = @@innodb_defragment;
      +SET GLOBAL innodb_defragment = 1;
      +OPTIMIZE TABLE t1;
      +SET GLOBAL innodb_defragment = @old_defragment;
      +
      +# Exploit MDEV-17468 to force the table definition to be reloaded
      +ALTER TABLE t1 ADD vb INT AS (b) VIRTUAL;
      +CHECK TABLE t1;
      +SELECT * FROM t1;
      +DROP TABLE t1;
      +
       SET GLOBAL innodb_purge_rseg_truncate_frequency = @save_frequency;
      

      It would crash at the second ALTER TABLE:

      10.3 9053047f3db37a174f6c1333acf189b6558c50c4

      mysqld: /mariadb/10.3/storage/innobase/rem/rem0rec.cc:322: void rec_init_offsets_comp_ordinary(const rec_t *, const dict_index_t *, ulint *, ulint, const dict_col_t::def_t *, rec_leaf_format): Assertion `index->is_instant()' failed.
      #7  0x000055ba28037d8d in rec_init_offsets_comp_ordinary (rec=0x7f4a2fb840ab "\200", index=0x7f49d4046838, offsets=<optimized out>, n_core=4, def_val=0x0, format=REC_LEAF_COLUMNS_ADDED) at /mariadb/10.3/storage/innobase/rem/rem0rec.cc:322
      #8  0x000055ba2803466e in rec_init_offsets (rec=<optimized out>, index=<optimized out>, offsets=0x7f49d40a8e20, leaf=<optimized out>) at /mariadb/10.3/storage/innobase/rem/rem0rec.cc:603
      #9  rec_get_offsets_func (rec=0x7f4a2fb840ab "\200", index=0x7f49d4046838, offsets=0x7f49d40a8e20, leaf=<optimized out>, n_fields=<optimized out>, file=<optimized out>, line=6706, heap=0x7f4a2f102640) at /mariadb/10.3/storage/innobase/rem/rem0rec.cc:864
      #10 0x000055ba28171ae8 in btr_estimate_number_of_different_key_vals (index=0x7f49d4046838) at /mariadb/10.3/storage/innobase/btr/btr0cur.cc:6704
      #11 0x000055ba28212ee8 in dict_stats_update_transient_for_index (index=0x7f49d4046838) at /mariadb/10.3/storage/innobase/dict/dict0stats.cc:890
      #12 0x000055ba28213261 in dict_stats_update_transient (table=<optimized out>) at /mariadb/10.3/storage/innobase/dict/dict0stats.cc:948
      #13 dict_stats_update (table=0x7f49d4045628, stats_upd_option=<optimized out>) at /mariadb/10.3/storage/innobase/dict/dict0stats.cc:3359
      #14 0x000055ba27f3fae3 in ha_innobase::open (this=0x7f49d41becb0, name=0x7f49d409edf8 "./test/t1") at /mariadb/10.3/storage/innobase/handler/ha_innodb.cc:6154
      #15 0x000055ba27d8180e in handler::ha_open (this=0x7f49d41becb0, table_arg=0x7f49d41d4438, name=0x7f49d409edf8 "./test/t1", mode=2, test_if_locked=18, mem_root=0x0, partitions_to_open=0x0) at /mariadb/10.3/sql/handler.cc:2760
      #16 0x000055ba27c22d92 in open_table_from_share (thd=0x7f49d4000cf8, share=0x7f49d409e8c0, alias=<optimized out>, db_stat=33, prgflag=<optimized out>, ha_open_flags=0, outparam=0x7f49d41d4438, is_create_table=<optimized out>, partitions_to_open=0x0) at /mariadb/10.3/sql/table.cc:3506
      #17 0x000055ba27ae0d04 in open_table (thd=<optimized out>, table_list=<optimized out>, ot_ctx=0x7f4a2f105250) at /mariadb/10.3/sql/sql_base.cc:1979
      #18 0x000055ba27bfe223 in mysql_inplace_alter_table (thd=<optimized out>, table_list=0x7f49d4011de0, table=0x0, altered_table=<optimized out>, ha_alter_info=<optimized out>, inplace_supported=HA_ALTER_INPLACE_INSTANT, target_mdl_request=<optimized out>, alter_ctx=<optimized out>) at /mariadb/10.3/sql/sql_table.cc:7631
      #19 mysql_alter_table (thd=<optimized out>, new_db=<optimized out>, new_name=<optimized out>, create_info=<optimized out>, table_list=0x7f49d4011de0, alter_info=<optimized out>, order_num=<optimized out>, order=<optimized out>, ignore=<optimized out>) at /mariadb/10.3/sql/sql_table.cc:9797
      #20 0x000055ba27c5f320 in Sql_cmd_alter_table::execute (this=<optimized out>, thd=0x7f49d4000cf8) at /mariadb/10.3/sql/sql_alter.cc:500
      #21 0x000055ba27b5068f in mysql_execute_command (thd=0x7f49d4000cf8) at /mariadb/10.3/sql/sql_parse.cc:6023
      #22 0x000055ba27b4d183 in mysql_parse (thd=0x7f49d4000cf8, rawbuf=0x7f49d4011ce0 "ALTER TABLE t1 ADD vb INT AS (b) VIRTUAL", length=<optimized out>, parser_state=0x7f4a2f1076a0, is_com_multi=<optimized out>, is_next_command=<optimized out>) at /mariadb/10.3/sql/sql_parse.cc:7829
      

        Attachments

          Issue Links

            Activity

              People

              • Assignee:
                marko Marko Mäkelä
                Reporter:
                marko Marko Mäkelä
              • Votes:
                1 Vote for this issue
                Watchers:
                4 Start watching this issue

                Dates

                • Created:
                  Updated:
                  Resolved: