简单记录,供参考
一、总体格式
最近在分析一个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是混用的。
七、总体格式

八、具体分配过程参考代码
我这里只是看了下分配过程,但是涉及到更多的多版本管理并没有去学习,分配的大概如下,供参考
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是混用的。
对当前版本的备份可用性做一些恢复测试,避免不可用的情况。
网友评论