美文网首页
MySQL:8.0 新的LOB字段组织方式(BUG)

MySQL:8.0 新的LOB字段组织方式(BUG)

作者: 重庆八怪 | 来源:发表于2025-01-21 17:52 被阅读0次

简单记录,供参考


一、总体格式

最近在分析一个BUG的时候,把8.0的新的LOB字段的组织方式稍微看了一下,简单来讲对于超长的大字段,每个字段我们都会用到溢出page处理,这里溢出段主要包含4个元素,

  • 一个ref指针,这个指针20字节,用于指向lob first page,也就是指向lob 溢出page的第一个page
  • 一个lob first page,这个page是可以说是一个元数据管理的page,不过为了防止lob字段的数据不大,其中也会包含一些数据大概15K的样子,其中还包含了10个index_entry_t,每个index_entry_t用于指向一个lob data page
  • 多个lob index page,如果lob first page重的10个index_entry_t不够了就需要分配额外的lob index page用于专门存储index_entry_t,每个index_entry_t用于指向一个lob data page,我们可以称其为node
  • 更多的lob data page,当然如果第一个lob frist page的15K数据已经存不下数据了就需要额外的开辟lob data page

下面我们简单看看了下他们的组织方式。

二、ref指针

在每个大字段的下面留下了一个20字节的指针,内部叫做lob::ref_t,其格式如下

lob::ref_t ( col b ref)

  • BTR_EXTERN_SPACE_ID 0 4
  • BTR_EXTERN_PAGE_NO 4 4
  • BTR_EXTERN_VERSION 8 4 Version number of LOB (LOB in new format)
  • BTR_EXTERN_LEN 12 8

这个指针重主要包含是4字节的space id,4字节的page no就是我们常说的指向lob字段数据的指针。

三、lob first page

这个page的大概组织方式如下

  • OFFSET_VERSION 38 1
  • OFFSET_FLAGS 39 1
  • OFFSET_LOB_VERSION 40 4
  • OFFSET_LAST_TRX_ID 44 6 //最后一次修改本lob行的事务
  • OFFSET_DATA_LEN 50 4
  • OFFSET_TRX_ID 54 6 //修改事务的ID
  • OFFSET_INDEX_LIST 60 4 + 2 * FIL_ADDR_SIZE(6) = 16
    FLST_LEN(4)+FLST_FIRST(6)+FLST_LAST(6) FLST_FIRST
    4 bytes pageno 2bytes offset 指向的是lob::index_entry_t所在的page和offset
  • OFFSET_INDEX_FREE_NODES 76 4 + 2 * FIL_ADDR_SIZE(6) = 16 FLST_LEN(4)+FLST_FIRST(6)+FLST_LAST(6) FLST_FIRST:4 bytes pageno 2bytes offset 指向的是lob::index_entry_t所在的page和offset
  • 10个index_entry_t,每个60字节
    index_entry_t                                               92+N*60  (N为10)  OFFSET_PAGE_NO     OFFSET_PREV   OFFSET_NEXT            <---OFFSET_INDEX_FREE_NODES.FLST_FIRST
    index_entry_t                                                                 OFFSET_PAGE_NO     OFFSET_PREV   OFFSET_NEXT
    index_entry_t                                                                 OFFSET_PAGE_NO     OFFSET_PREV   OFFSET_NEXT
    index_entry_t                                                                 OFFSET_PAGE_NO     OFFSET_PREV   OFFSET_NEXT
    index_entry_t                                                                 OFFSET_PAGE_NO     OFFSET_PREV   OFFSET_NEXT
    index_entry_t                                                                 OFFSET_PAGE_NO     OFFSET_PREV   OFFSET_NEXT
    index_entry_t                                                                 OFFSET_PAGE_NO     OFFSET_PREV   OFFSET_NEXT
    index_entry_t                                                                 OFFSET_PAGE_NO     OFFSET_PREV   OFFSET_NEXT
    index_entry_t                                                                 OFFSET_PAGE_NO     OFFSET_PREV   OFFSET_NEXT
    index_entry_t                                                                 OFFSET_PAGE_NO     OFFSET_PREV   OFFSET_NEXT             <---OFFSET_INDEX_FREE_NODES.FLST_LAST
  • 剩下的空间用于存储数据,first page data(frist page的存储的数据)

需要注意这里的fsp header的FIL_PAGE_NEXT指向的下一个page是lob index page,且使用头插法插入lob index page,多个lob index page也形成了链表。
这里涉及到了lob::index_entry_t,我们也简单看看lob::index_entry_t的物理组织方式,如下

  • OFFSET_PREV 0 6 上一个 lob::index_entry_t
  • OFFSET_NEXT 6 6 下一个 lob::index_entry_t
  • OFFSET_VERSIONS 12 16
  • OFFSET_TRXID 28 34
  • OFFSET_TRXID_MODIFIER 34 6
  • OFFSET_TRX_UNDO_NO 40 4
  • OFFSET_TRX_UNDO_NO_MODIFIER 44 4
  • OFFSET_PAGE_NO 48 4 --->lob page no
  • OFFSET_DATA_LEN 52 4
  • OFFSET_LOB_VERSION 56 4

其中OFFSET_PAGE_NO就是指向实际的lob data page的指针,而OFFSET_PREV 和OFFSET_NEXT将所有的lob::index_entry_t连接成了,由OFFSET_INDEX_LIST和OFFSET_INDEX_FREE_NODES作为头节点分为2个链表,分别代表使用和空闲的,使用的时候总是从空闲链表的第一个节点开始使用。其他属性和多版本有关暂未研究。

四、lob index page

这个就很简单了几乎全是lob::index_entry_t,这是除了first page中存储10个lob::index_entry_t意外需要的额外的存储lob::index_entry_t的专用page,那么我们可以大概知道160K+15K 左右以上的lob 字段就需要分配额外的lob index page。这里面除了1字节的OFFSET_VERSION,全是lob::index_entry_t

  • OFFSET_VERSION 38 1

五、lob data page

这里存储的也全部是数据,大概组织如下,

  • OFFSET_VERSION 38 1
  • OFFSET_DATA_LEN 39 4
  • OFFSET_TRX_ID 43 6

其他的存储的全部是lob字段的数据。lob data page只有在lob first page的15K 数据不够存储的时候才会新开辟。

六、关于lob字段page的来源

最后在分配过程中发现在分配lob 字段相关的page的时候,是直接从primary 主键叶子节点的inode(segment )的extent中获取的,也就是说大字段的page并没有专门的inode(segment)和extent给lob page使用,如下证明,

current read blocks is : 1173 --This Block is data blocks( index pages)!
current read blocks is : 1174 --This Block is data blocks( index pages)!
current read blocks is : 1175 --This frist Block is uncompressed LOB!
current read blocks is : 1176 --This Block is uncompressed LOB!
current read blocks is : 1177 --This Block is uncompressed LOB!

这里明显1173到1177(1173/64 == 1177/64)都属于一个extent,那么它们page是混用的。

七、总体格式

image.png

八、具体分配过程参考代码

我这里只是看了下分配过程,但是涉及到更多的多版本管理并没有去学习,分配的大概如下,供参考

row_ins_clust_index_entry  3115
  ->row_ins_clust_index_entry_low 2570
     ->row_ins_index_entry_big_rec_func 2302
        ->lob::btr_store_big_rec_extern_fields 577
           ->lob::insert 968  插入一个lob 字段
               -> ref.set_length(0, mtr)
                   设置ref 指向的 lob的数据长度为0
              -> if (!ref_t::is_big(page_size, len)) 
                  当前 8025 也是 始终返回为ture                     
              ->first_page_t first(mtr, index)
                 调用lob::first_page_t::first_page_t 103
              ->buf_block_t *first_block = first.alloc 
                调用lob::first_page_t::alloc 240
                分配和初始化 frist page,返回到firtst_block中
                ->lob::alloc_lob_page
                   分配lob page,TODO分配一个lob page
                ->set_page_type();
                         初始化为FIL_PAGE_TYPE为FIL_PAGE_TYPE_LOB_FIRST,MLOG_2BYTES
               ->set_version_0();
                  设置版本为0 MLOG_1BYTE
               ->set_data_len(0); set_trx_id(0);
                   设置长度和事务ID为0
               ->byte *free_lst = free_list(); 
                   获取freelst链表  OFFSET_INDEX_FREE_NODES
               ->flst_init(index_lst, m_mtr)
                  初始化inex 链表 4 len 6 frist 6 last  NULL NULL 
               ->flst_init(free_lst, m_mtr)
                  初始化free 链表 4 len 6 frist 6 last  NULL NULL
               ->nc = node_count()
                  准备初始化10 index entry放到frist page里面
               ->byte *cur = nodes_begin()
                 获取 index_entry_t 开始的指针 也就是LOB_PAGE_DATA开始的位置
               ->for (ulint i = 0; i < nc; ++i)
                从LOB_PAGE_DATA开始初始化10个
                 ->index_entry_t entry(cur, m_mtr, m_index)
                     在cur位置上初始化一个index_entry_t
                     初始化这个index_entry_t,放入当前所在的cur到m_node中,索引和mtr等
                 ->entry.init()
                    初始化这个index_entry_t,所有物理结构信息初始化为0
                 ->flst_add_last(free_lst, cur, m_mtr)
                    将初始化好的index_entry_t放入到链表free_lst中
                    OFFSET_INDEX_FREE_NODES.FLST_FIRST 指向第一个 index_entry_t
                    OFFSET_INDEX_FREE_NODES.FLST_LAST  指向最后一个 index_entry_t
                 ->cur += index_entry_t::SIZE
                    继续初始化剩下的10个index_entry_t
                  每个index_entry_t 60字节
                 ->set_next_page_null
                    设置本frist page的下一个page为NULL,这个是FIL HEADER的属性FIL_PAGE_NEXT MLOG_4BYTES
              ->first.set_last_trx_id(trxid); 
                 设置事务ID
              ->first.init_lob_version();
                 初始化lob vesion为1
              ->page_no_t first_page_no = first.get_page_no(); 
                 获取page no
              -> page_id_t first_page_id(space_id, first_page_no); 
                  获取frist page     
              ->flst_base_node_t *index_list = first.index_list()      
                获取frist page 的 lob::index_entry_t list
              ->ulint to_write = first.write(trxid, ptr, len)
               调用first_page_t::write 将lob写入到first page中  len 返回剩下需要写入的字节
                  ->byte *ptr = data_begin()
                     获取frist page数据的起点 LOB_PAGE_DATA + index_array_size
                  ->ulint written = (len > max_space_available()) ? max_space_available() : len
                     这里判断frist page 能够写入的最大内存,其中为16K- LOB_PAGE_DATA + index_array_size-LOB_PAGE_TRAILER_LEN
                     如果小于就写入len长度,否则写入max长度大约15K多
                  ->mlog_write_string(ptr, data, written, m_mtr)
                     写入数据
                  ->set_data_len(written)
                     设置frist page中长度
                  ->set_trx_id(trxid)
                      写入trxid到frist page  OFFSET_TRX_ID中
                  ->data += written
                     推进数据指针
                  ->len -= written
                      需要写入的长度减去吸入的长度,剩下还需要写入的长度
                  ->return (written)
                     返回写入的长度,可能len的内容还没有写完,frist page最多也就是写入15K左右   
             ->total_written += to_write
                写入长度增加
             ->ulint remaining = len;   
                剩余需要写入的长度,也可能不需要写入了
             ->接下来初始化第一个Insert an index entry
                ->flst_base_node_t *f_list = free_list();
                   获取free list地址 flst_base_node_t是一个地址
                ->flst_node_t *node = first.alloc_index_entry(ctx->is_bulk())
                   ->flst_read_addr(base + FLST_FIRST, mtr)
                      返回pageno和offset {page = 5, boffset = 96}
                   ->if (fil_addr_is_null(node_addr))
                      是否free list的 frist index entry 为NULL 因为正常free list的 frist应该指向 node page 为NULL则可能是用完了
                    ->node_page_t node_page(m_mtr, m_index); 
                       需要新分配node_page_t  page
                    ->buf_block_t *block = node_page.alloc(*this, bulk)
                       分配node page 和frist page不同全部用于存储 index node t 信息
                       且frist page的next page指向本node_page_t 信息 ,
                       本node_page_t的next 指向前面的node_page_t
                    -> node_addr = flst_get_first(f_list, m_mtr);
                        获取新的free list FLST_FIRST 地址
                    ->flst_node_t *node = addr2ptr_x(node_addr); 
                       获取地址
                   ->flst_remove(f_list, node, m_mtr);
                      从free list中去除
                      然后返回这个新分配的node,也就是Insert an index entry
             -> 接下来初始化这个index_entry_t的信息,这里明显frist page的信息,因为写入也是写入到frist page中
                 因此这里不需要初始化lob data page了。
                 {
                   index_entry_t entry(node, mtr, index);
                   entry.set_versions_null();
                   entry.set_trx_id(trxid);
                   entry.set_trx_id_modifier(trxid);
                   entry.set_trx_undo_no(undo_no);
                   entry.set_trx_undo_no_modifier(undo_no);
                   entry.set_page_no(first.get_page_no());
                   entry.set_data_len(to_write);
                   entry.set_lob_version(1);
                 }
             ->flst_add_last(index_list, node, mtr)
                将这个index_entry_t写入到使用加入到 index list的末尾               
            -> first.set_trx_id(trxid);
            -> first.set_data_len(to_write);
                更新first page的信息
            ->while (remaining > 0)
               这里要写入说明第一个frist page 的 15k左右已经写满了  开始循环写入剩下的内容
               ->data_page_t data_page(mtr, index)
               ->buf_block_t *block = data_page.alloc(mtr, ctx->is_bulk())
                  初始化一个buf_block_t,并且分配page 
               -> to_write = data_page.write(trxid, ptr, remaining)
                  往分配的data_page_t数据块中写入
               -> total_written += to_write;
                   写入总量增加
               ->data_page.set_trx_id(trxid);
                  设置data page的事务id
               ->flst_node_t *node = first.alloc_index_entry(ctx->is_bulk()); 
                  分配一个 index_entry_t
               ->写入index_entry_t信息
                 { 
                    entry.set_versions_null();
                   entry.set_trx_id(trxid);
                   entry.set_trx_id_modifier(trxid);
                   entry.set_trx_undo_no(undo_no);
                   entry.set_trx_undo_no_modifier(undo_no);
                   entry.set_page_no(data_page.get_page_no());
                   entry.set_data_len(to_write);
                   entry.set_lob_version(1);
                  }
               ->entry.push_back(first.index_list())
                  写入到index list的 尾部
              ->ref.update(space_id, first_page_no, 1, mtr); 
                 设置lob ref 指针 space id page no 等  lob::ref_t::update
              ->ref.set_length(total_written, mtr); 
                  设置lob ref 指针的长度 

ob::first_page_t::free_all_data_pages
  ->mtr_t local_mtr;
  ->mtr_start(&local_mtr);
     本地MTR
  ->fil_addr_t node_loc = flst_get_first(flst, &local_mtr); 
     获取index list frist node
  ->while (!fil_addr_is_null(node_loc))
     循环释放 index_entry_t 对应的data
      ->flst_node_t *node = addr2ptr_x(node_loc, &local_mtr);
      ->cur_entry.reset(node);
         获取当前的index_entry_t 
     ->ata_page_t data_page(&local_mtr, m_index);
        /初始化data_page_t 用的MTR为local_mtr
     ->data_page.load_x(page_no);
        获取page
     ->data_page.dealloc(); 
        释放 page
        ->btr_page_free_low(m_index, m_block, ULINT_UNDEFINED, m_mtr)
          ->root = btr_root_get(index, mtr)
             获取root节点
          -> if (level == 0 || level == ULINT_UNDEFINED)
              如果是大字段这里level 0 为 也就是分配到叶子节点的segment
              ->seg_header = root + PAGE_HEADER + PAGE_BTR_SEG_LEAF; 
                 PAGE_BTR_SEG_LEAF 和下面的PAGE_BTR_SEG_TOP 只有在root节点才有
               else ...          
          ->fseg_free_page(seg_header, block->page.id.space(), block->page.id.page_no(),level != ULINT_UNDEFINED, mtr);
             带入segment的header ,这里mtr为 local_mtr
             ->fil_space_t *space = fil_space_get(space_id);
                获取space
             ->mtr_x_lock_space(space, mtr);
                对space上锁
             ->seg_inode = fseg_inode_get(seg_header, space_id, page_size, mtr, &iblock)
                获取本segment的 segment 结构,这里大字段就是叶子节点
             ->const page_id_t page_id(space_id, page)
                将需要释放的page的space和page no初始化到page_id中
             ->fseg_free_page_low(seg_inode, page_id, page_size, ahi, mtr)
                这里seg_inode为需要释放的inode 这里就是叶子节点的inode
                page_id是需要释放的page
                mtr 为local_mtr
                -> 如果AHI开启
                   btr_search_drop_page_hash_when_freed(page_id, page_size)
                   释放这个page的AHI

     ->cur_entry.set_page_no(FIL_NULL)
        更新index_entry_t 指向的page no为FIL NULL
     ->node_loc = cur_entry.get_next();
        获取下一个index_entry_t 
    ->restart_mtr(&local_mtr);
      重启MTR-->每次都写入到redo commit
 ->mtr_commit(&local_mtr)
    最后commit

alloc_lob_page
  ->btr_page_alloc(index, hint, FSP_NO_DIR, 0, alloc_mtr, lob_mtr)
    ->btr_page_alloc_low(index, hint_page_no, file_direction, level, mtr, init_mtr)   level 为0
      ->btr_page_alloc(index, hint_page_no, file_direction, level,mtr, init_mtr)
          if (level == 0) { 从root level0的segment中的分配
              seg_header = root + PAGE_HEADER + PAGE_BTR_SEG_LEAF;
            } else {
             seg_header = root + PAGE_HEADER + PAGE_BTR_SEG_TOP;
            }
      
     
           seg_header = root + PAGE_HEADER + PAGE_BTR_SEG_LEAF
         可以发现lob的 extent 全部来自 主键 index的level 0 page 中,也就是和primary 主键叶子节点的extent 一样

八、其他

2025-01-10T23:18:26.420715+08:00 0 [ERROR] [MY-013183] [InnoDB] Assertion failure: fsp0fsp.cc:3478:not_full_n_used > 0 thread 140389791692544
InnoDB: We intentionally generate a memory trap.
InnoDB: Submit a detailed bug report to http://bugs.mysql.com.
InnoDB: If you get repeated assertion failures or crashes, even
InnoDB: immediately after the mysqld startup, there may be
InnoDB: corruption in the InnoDB tablespace. Please refer to
InnoDB: http://dev.mysql.com/doc/refman/8.0/en/forcing-innodb-recovery.html
InnoDB: about forcing recovery.
15:18:26 UTC - mysqld got signal 6 ;
Most likely, you have hit a bug, but this error can also be caused by malfunctioning hardware.
Thread pointer: 0x7fa2b0000b60
Attempting backtrace. You can use the following information to find out
where mysqld died. If you see no messages after this, something went
terribly wrong...
stack_bottom = 7faf0ba9ec28 thread_stack 0x80000
/mysql/base/8.0.25/bin/mysqld(my_print_stacktrace(unsigned char const*, unsigned long)+0x2e) [0x2079c3e]
/mysql/base/8.0.25/bin/mysqld(handle_fatal_signal+0x323) [0x10616b3]
/lib64/libpthread.so.0(+0x12cf0) [0x7fb8af951cf0]
/lib64/libc.so.6(gsignal+0x10f) [0x7fb8ad882acf]
/lib64/libc.so.6(abort+0x127) [0x7fb8ad855ea5]
/mysql/base/8.0.25/bin/mysqld() [0xdafadb]
/mysql/base/8.0.25/bin/mysqld() [0x24aa0d3]
/mysql/base/8.0.25/bin/mysqld(fseg_free_page(unsigned char*, unsigned int, unsigned int, bool, mtr_t*)+0xf2) [0x24aa262]
/mysql/base/8.0.25/bin/mysqld(lob::first_page_t::free_all_data_pages()+0x312) [0x24facb2]
/mysql/base/8.0.25/bin/mysqld(lob::first_page_t::destroy()+0x11) [0x24fb741]
/mysql/base/8.0.25/bin/mysqld(lob::purge(lob::DeleteContext*, dict_index_t*, unsigned long, unsigned long, unsigned long, upd_field_t const*)+0x1cce) [0x250022e]
/mysql/base/8.0.25/bin/mysqld(lob::BtrContext::free_externally_stored_fields(unsigned long, unsigned long, bool,


lob::BtrContext::free_externally_stored_fields


Bug #33454557 Innodb asserts due to corruption in the fsp layer while running MTR tests
git show 0121b240a119486c4c44613d65417229328dda0f


https://bugs.mysql.com/bug.php?id=105592



BtrContext::free_externally_stored_fields







lob::ref_t
     set_space_id
     set_page_no
     set_offset
     set_length
     
     





     
 
/** The reference in a field for which data is stored on a different page.
The reference is at the end of the 'locally' stored part of the field.
'Locally' means storage in the index record.
We store locally a long enough prefix of each column so that we can determine
the ordering parts of each index record without looking into the externally
stored part. */
/*-------------------------------------- @{ */

/** Space identifier where stored. */
const ulint BTR_EXTERN_SPACE_ID = 0;

/** page number where stored */
const ulint BTR_EXTERN_PAGE_NO = 4;

/** offset of BLOB header on that page */
const ulint BTR_EXTERN_OFFSET = 8;

/** Version number of LOB (LOB in new format)*/
const ulint BTR_EXTERN_VERSION = BTR_EXTERN_OFFSET;

/** 8 bytes containing the length of the externally stored part of the LOB.
The 2 highest bits are reserved to the flags below. */
const ulint BTR_EXTERN_LEN = 12;


#define BTR_EXTERN_FIELD_REF_SIZE FIELD_REF_SIZE
constexpr size_t FIELD_REF_SIZE = 20;


20字节的

ref内存结构

ref_mem_t

      space_id_t m_space_id;

  /** Page number of first LOB page. */
  page_no_t m_page_no;

  /** Offset within m_page_no where LOB begins. */
  ulint m_offset;

  /** Length of LOB */
  ulint m_length;

  /** Whether the LOB is null. */
  bool m_null;

  /** Whether the clustered index record owns this LOB. */
  bool m_owner;

  /** Whether the clustered index record inherited this LOB from
  another clustered index record. */
  bool m_inherit;

  /** Whether the LOB is partially updated. */
  bool m_partial;

  /** Whether the blob is being modified. */
  bool m_being_modified;



big_rec_t
   mem_heap_t *heap;
   const ulint capacity;
    ulint n_fields;
    big_rec_field_t *fields;


big_rec_field_t
    ulint field_no
    ulint len;
    void *data;
    bool ext_in_old;
    bool ext_in_new;
    


first_page_t表示first page上数据的组织


first_page_t 
     基础类basic_page_t
                  ->buf_block_t *m_block;
                  ->mtr_t *m_mtr;
                  ->dict_index_t *m_index;



最近在分析一个BUG的时候,把8.0的新的LOB的格式稍微看了一下,简单来讲对于超长的大字段,在每个大字段的下面留下了一个20字节的指针,内部叫做lob::ref_t,其格式如下

lob::ref_t ( col b ref)  
   BTR_EXTERN_SPACE_ID  0 4
   BTR_EXTERN_PAGE_NO  4 4
   BTR_EXTERN_OFFSET     8 4  OLD
   BTR_EXTERN_VERSION   8 4   Version number of LOB (LOB in new format)
   BTR_EXTERN_LEN            12 8  ref 指向的lob 行的长度




lob::ref_t ( col b ref)  
   BTR_EXTERN_SPACE_ID  0 4
   BTR_EXTERN_PAGE_NO  4 4
   BTR_EXTERN_OFFSET     8 4  OLD
   BTR_EXTERN_VERSION   8 4   Version number of LOB (LOB in new format)
   BTR_EXTERN_LEN            12 8  ref 指向的lob 行的长度
        |
        |/
lob::first_page_t (frist page)为物理结构 继承basic_page_t   第一个page里面包含了数据,也包含了管理信息,还包含了10个index_entry_t

OFFSET_VERSION                         38  1
OFFSET_FLAGS                             39   1
OFFSET_LOB_VERSION               40   4
OFFSET_LAST_TRX_ID                 44   6    //最后一次修改本lob行的事务
OFFSET_DATA_LEN                       50   4
OFFSET_TRX_ID                             54   6   //修改事务的ID
OFFSET_INDEX_LIST                      60   4 + 2 * FIL_ADDR_SIZE(6) = 16        FLST_LEN(4)+FLST_FIRST(6)+FLST_LAST(6)    FLST_FIRST:4 bytes pageno 2bytes offset  指向的是lob::index_entry_t所在的page和offset
OFFSET_INDEX_FREE_NODES    76   4 + 2 * FIL_ADDR_SIZE(6) = 16       FLST_LEN(4)+FLST_FIRST(6)+FLST_LAST(6)    FLST_FIRST:4 bytes pageno 2bytes offset  指向的是lob::index_entry_t所在的page和offset
LOB_PAGE_DATA                            92    offset where the contents of the first page begins
    index_entry_t                                      92+N*60  (N为10)  OFFSET_PAGE_NO     OFFSET_PREV   OFFSET_NEXT            <---OFFSET_INDEX_FREE_NODES.FLST_FIRST
    index_entry_t                                                                 OFFSET_PAGE_NO     OFFSET_PREV   OFFSET_NEXT
    index_entry_t                                                                 OFFSET_PAGE_NO     OFFSET_PREV   OFFSET_NEXT
    index_entry_t                                                                 OFFSET_PAGE_NO     OFFSET_PREV   OFFSET_NEXT
    index_entry_t                                                                 OFFSET_PAGE_NO     OFFSET_PREV   OFFSET_NEXT
    index_entry_t                                                                 OFFSET_PAGE_NO     OFFSET_PREV   OFFSET_NEXT
    index_entry_t                                                                 OFFSET_PAGE_NO     OFFSET_PREV   OFFSET_NEXT
    index_entry_t                                                                 OFFSET_PAGE_NO     OFFSET_PREV   OFFSET_NEXT
    index_entry_t                                                                 OFFSET_PAGE_NO     OFFSET_PREV   OFFSET_NEXT
    index_entry_t                                                                 OFFSET_PAGE_NO     OFFSET_PREV   OFFSET_NEXT             <---OFFSET_INDEX_FREE_NODES.FLST_LAST
                           first page data(frist page的存储的数据)
                    
                                   |
                                   |
                                   |   next page
                                   |
                                   |/
                                                                     <--------------------------------------------------------- 每次新加入的 放入到lob::first_page_t 的后面
lob::node_page_t(index page)
       index_entry_t                      38+N*60(N为一个page能够容下的index_entry_t个数)   OFFSET_PAGE_NO      OFFSET_PREV   OFFSET_NEXT
       index_entry_t                                                                           OFFSET_PAGE_NO     OFFSET_PREV   OFFSET_NEXT 
       index_entry_t                                                                           OFFSET_PAGE_NO     OFFSET_PREV   OFFSET_NEXT 
       index_entry_t                                                                           OFFSET_PAGE_NO     OFFSET_PREV   OFFSET_NEXT 
       index_entry_t                                                                           OFFSET_PAGE_NO     OFFSET_PREV   OFFSET_NEXT 
       index_entry_t                                                                           OFFSET_PAGE_NO     OFFSET_PREV   OFFSET_NEXT 
       index_entry_t                                                                           OFFSET_PAGE_NO     OFFSET_PREV   OFFSET_NEXT 
       ....                                                                                                    |
                                                                                                               |
                                                                                                               |/
                                                                                                     lob::data_page_t (data page 每个OFFSET_PAGE_NO都指向一个data page)           
                                                                                                    OFFSET_VERSION         38 1
                                                                                                    OFFSET_DATA_LEN      39 4
                                                                                                    OFFSET_TRX_ID             43 6
                                                                                                    LOB_PAGE_DATA          49
                                                                                                  



alloc_index_entry
   从index free frist 开始分配 ,然后去掉
   
flst_add_last
  使用加入到 index list的末尾


lob::node_page_t 为物理结构 继承basic_page_t  index_node_t的page 基本都是index_node_t
    OFFSET_VERSION    39 1
    LOB_PAGE_DATA     40



lob::data_page_t 继承basic_page_t  包含数据的lob page
   OFFSET_VERSION         38 1
   OFFSET_DATA_LEN      39 4
   OFFSET_TRX_ID             43 6
   LOB_PAGE_DATA          49
   
  


lob::basic_page_t 为逻辑结构
  buf_block_t *m_block;   当前的page
  mtr_t *m_mtr;
  dict_index_t *m_index;   当前的索引

 
 flst_node_t (frist 链表中OFFSET_INDEX_LIST和OFFSET_INDEX_FREE_NODES存储的就是这个)
  /** Page number within a space */
  page_no_t page;

  /** Byte offset within the page */
  uint32_t boffset;
 
 
 


lob::index_entry_t  结构
   OFFSET_PREV                                      0  6   上一个 lob::index_entry_t
   OFFSET_NEXT                                       6  6   下一个 lob::index_entry_t  
   OFFSET_VERSIONS                             12 16
   OFFSET_TRXID                                     28 34
   OFFSET_TRXID_MODIFIER                  34 6
   OFFSET_TRX_UNDO_NO                     40 4
   OFFSET_TRX_UNDO_NO_MODIFIER  44 4
   OFFSET_PAGE_NO                                48 4   --->lob page no
   OFFSET_DATA_LEN                               52 4
   OFFSET_LOB_VERSION                        56 4
   SIZE                                                           60
   

  byte *m_node;                             --->当前index_entry_t的位置
  mtr_t *m_mtr;                      
  const dict_index_t *m_index;      --->当前索引
  buf_block_t *m_block;                --->对应的page



lob::ref_t 
   BTR_EXTERN_SPACE_ID  0 4
   BTR_EXTERN_PAGE_NO  4 4
   BTR_EXTERN_OFFSET     8 4  OLD
   BTR_EXTERN_VERSION   8 4   Version number of LOB (LOB in new format)
   BTR_EXTERN_LEN            12 8  ref 指向的lob 行的长度
   
   20字节
   



CREATE TABLE `t1` (
`a` int NOT NULL,
`b` longblob,
`c` longblob,
PRIMARY KEY (`a`)
) ENGINE=InnoDB;

80000018
000000005737
820000008a0110

ref
00000032
00000005
00000001
0000000000015c40

ref
00000032
0000000b
00000001
0000000000015c40




first_page初始化


row_ins_clust_index_entry  3115
  ->row_ins_clust_index_entry_low 2570
     ->row_ins_index_entry_big_rec_func 2302
        ->lob::btr_store_big_rec_extern_fields 577
           ->lob::insert 968  插入一个lob 字段
               -> ref.set_length(0, mtr)
                   设置ref 指向的 lob的数据长度为0
              -> if (!ref_t::is_big(page_size, len)) 
                  当前 8025 也是 始终返回为ture                     
              ->first_page_t first(mtr, index)
                 调用lob::first_page_t::first_page_t 103
              ->buf_block_t *first_block = first.alloc 
                调用lob::first_page_t::alloc 240
                分配和初始化 frist page,返回到firtst_block中
                ->lob::alloc_lob_page
                   分配lob page,TODO分配一个lob page
                ->set_page_type();
                         初始化为FIL_PAGE_TYPE为FIL_PAGE_TYPE_LOB_FIRST,MLOG_2BYTES
               ->set_version_0();
                  设置版本为0 MLOG_1BYTE
               ->set_data_len(0); set_trx_id(0);
                   设置长度和事务ID为0
               ->byte *free_lst = free_list(); 
                   获取freelst链表  OFFSET_INDEX_FREE_NODES
               ->flst_init(index_lst, m_mtr)
                  初始化inex 链表 4 len 6 frist 6 last  NULL NULL 
               ->flst_init(free_lst, m_mtr)
                  初始化free 链表 4 len 6 frist 6 last  NULL NULL
               ->nc = node_count()
                  准备初始化10 index entry放到frist page里面
               ->byte *cur = nodes_begin()
                 获取 index_entry_t 开始的指针 也就是LOB_PAGE_DATA开始的位置
               ->for (ulint i = 0; i < nc; ++i)
                从LOB_PAGE_DATA开始初始化10个
                 ->index_entry_t entry(cur, m_mtr, m_index)
                     在cur位置上初始化一个index_entry_t
                     初始化这个index_entry_t,放入当前所在的cur到m_node中,索引和mtr等
                 ->entry.init()
                    初始化这个index_entry_t,所有物理结构信息初始化为0
                 ->flst_add_last(free_lst, cur, m_mtr)
                    将初始化好的index_entry_t放入到链表free_lst中
                    OFFSET_INDEX_FREE_NODES.FLST_FIRST 指向第一个 index_entry_t
                    OFFSET_INDEX_FREE_NODES.FLST_LAST  指向最后一个 index_entry_t
                 ->cur += index_entry_t::SIZE
                    继续初始化剩下的10个index_entry_t
                  每个index_entry_t 60字节
                 ->set_next_page_null
                    设置本frist page的下一个page为NULL,这个是FIL HEADER的属性FIL_PAGE_NEXT MLOG_4BYTES
              ->first.set_last_trx_id(trxid); 
                 设置事务ID
              ->first.init_lob_version();
                 初始化lob vesion为1
              ->page_no_t first_page_no = first.get_page_no(); 
                 获取page no
              -> page_id_t first_page_id(space_id, first_page_no); 
                  获取frist page     
              ->flst_base_node_t *index_list = first.index_list()      
                获取frist page 的 lob::index_entry_t list
              ->ulint to_write = first.write(trxid, ptr, len)
               调用first_page_t::write 将lob写入到first page中  len 返回剩下需要写入的字节
                  ->byte *ptr = data_begin()
                     获取frist page数据的起点 LOB_PAGE_DATA + index_array_size
                  ->ulint written = (len > max_space_available()) ? max_space_available() : len
                     这里判断frist page 能够写入的最大内存,其中为16K- LOB_PAGE_DATA + index_array_size-LOB_PAGE_TRAILER_LEN
                     如果小于就写入len长度,否则写入max长度大约15K多
                  ->mlog_write_string(ptr, data, written, m_mtr)
                     写入数据
                  ->set_data_len(written)
                     设置frist page中长度
                  ->set_trx_id(trxid)
                      写入trxid到frist page  OFFSET_TRX_ID中
                  ->data += written
                     推进数据指针
                  ->len -= written
                      需要写入的长度减去吸入的长度,剩下还需要写入的长度
                  ->return (written)
                     返回写入的长度,可能len的内容还没有写完,frist page最多也就是写入15K左右   
             ->total_written += to_write
                写入长度增加
             ->ulint remaining = len;   
                剩余需要写入的长度,也可能不需要写入了
             ->接下来初始化第一个Insert an index entry
                ->flst_base_node_t *f_list = free_list();
                   获取free list地址 flst_base_node_t是一个地址
                ->flst_node_t *node = first.alloc_index_entry(ctx->is_bulk())
                   ->flst_read_addr(base + FLST_FIRST, mtr)
                      返回pageno和offset {page = 5, boffset = 96}
                   ->if (fil_addr_is_null(node_addr))
                      是否free list的 frist index entry 为NULL 因为正常free list的 frist应该指向 node page 为NULL则可能是用完了
                    ->node_page_t node_page(m_mtr, m_index); 
                       需要新分配node_page_t  page
                    ->buf_block_t *block = node_page.alloc(*this, bulk)
                       分配node page 和frist page不同全部用于存储 index node t 信息
                       且frist page的next page指向本node_page_t 信息 ,
                       本node_page_t的next 指向前面的node_page_t
                    -> node_addr = flst_get_first(f_list, m_mtr);
                        获取新的free list FLST_FIRST 地址
                    ->flst_node_t *node = addr2ptr_x(node_addr); 
                       获取地址
                   ->flst_remove(f_list, node, m_mtr);
                      从free list中去除
                      然后返回这个新分配的node,也就是Insert an index entry
             -> 接下来初始化这个index_entry_t的信息,这里明显frist page的信息,因为写入也是写入到frist page中
                 因此这里不需要初始化lob data page了。
                 {
                   index_entry_t entry(node, mtr, index);
                   entry.set_versions_null();
                   entry.set_trx_id(trxid);
                   entry.set_trx_id_modifier(trxid);
                   entry.set_trx_undo_no(undo_no);
                   entry.set_trx_undo_no_modifier(undo_no);
                   entry.set_page_no(first.get_page_no());
                   entry.set_data_len(to_write);
                   entry.set_lob_version(1);
                 }
             ->flst_add_last(index_list, node, mtr)
                将这个index_entry_t写入到使用加入到 index list的末尾               
            -> first.set_trx_id(trxid);
            -> first.set_data_len(to_write);
                更新first page的信息
            ->while (remaining > 0)
               这里要写入说明第一个frist page 的 15k左右已经写满了  开始循环写入剩下的内容
               ->data_page_t data_page(mtr, index)
               ->buf_block_t *block = data_page.alloc(mtr, ctx->is_bulk())
                  初始化一个buf_block_t,并且分配page 
               -> to_write = data_page.write(trxid, ptr, remaining)
                  往分配的data_page_t数据块中写入
               -> total_written += to_write;
                   写入总量增加
               ->data_page.set_trx_id(trxid);
                  设置data page的事务id
               ->flst_node_t *node = first.alloc_index_entry(ctx->is_bulk()); 
                  分配一个 index_entry_t
               ->写入index_entry_t信息
                 { 
                    entry.set_versions_null();
                   entry.set_trx_id(trxid);
                   entry.set_trx_id_modifier(trxid);
                   entry.set_trx_undo_no(undo_no);
                   entry.set_trx_undo_no_modifier(undo_no);
                   entry.set_page_no(data_page.get_page_no());
                   entry.set_data_len(to_write);
                   entry.set_lob_version(1);
                  }
               ->entry.push_back(first.index_list())
                  写入到index list的 尾部
              ->ref.update(space_id, first_page_no, 1, mtr); 
                 设置lob ref 指针 space id page no 等  lob::ref_t::update
              ->ref.set_length(total_written, mtr); 
                  设置lob ref 指针的长度 
               
               
               
               
               
alloc_lob_page
  ->btr_page_alloc(index, hint, FSP_NO_DIR, 0, alloc_mtr, lob_mtr)
    ->btr_page_alloc_low(index, hint_page_no, file_direction, level, mtr, init_mtr)   level 为0
      ->btr_page_alloc(index, hint_page_no, file_direction, level,mtr, init_mtr)
          if (level == 0) { 从root level0的segment中的分配
              seg_header = root + PAGE_HEADER + PAGE_BTR_SEG_LEAF;
            } else {
             seg_header = root + PAGE_HEADER + PAGE_BTR_SEG_TOP;
            }
      
     
           seg_header = root + PAGE_HEADER + PAGE_BTR_SEG_LEAF
         可以发现lob的 extent 全部来自 主键 index的level 0 page 中,也就是和primary 主键叶子节点的
         extent 一样
               
               
               
 混用extent 
current read blocks is : 1149 --This Block is uncompressed LOB!
current read blocks is : 1150 --This Block is uncompressed LOB!
current read blocks is : 1151 --This Block is uncompressed LOB!
current read blocks is : 1152 --This Block is data blocks( index pages)!
current read blocks is : 1153 --This Block is data blocks( index pages)!
current read blocks is : 1154 --This Block is data blocks( index pages)!
current read blocks is : 1155 --This Block is data blocks( index pages)!
current read blocks is : 1156 --This Block is data blocks( index pages)!
current read blocks is : 1157 --This Block is data blocks( index pages)!
current read blocks is : 1158 --This Block is data blocks( index pages)!
current read blocks is : 1159 --This Block is data blocks( index pages)!
current read blocks is : 1160 --This Block is data blocks( index pages)!
current read blocks is : 1161 --This Block is data blocks( index pages)!
current read blocks is : 1162 --This Block is data blocks( index pages)!
current read blocks is : 1163 --This Block is data blocks( index pages)!
current read blocks is : 1164 --This Block is data blocks( index pages)!
current read blocks is : 1165 --This Block is data blocks( index pages)!
current read blocks is : 1166 --This Block is data blocks( index pages)!
current read blocks is : 1167 --This Block is data blocks( index pages)!
current read blocks is : 1168 --This Block is data blocks( index pages)!
current read blocks is : 1169 --This Block is data blocks( index pages)!
current read blocks is : 1170 --This Block is data blocks( index pages)!
current read blocks is : 1171 --This Block is data blocks( index pages)!
current read blocks is : 1172 --This Block is data blocks( index pages)!
current read blocks is : 1173 --This Block is data blocks( index pages)!
current read blocks is : 1174 --This Block is data blocks( index pages)!
current read blocks is : 1175 --This frist Block is uncompressed LOB!
current read blocks is : 1176 --This Block is uncompressed LOB!
current read blocks is : 1177 --This Block is uncompressed LOB!
current read blocks is : 1178 --This Block is uncompressed LOB!
current read blocks is : 1179 --This frist Block is uncompressed LOB!
current read blocks is : 1180 --This Block is uncompressed LOB!
current read blocks is : 1181 --This Block is uncompressed LOB!
current read blocks is : 1182 --This Block is uncompressed LOB!
current read blocks is : 1183 --This frist Block is uncompressed LOB!
current read blocks is : 1184 --This Block is uncompressed LOB!
current read blocks is : 1185 --This Block is uncompressed LOB!
current read blocks is : 1186 --This Block is uncompressed LOB!
current read blocks is : 1187 --This frist Block is uncompressed LOB!
current read blocks is : 1188 --This Block is uncompressed LOB!
current read blocks is : 1189 --This Block is uncompressed LOB!
current read blocks is : 1190 --This Block is uncompressed LOB!
current read blocks is : 1191 --This frist Block is uncompressed LOB!
current read blocks is : 1192 --This Block is uncompressed LOB!
current read blocks is : 1193 --This Block is uncompressed LOB!
current read blocks is : 1194 --This Block is uncompressed LOB!
current read blocks is : 1195 --This frist Block is uncompressed LOB!
current read blocks is : 1196 --This Block is uncompressed LOB!
current read blocks is : 1197 --This Block is uncompressed LOB!
current read blocks is : 1198 --This Block is uncompressed LOB!
current read blocks is : 1199 --This Block is new allocate blocks!
current read blocks is : 1200 --This Block is new allocate blocks!
current read blocks is : 1201 --This Block is new allocate blocks!
current read blocks is : 1202 --This Block is new allocate blocks!
current read blocks is : 1203 --This Block is new allocate blocks!               
               





lob::first_page_t::free_all_data_pages
  ->mtr_t local_mtr;
  ->mtr_start(&local_mtr);
     本地MTR
  ->fil_addr_t node_loc = flst_get_first(flst, &local_mtr); 
     获取index list frist node
  ->while (!fil_addr_is_null(node_loc))
     循环释放 index_entry_t 对应的data
      ->flst_node_t *node = addr2ptr_x(node_loc, &local_mtr);
      ->cur_entry.reset(node);
         获取当前的index_entry_t 
     ->ata_page_t data_page(&local_mtr, m_index);
        /初始化data_page_t 用的MTR为local_mtr
     ->data_page.load_x(page_no);
        获取page
     ->data_page.dealloc(); 
        释放 page
        ->btr_page_free_low(m_index, m_block, ULINT_UNDEFINED, m_mtr)
          ->root = btr_root_get(index, mtr)
             获取root节点
          -> if (level == 0 || level == ULINT_UNDEFINED)
              如果是大字段这里level 0 为 也就是分配到叶子节点的segment
              ->seg_header = root + PAGE_HEADER + PAGE_BTR_SEG_LEAF; 
                 PAGE_BTR_SEG_LEAF 和下面的PAGE_BTR_SEG_TOP 只有在root节点才有
               else ...          
          ->fseg_free_page(seg_header, block->page.id.space(), block->page.id.page_no(),level != ULINT_UNDEFINED, mtr);
             带入segment的header ,这里mtr为 local_mtr
             ->fil_space_t *space = fil_space_get(space_id);
                获取space
             ->mtr_x_lock_space(space, mtr);
                对space上锁
             ->seg_inode = fseg_inode_get(seg_header, space_id, page_size, mtr, &iblock)
                获取本segment的 segment 结构,这里大字段就是叶子节点
             ->const page_id_t page_id(space_id, page)
                将需要释放的page的space和page no初始化到page_id中
             ->fseg_free_page_low(seg_inode, page_id, page_size, ahi, mtr)
                这里seg_inode为需要释放的inode 这里就是叶子节点的inode
                page_id是需要释放的page
                mtr 为local_mtr
                -> 如果AHI开启
                   btr_search_drop_page_hash_when_freed(page_id, page_size)
                   释放这个page的AHI
                   
                
                
     ->cur_entry.set_page_no(FIL_NULL)
        更新index_entry_t 指向的page no为FIL NULL
     ->node_loc = cur_entry.get_next();
        获取下一个index_entry_t 
    ->restart_mtr(&local_mtr);
      重启MTR-->每次都写入到redo commit
 ->mtr_commit(&local_mtr)
    最后commit
     
  
 






flst_add_last





---------------------------------------------------

BtrContext  基础类
     mtr_t *m_mtr;
     btr_pcur_t *m_pcur;
     dict_index_t *m_index;
     rec_t *m_rec;
     ulint *m_offsets;
     buf_block_t *m_block;
     opcode m_op;
  /** Record offset within the page. */
     ulint m_rec_offset;
 /** Page number where the clust rec is present. */
     page_no_t m_btr_page_no;


InsertContext 
   const big_rec_t *m_big_rec_vec






Being_modified
     BtrContext &m_btr_ctx;
     const big_rec_t *m_big_rec_vec;
      btr_pcur_t *m_pcur;
      ulint *m_offsets;
      opcode m_op;
      mtr_t *m_mtr;


  













CREATE TABLE `t1` (
`a` int NOT NULL,
`b` longblob,
`c` longblob,
PRIMARY KEY (`a`)
) ENGINE=InnoDB;

80000018
000000005737
820000008a0110

ref
00000032
00000005
00000001
0000000000015c40

ref
00000032
0000000b
00000001
0000000000015c40








             ->lob::ref_t::update
             ->lob::ref_t::set_length
  


page 
  FSP_FREE:空闲extent
  FSP_FREE_FRAG:半满extent
  PSP_FULL_FRAG:满extent


PRIMARY 
root  page                                    inode page
 PAGE_BTR_SEG_LEAF ---->  FSEG_ID: segment ID
 inode space                               FSEG_NOT_FULL: 本segment 半满的extent
 inode offset                                FSEG_FREE:本segment free的extent
                                                    FSEG_FULL:本segment free全满的extent
                                                    FSEG_NOT_FULL_N_USED:半满extent的使用的page数量
                                                    FSEG_FRAG_ARR:碎片extent数组
                                                    
                                                    
                                                    
各位老师好 前面的BUG。考虑方面主要是FSEG_NOT_FULL_N_USED变量的问题在应用redo的时候出现了混乱,而FSEG_NOT_FULL_N_USED如下

索引primary 
root  page                                    inode page
 PAGE_BTR_SEG_LEAF ---->  FSEG_ID: segment ID
 inode space                               FSEG_NOT_FULL: 本segment 半满的extent
 inode offset                                FSEG_FREE:本segment free的extent
                                                    FSEG_FULL:本segment free全满的extent
                                                    FSEG_NOT_FULL_N_USED:半满extent的page数量 -----
                                                    FSEG_FRAG_ARR:碎片extent数组
                                                    
代表是本索引,这里以primary key 为例子,也就是大字段所在的索引,其中大字段主要使用的是primary key的叶子节点segment,通过PAGE_BTR_SEG_LEAF
指向叶子节点的inode,inode中有属性FSEG_NOT_FULL_N_USED,代表是当前所属这个segnment的extent中能够使用的page数量。

而包含大字段的表的字段比如
CREATE TABLE `t1` (
`a` int NOT NULL,
`b` longblob,
`c` longblob,
PRIMARY KEY (`a`)
) ENGINE=InnoDB;


每行数据的每个大字段都有一个ref指针指向20字节,也就是b和c都有这样一个ref例子如下
ref b
00000032    space id
00000005    指向的page no
00000001    VERSION
0000000000015c40  本大字段的长度

ref c
00000032
0000000b
00000001
0000000000015c40


而指向的page no 在8.0中是大字段的frist page,在8.0中大字段的page包含。

frist page:page就是大字段初始page,他是全部大字段的一个管理page,同时包含了index条目(10个),而index条目主要用于指向对应的大字段data page
同时存储一部分数据,能够存储大概15K的数据
index page:全部为index条目,一个条目60字节,全部用于索引data page。
data page:全部为数据。

每个index条目指向一个data page,如果大字段不大那一个page就够了,不需要额外的其他page。如果本frist page不够,data page,甚至是index page 都是需要的,
因为first page只有10个index条目因此只能指向10个data page,那么如果大字段的数据在170K左右的时候就需要分配index page来指向新的data page了,这里涉及到
2个链表,一个是大字段frist page和index page之间的链表以及各个index条目之间的链表。以t1表的col b为列子如下lob数据结构如上图









MTR是一个修改redo和page加锁的最小事务,只有当MTR提交才会将其修改的所有redo日志写入到redo buffer中,然后顺序写入到redo file.
在做lob.purge的时候使用了3个MTR,其中frist page的MTR就是上层修改的MTR_TREE,而删除data page和index page时候使用了本地新建的一个MTR,而
关键代码如下
BtrContext::free_externally_stored_fields
   ->for (ulint i = 0; i < n_fields; i++) { //释放每个大字段
       ->DeleteContext ctx(*this, field_ref, i, rollback);
          使用上线文的MTR初始化
      ->lob::purge
          ->first.set_mtr(ctx->get_mtr());  //本处MTR是上线文中的上层MTR
            ->first.destroy() 
            ->free_all_data_pages();  //释放所有的lob data page,本函数使用单独MTR1
            ->free_all_index_pages(); //释放所有node t page,本函数使用单独MTR2
            ->dealloc();              //释放frist page,使用上层MTR_TREE
            
这里我们简单debug每个字段释放额MTR是否相当,这里使用t1表,有2个大字段b和c

(gdb) p ctx->m_mtr
$13 = (mtr_t *) 0x7fff86ffc2f0
(gdb) n
1158          if (need_recalc()) {
(gdb) n
1150      for (ulint i = 0; i < n_fields; i++) {
(gdb) n
1151        if (rec_offs_nth_extern(m_offsets, i)) {
(gdb) n
1152          byte *field_ref = btr_rec_get_field_ref(m_rec, m_offsets, i);
(gdb) n
1154          DeleteContext ctx(*this, field_ref, i, rollback);
(gdb) n
1156          upd_field_t *uf = nullptr;
(gdb) p ctx->m_field_no
$14 = 5
(gdb) p ctx->m_mtr
$15 = (mtr_t *) 0x7fff86ffc2f0

我们看到mtr_t的地址都是一样的因此是同一个mtr,也就是这里的MTR_TREE,



还是表t1为列子中有2个lob字段,分别为col b ,col c   ,我们写出释放lob的过程为
            
col b的大字段清理
   单独MTR1   --->drop data page ---> COMMIT
   单独MTR2  ---->drop index page---> COMMIT
 
   MTR_TREE---> DROP FRIST PAGE                  (假设这个MTR 导致了extent 从满到半满 ,则会增加not_full_n_used为A 时间点T1  )

col c的大字段清理
 
   单独MTR 3  --->drop data page---> COMMIT       (假设这个MTR 导致了extent 从满到半满 ,则会增加not_full_n_used为B 时间点T2)
   单独MTR 4  ---->drop index page ---> COMMIT   (假设这个MTR 导致了extent 从满到半满 ,则会增加not_full_n_used为C 时间点T3)
   
   MTR_TREE---> DROP FRIST PAGE    
   
MTR_TREE COMMIT

这个时候正确的not_full_n_used为C,且C>B>A,

因为MTR commit redo日志才写盘,因此 时间点T1的MTR日志也就是MTR_TREE最后才写入那么写入redo如下


not_full_n_used为B 时间点T2
not_full_n_used为C 时间点T3
not_full_n_used为A 时间点T1

回放的时候显示修改not_full_n_used为B,然后修改为C然后,修改A,因此not_full_n_used无故减小了,而每次释放page的时候,这个segment
的not_full_n_used应该减去1(当前未满extent使用的page数量),也就是not_full_n_used -1,由于not_full_n_used减小了,那么在某个时刻它应该为>0的但是它已经为0了,因此断言crash,
在coredump中也可以看到这个值为0。这个BUG的概率还是很低的,需要满足
1、需要2个或者2个以上的lob字段
2、需要满足上面的条件在COL1 drop frist page的时候刚好一个extent从满到半满
3、COL2的后续的drop data page或drop index page也出现了一个extent从满到半满的情况
4、刚好数据没有刷脏,需要这段redo做恢复。

然后刚好需要crash recovery 用到这部分redo,xtrabackup备份也是crash recvoery。

因为first page很少,因为是第一个类似字典page因此很难出现2的情况。然后刚好需要crash recovery 用到这部分redo,xtrabackup备份也是crash recovery。

最后由于lob的extent和btree的extent在同一个extent混用,因为一个extent包含
64个page,有些page可能是lob的有些可能是btree的,因此这个损坏对btree也有影响,证明如下
current read blocks is : 1174 --This Block is data blocks( index pages)!
current read blocks is : 1175 --This frist Block is uncompressed LOB!
current read blocks is : 1176 --This Block is uncompressed LOB!

因为一个extent 64个page 这里 1174和1175是lob page而1176是btree page,他们只可能在 1152(18*64) -1216(19*64) 这个extent中,因此extent是混用的。

对当前版本的备份可用性做一些恢复测试,避免不可用的情况。

相关文章

网友评论

      本文标题:MySQL:8.0 新的LOB字段组织方式(BUG)

      本文链接:https://www.haomeiwen.com/subject/kghqujtx.html