概述
InnoDB是MySQL的默认存储引擎,以其卓越的事务处理能力、ACID特性支持和高并发性能而著称。InnoDB的核心组件和关键技术实现,揭示其高性能的技术奥秘。
1. InnoDB整体架构
1.1 InnoDB设计原则
InnoDB存储引擎遵循以下核心设计原则:
- ACID保证:完整的事务特性支持
- 高并发:多版本并发控制(MVCC)和行级锁定
- 崩溃恢复:通过redo/undo日志确保数据完整性
- 高性能:智能缓冲池和B+树索引结构
1.2 InnoDB架构全景图
graph TB
subgraph "InnoDB存储引擎架构"
subgraph "SQL层接口"
HandlerAPI[Handler API]
HAInterface[ha_innobase类]
SQLCommands[SQL命令处理]
end
subgraph "InnoDB内核层"
subgraph "事务子系统"
TrxSys[事务系统 trx0trx]
MVCC[多版本控制 read0read]
Isolation[隔离级别管理]
Deadlock[死锁检测 lock0wait]
end
subgraph "锁子系统"
LockSys[锁系统 lock0lock]
TableLock[表锁管理]
RowLock[行锁管理]
IntentionLock[意向锁]
LockQueue[锁等待队列]
end
subgraph "索引子系统"
BTree[B+树 btr0btr]
Cursor[B+树游标 btr0cur]
Search[B+树搜索 btr0sea]
Page[页面管理 page0page]
Record[记录格式 rem0rec]
end
subgraph "缓冲池子系统"
BufPool[缓冲池 buf0buf]
BufLRU[LRU管理 buf0lru]
BufFlush[刷新控制 buf0flu]
BufRead[预读机制 buf0rea]
PageHash[页面哈希表]
end
subgraph "日志子系统"
RedoLog[Redo日志 log0log]
UndoLog[Undo日志 trx0undo]
LogWriter[日志写入器]
LogRecovery[崩溃恢复 log0recv]
LogArchive[日志归档]
end
subgraph "文件子系统"
FileSpace[表空间 fil0fil]
SystemTS[系统表空间]
FileTS[独立表空间]
TempTS[临时表空间]
UndoTS[Undo表空间]
end
end
subgraph "存储层"
subgraph "系统表"
DataDict[数据字典表]
TrxSysTable[事务系统表]
UndoTables[Undo段表]
end
subgraph "用户数据"
UserTables[用户表数据]
UserIndexes[用户索引数据]
BlobData[BLOB数据页]
end
subgraph "文件系统"
IBDFiles[.ibd数据文件]
IBLogFiles[ib_logfile日志文件]
IBTmp[ibtmp临时文件]
IBUndo[undo日志文件]
end
end
subgraph "后台线程"
MasterThread[主线程]
IOThread[I/O线程]
PurgeThread[清理线程]
PageCleanerThread[页清理线程]
end
end
%% 连接关系
HandlerAPI --> HAInterface
HAInterface --> SQLCommands
SQLCommands --> TrxSys
SQLCommands --> LockSys
SQLCommands --> BTree
SQLCommands --> BufPool
TrxSys --> MVCC
MVCC --> Isolation
TrxSys --> Deadlock
LockSys --> TableLock
LockSys --> RowLock
RowLock --> IntentionLock
RowLock --> LockQueue
BTree --> Cursor
BTree --> Search
BTree --> Page
Page --> Record
BufPool --> BufLRU
BufPool --> BufFlush
BufPool --> BufRead
BufPool --> PageHash
RedoLog --> LogWriter
UndoLog --> LogRecovery
LogWriter --> LogArchive
FileSpace --> SystemTS
FileSpace --> FileTS
FileSpace --> TempTS
FileSpace --> UndoTS
BTree --> UserTables
BTree --> UserIndexes
Page --> BlobData
BufPool --> IBDFiles
RedoLog --> IBLogFiles
FileSpace --> IBTmp
UndoLog --> IBUndo
MasterThread --> IOThread
MasterThread --> PurgeThread
MasterThread --> PageCleanerThread
style BTree fill:#e1f5fe
style BufPool fill:#f3e5f5
style LockSys fill:#e8f5e8
style TrxSys fill:#fff3e0
style RedoLog fill:#fce4ec
2. B+树索引结构深度解析
2.1 B+树核心数据结构
/**
* B+树索引的核心数据结构和操作接口
* InnoDB中所有数据都存储在B+树结构中
*/
// B+树页面类型定义
enum page_type_t {
FIL_PAGE_INDEX = 17855, ///< B+树叶子页面
FIL_PAGE_RTREE = 17854, ///< R树页面(空间索引)
FIL_PAGE_SDI = 17853, ///< 序列化数据字典页面
FIL_PAGE_TYPE_ALLOCATED = 0, ///< 新分配的页面
FIL_PAGE_UNDO_LOG = 2, ///< Undo日志页面
FIL_PAGE_INODE = 3, ///< 段信息节点页面
FIL_PAGE_IBUF_FREE_LIST = 4, ///< 插入缓冲空闲列表页面
FIL_PAGE_TYPE_SYS = 6, ///< 系统页面
FIL_PAGE_TYPE_TRX_SYS = 7, ///< 事务系统页面
FIL_PAGE_TYPE_FSP_HDR = 8, ///< 文件空间头页面
FIL_PAGE_TYPE_XDES = 9, ///< 扩展描述页面
FIL_PAGE_TYPE_BLOB = 10, ///< BLOB页面
FIL_PAGE_TYPE_ZBLOB = 11, ///< 压缩BLOB页面
FIL_PAGE_TYPE_ZBLOB2 = 12 ///< 压缩BLOB页面(新格式)
};
/**
* B+树索引页面头部结构
* 包含页面的元数据信息
*/
struct page_header_t {
// 页面通用头部(38字节)
uint32_t checksum; ///< 页面校验和
uint32_t page_no; ///< 页面编号
uint32_t prev_page; ///< 前一页面编号
uint32_t next_page; ///< 后一页面编号
uint64_t lsn; ///< 页面LSN(日志序列号)
uint16_t page_type; ///< 页面类型
uint64_t file_flush_lsn; ///< 文件刷新LSN
uint32_t space_id; ///< 表空间ID
// 索引页面专用头部(36字节)
uint16_t n_dir_slots; ///< 页目录槽数量
uint16_t heap_top; ///< 堆顶位置(空闲空间开始)
uint16_t n_heap; ///< 页面中记录数(包括伪记录)
uint16_t free; ///< 指向第一个空闲记录
uint16_t garbage; ///< 被删除记录占用的字节数
uint16_t last_insert; ///< 最后插入记录的位置
uint16_t direction; ///< 插入方向(PAGE_LEFT, PAGE_RIGHT等)
uint16_t n_direction; ///< 同一方向连续插入的记录数
uint16_t n_recs; ///< 用户记录数量
uint64_t max_trx_id; ///< 修改页面的最大事务ID
uint16_t level; ///< B+树层级(0表示叶子层)
uint64_t index_id; ///< 索引ID
};
/**
* B+树记录格式结构
* InnoDB支持多种记录格式:Redundant、Compact、Dynamic、Compressed
*/
struct record_header_t {
// Compact格式记录头(5字节)
uint8_t info_flags; ///< 记录信息标志
uint16_t n_owned; ///< 拥有的记录数(仅目录记录使用)
uint16_t heap_no; ///< 堆中的位置编号
uint8_t record_type; ///< 记录类型(0=普通,1=B+树节点指针,2=最小记录,3=最大记录)
uint16_t next_record; ///< 指向下一条记录的偏移量
};
/**
* B+树游标结构
* 用于在B+树中定位和操作记录
*/
class btr_cur_t {
public:
// B+树路径信息
struct btr_path_t {
page_no_t page_no; ///< 页面编号
uint16_t nth_rec; ///< 页面中的记录序号
} path_arr[BTR_MAX_LEVELS]; ///< 从根到叶的路径数组
dict_index_t* index; ///< 索引对象指针
page_cur_t page_cur; ///< 页面游标
purge_node_t* purge_node; ///< 清理节点
buf_block_t* left_block; ///< 左兄弟页面块
que_thr_t* thr; ///< 查询线程
// 用于乐观插入和更新的成员
uint16_t flag; ///< BTR_CUR_DELETE_MARK等标志
ulint tree_height; ///< B+树高度
ulint up_match; ///< 向上匹配的记录数
ulint up_bytes; ///< 向上匹配的字节数
ulint low_match; ///< 向下匹配的记录数
ulint low_bytes; ///< 向下匹配的字节数
public:
/**
* 构造函数:初始化B+树游标
*/
btr_cur_t() : index(nullptr), purge_node(nullptr),
left_block(nullptr), thr(nullptr),
flag(0), tree_height(0), up_match(0),
up_bytes(0), low_match(0), low_bytes(0) {}
/**
* 在B+树中搜索记录
* @param tuple 搜索键值
* @param mode 搜索模式
* @param latch_mode 锁定模式
* @param cursor 游标对象
* @param mtr 迷你事务
* @return 搜索结果状态
*/
dberr_t search_to_nth_level(const dtuple_t* tuple,
page_cur_mode_t mode,
ulint latch_mode,
mtr_t* mtr) {
// 1. 从根页面开始搜索
dict_index_t* index = this->index;
buf_block_t* block = nullptr;
page_t* page = nullptr;
// 获取根页面
page_no_t root_page_no = dict_index_get_page(index);
space_id_t space_id = dict_index_get_space(index);
block = buf_page_get(page_id_t(space_id, root_page_no),
dict_table_page_size(index->table),
latch_mode, mtr);
page = buf_block_get_frame(block);
// 2. 逐层向下搜索
uint32_t level = btr_page_get_level(page, mtr);
while (level > 0) {
// 在非叶子页面中搜索
page_cur_search_with_match(block, index, tuple, mode,
&up_match, &up_bytes,
&low_match, &low_bytes,
&page_cur);
// 获取子页面页号
const rec_t* node_ptr = page_cur_get_rec(&page_cur);
page_no_t child_page_no = btr_node_ptr_get_child_page_no(node_ptr,
dict_index_get_n_unique_in_tree(index));
// 记录路径信息
path_arr[tree_height].page_no = page_get_page_no(page);
path_arr[tree_height].nth_rec = page_rec_get_n_recs_before(node_ptr);
tree_height++;
// 移动到子页面
block = buf_page_get(page_id_t(space_id, child_page_no),
dict_table_page_size(index->table),
latch_mode, mtr);
page = buf_block_get_frame(block);
level = btr_page_get_level(page, mtr);
}
// 3. 在叶子页面中精确搜索
page_cur_search_with_match(block, index, tuple, mode,
&up_match, &up_bytes,
&low_match, &low_bytes,
&page_cur);
return DB_SUCCESS;
}
/**
* 插入记录到B+树
* @param tuple 要插入的记录
* @param n_ext 外部存储字段数
* @param mtr 迷你事务
* @return 插入结果
*/
dberr_t optimistic_insert(const dtuple_t* tuple,
ulint n_ext,
mtr_t* mtr) {
page_t* page = page_cur_get_page(&page_cur);
dict_index_t* index = this->index;
// 1. 检查页面是否有足够空间
ulint rec_size = rec_get_converted_size(index, tuple, n_ext);
ulint max_size = page_get_max_insert_size_after_reorganize(page, 1);
if (rec_size > max_size) {
return DB_FAIL; // 需要页面分裂,转为悲观插入
}
// 2. 检查是否违反唯一性约束
if (dict_index_is_unique(index)) {
if (row_ins_duplicate_error_in_clust(this, tuple, mtr)) {
return DB_DUPLICATE_KEY;
}
}
// 3. 执行插入操作
rec_t* rec = page_cur_tuple_insert(&page_cur, tuple, index,
n_ext, mtr);
if (rec == nullptr) {
return DB_FAIL;
}
// 4. 更新页面统计信息
btr_search_update_hash_on_insert(this);
return DB_SUCCESS;
}
/**
* 删除当前记录
* @param mtr 迷你事务
* @return 删除结果
*/
dberr_t optimistic_delete(mtr_t* mtr) {
buf_block_t* block = page_cur_get_block(&page_cur);
page_t* page = buf_block_get_frame(block);
// 1. 检查页面合并条件
if (page_get_n_recs(page) < 2) {
return DB_FAIL; // 需要页面合并,转为悲观删除
}
// 2. 执行删除标记
page_cur_delete_rec(&page_cur, index, mtr);
// 3. 更新自适应哈希索引
btr_search_update_hash_on_delete(this);
return DB_SUCCESS;
}
};
/**
* B+树分裂操作
* 当页面空间不足时,需要进行页面分裂
*/
class btr_page_split_t {
private:
dict_index_t* index; ///< 索引对象
buf_block_t* block; ///< 要分裂的页面
const rec_t* split_rec; ///< 分裂点记录
public:
/**
* 执行页面分裂
* @param cursor B+树游标
* @param tuple 触发分裂的记录
* @param mtr 迷你事务
* @return 分裂结果
*/
dberr_t execute_split(btr_cur_t* cursor,
const dtuple_t* tuple,
mtr_t* mtr) {
// 1. 选择分裂点
const rec_t* split_rec = choose_split_point(cursor, tuple);
if (split_rec == nullptr) {
return DB_CORRUPTION;
}
// 2. 分配新页面
buf_block_t* new_block = btr_page_alloc(index, 0, FSP_UP,
btr_page_get_level(page, mtr),
mtr);
if (new_block == nullptr) {
return DB_OUT_OF_FILE_SPACE;
}
// 3. 创建新页面
page_t* new_page = buf_block_get_frame(new_block);
btr_page_create(new_block, index,
btr_page_get_level(page, mtr), mtr);
// 4. 移动记录到新页面
move_records_to_new_page(split_rec, new_block, mtr);
// 5. 更新页面链接
update_page_links(new_block, mtr);
// 6. 插入节点指针到父页面
insert_node_pointer_to_parent(cursor, new_block, mtr);
return DB_SUCCESS;
}
private:
/**
* 选择最优分裂点
* 目标是使两个页面的空间利用率尽可能平衡
*/
const rec_t* choose_split_point(btr_cur_t* cursor, const dtuple_t* tuple) {
page_t* page = page_cur_get_page(&cursor->page_cur);
// 获取页面中间位置的记录
ulint total_recs = page_get_n_recs(page);
ulint split_point = total_recs / 2;
const rec_t* rec = page_get_infimum_rec(page);
for (ulint i = 0; i < split_point; i++) {
rec = page_rec_get_next_const(rec);
if (page_rec_is_supremum(rec)) {
break;
}
}
return rec;
}
/**
* 移动记录到新页面
*/
void move_records_to_new_page(const rec_t* split_rec,
buf_block_t* new_block,
mtr_t* mtr) {
page_t* page = buf_block_get_frame(block);
page_t* new_page = buf_block_get_frame(new_block);
// 从分裂点开始,将后续记录移动到新页面
const rec_t* rec = split_rec;
while (!page_rec_is_supremum(rec)) {
const rec_t* next_rec = page_rec_get_next_const(rec);
// 复制记录到新页面
rec_t* new_rec = page_cur_insert_rec_low(
page_get_supremum_rec(new_page),
index, rec, mtr);
// 从原页面删除记录
page_delete_rec(page, rec, index, mtr);
rec = next_rec;
}
}
/**
* 更新页面链接关系
*/
void update_page_links(buf_block_t* new_block, mtr_t* mtr) {
page_t* page = buf_block_get_frame(block);
page_t* new_page = buf_block_get_frame(new_block);
// 获取原页面的下一页
page_no_t next_page_no = btr_page_get_next(page, mtr);
// 设置新页面的前后链接
btr_page_set_prev(new_page, page_get_page_no(page), mtr);
btr_page_set_next(new_page, next_page_no, mtr);
// 更新原页面的后继
btr_page_set_next(page, page_get_page_no(new_page), mtr);
// 更新下一页面的前驱(如果存在)
if (next_page_no != FIL_NULL) {
buf_block_t* next_block = buf_page_get(
page_id_t(index->space, next_page_no),
dict_table_page_size(index->table),
RW_X_LATCH, mtr);
page_t* next_page = buf_block_get_frame(next_block);
btr_page_set_prev(next_page, page_get_page_no(new_page), mtr);
}
}
};
3. 缓冲池管理系统深度解析
3.1 缓冲池核心数据结构
/**
* InnoDB缓冲池管理系统
* 负责页面的缓存、替换和刷新策略
*/
/**
* 缓冲池实例结构
* InnoDB支持多个缓冲池实例以提高并发性
*/
struct buf_pool_t {
// 缓冲池基本信息
ulint instance_no; ///< 缓冲池实例编号
ulint curr_pool_size; ///< 当前缓冲池大小(页数)
ulint LRU_old_ratio; ///< LRU老化区域比例
// 页面管理
buf_block_t* blocks; ///< 缓冲块数组
byte* frame_mem; ///< 页面帧内存区域
ulint n_pend_reads; ///< 挂起的读操作数
time_t last_activity; ///< 最后活动时间
// LRU链表管理
UT_LIST_BASE_NODE_T(buf_page_t) LRU; ///< LRU链表
buf_page_t* LRU_old; ///< LRU老化区域起始指针
ulint LRU_old_len; ///< LRU老化区域长度
// 空闲链表
UT_LIST_BASE_NODE_T(buf_page_t) free; ///< 空闲页面链表
// 刷新链表
UT_LIST_BASE_NODE_T(buf_page_t) flush_list; ///< 脏页刷新链表
ulint n_flush_LRU; ///< LRU刷新中的页面数
ulint n_flush_list; ///< 刷新链表中的页面数
// 页面哈希表
hash_table_t* page_hash; ///< 页面哈希表
ulint page_hash_n_cells; ///< 哈希表槽数
// 并发控制
BufListMutex LRU_list_mutex; ///< LRU链表互斥锁
BufListMutex free_list_mutex; ///< 空闲链表互斥锁
BufFlushListMutex flush_list_mutex; ///< 刷新链表互斥锁
rw_lock_t page_hash_latch; ///< 页面哈希表读写锁
// 统计信息
BufPoolStat stat; ///< 缓冲池统计信息
buf_pool_info_t old_stat; ///< 历史统计信息
// 压缩页面管理
UT_LIST_BASE_NODE_T(buf_buddy_free_t) zip_free[BUF_BUDDY_SIZES_MAX];
public:
/**
* 初始化缓冲池实例
* @param instance_no 实例编号
* @param pool_size 缓冲池大小
* @return 初始化结果
*/
dberr_t init(ulint instance_no, ulint pool_size) {
this->instance_no = instance_no;
this->curr_pool_size = pool_size;
this->LRU_old_ratio = BUF_LRU_OLD_RATIO_DIV * BUF_LRU_OLD_THRESHOLD / 100;
// 1. 分配页面帧内存
ulint chunk_size = pool_size * UNIV_PAGE_SIZE;
frame_mem = static_cast<byte*>(ut_malloc_nokey(chunk_size));
if (frame_mem == nullptr) {
return DB_OUT_OF_MEMORY;
}
// 2. 分配缓冲块数组
blocks = static_cast<buf_block_t*>(
ut_zalloc_nokey(pool_size * sizeof(buf_block_t))
);
if (blocks == nullptr) {
ut_free(frame_mem);
return DB_OUT_OF_MEMORY;
}
// 3. 初始化页面哈希表
page_hash_n_cells = 2 * pool_size;
page_hash = hash_create(page_hash_n_cells);
if (page_hash == nullptr) {
ut_free(blocks);
ut_free(frame_mem);
return DB_OUT_OF_MEMORY;
}
// 4. 初始化互斥锁
mutex_create(LATCH_ID_BUF_LRU_LIST, &LRU_list_mutex);
mutex_create(LATCH_ID_BUF_FREE_LIST, &free_list_mutex);
mutex_create(LATCH_ID_BUF_FLUSH_LIST, &flush_list_mutex);
rw_lock_create(buf_pool_page_hash_key, &page_hash_latch, SYNC_BUF_PAGE_HASH);
// 5. 初始化缓冲块
for (ulint i = 0; i < pool_size; i++) {
buf_block_t* block = &blocks[i];
block->frame = frame_mem + i * UNIV_PAGE_SIZE;
block->page.buf_pool_instance = this;
block->page.state = BUF_BLOCK_NOT_USED;
// 添加到空闲链表
UT_LIST_ADD_LAST(free, &block->page);
}
// 6. 初始化统计信息
memset(&stat, 0, sizeof(stat));
memset(&old_stat, 0, sizeof(old_stat));
return DB_SUCCESS;
}
/**
* 从缓冲池获取页面
* @param page_id 页面ID
* @param page_size 页面大小
* @param rw_latch 读写锁类型
* @param guess 页面块猜测
* @param mode 获取模式
* @param mtr 迷你事务
* @return 页面块指针
*/
buf_block_t* get_page(const page_id_t& page_id,
const page_size_t& page_size,
ulint rw_latch,
buf_block_t* guess,
Page_fetch mode,
mtr_t* mtr) {
buf_block_t* block;
bool accessed;
// 1. 在页面哈希表中查找
rw_lock_s_lock(&page_hash_latch);
block = reinterpret_cast<buf_block_t*>(
hash_get_first(page_hash,
buf_page_address_fold(page_id.space(), page_id.page_no()))
);
// 遍历哈希冲突链
while (block != nullptr) {
if (block->page.id.equals(page_id) &&
block->page.size.equals(page_size)) {
break;
}
block = reinterpret_cast<buf_block_t*>(
hash_get_next(page_hash, &block->page.hash)
);
}
if (block != nullptr) {
// 2. 页面在缓冲池中,增加固定计数
buf_block_buf_fix_inc(block, mtr);
accessed = buf_page_is_accessed(&block->page);
rw_lock_s_unlock(&page_hash_latch);
// 3. 获取页面锁
bool success = buf_page_get_known_nowait(block, rw_latch,
BUF_KEEP_OLD, mtr);
if (!success) {
buf_block_buf_fix_dec(block);
return nullptr;
}
// 4. 更新访问时间和LRU位置
if (mode != Page_fetch::PEEK_IF_IN_POOL) {
buf_page_make_young_if_needed(&block->page);
}
// 5. 更新统计信息
++stat.n_page_gets;
if (accessed) {
++stat.n_pages_not_made_young;
} else {
++stat.n_pages_made_young;
}
return block;
}
rw_lock_s_unlock(&page_hash_latch);
// 6. 页面不在缓冲池中,需要从磁盘读取
if (mode == Page_fetch::IF_IN_POOL ||
mode == Page_fetch::PEEK_IF_IN_POOL) {
return nullptr; // 仅在池中获取,返回空
}
return read_page_from_disk(page_id, page_size, rw_latch, mode, mtr);
}
/**
* LRU页面替换算法
* @param n_to_scan 扫描页面数
* @return 释放的页面数
*/
ulint LRU_scan_and_free_block(ulint n_to_scan) {
ulint freed = 0;
mutex_enter(&LRU_list_mutex);
buf_page_t* bpage = UT_LIST_GET_LAST(LRU);
// 从LRU链表尾部开始扫描
for (ulint scanned = 0; scanned < n_to_scan && bpage != nullptr; scanned++) {
buf_page_t* prev_bpage = UT_LIST_GET_PREV(LRU, bpage);
// 检查页面是否可以被驱逐
if (buf_page_can_relocate(bpage)) {
if (buf_page_get_state(bpage) == BUF_BLOCK_FILE_PAGE) {
buf_block_t* block = reinterpret_cast<buf_block_t*>(bpage);
// 尝试获取页面的X锁
if (rw_lock_x_lock_nowait(&block->lock)) {
// 成功获取锁,可以驱逐此页面
bool success = buf_LRU_free_page(bpage, true);
if (success) {
freed++;
}
rw_lock_x_unlock(&block->lock);
}
}
}
bpage = prev_bpage;
}
mutex_exit(&LRU_list_mutex);
return freed;
}
/**
* 脏页刷新管理
* @param lsn_limit LSN限制
* @param n_to_flush 要刷新的页面数
* @return 实际刷新的页面数
*/
ulint flush_dirty_pages(lsn_t lsn_limit, ulint n_to_flush) {
ulint flushed = 0;
mutex_enter(&flush_list_mutex);
buf_page_t* bpage = UT_LIST_GET_LAST(flush_list);
// 从刷新链表尾部(最旧LSN)开始
while (bpage != nullptr && flushed < n_to_flush) {
if (bpage->oldest_modification >= lsn_limit) {
break; // LSN太新,不需要刷新
}
buf_page_t* prev_bpage = UT_LIST_GET_PREV(flush_list, bpage);
// 检查页面是否可以刷新
if (buf_flush_ready_for_flush(bpage, BUF_FLUSH_LIST)) {
// 异步刷新页面到磁盘
bool success = buf_flush_page(bpage, BUF_FLUSH_LIST, true);
if (success) {
flushed++;
}
}
bpage = prev_bpage;
}
mutex_exit(&flush_list_mutex);
return flushed;
}
private:
/**
* 从磁盘读取页面到缓冲池
*/
buf_block_t* read_page_from_disk(const page_id_t& page_id,
const page_size_t& page_size,
ulint rw_latch,
Page_fetch mode,
mtr_t* mtr) {
// 1. 获取空闲页面块
buf_block_t* block = get_free_block();
if (block == nullptr) {
return nullptr; // 缓冲池已满且无法驱逐页面
}
// 2. 初始化页面信息
block->page.id = page_id;
block->page.size = page_size;
block->page.state = BUF_BLOCK_FILE_PAGE;
block->page.access_time = time(nullptr);
// 3. 将页面添加到哈希表
rw_lock_x_lock(&page_hash_latch);
hash_insert(page_hash,
buf_page_address_fold(page_id.space(), page_id.page_no()),
&block->page);
rw_lock_x_unlock(&page_hash_latch);
// 4. 启动异步I/O读取页面
fil_io_t io_type = (mode == Page_fetch::SCAN) ?
OS_FILE_READ | OS_AIO_SIMULATED_WAKE_LATER :
OS_FILE_READ;
dberr_t err = fil_io(io_type, true, page_id, page_size,
0, page_size.physical(),
block->frame, nullptr);
if (err != DB_SUCCESS) {
// 读取失败,清理资源
remove_from_hash_and_free(block);
return nullptr;
}
// 5. 获取页面锁并添加到LRU
buf_page_get_known_nowait(block, rw_latch, BUF_MAKE_YOUNG, mtr);
buf_page_add_to_LRU(&block->page);
// 6. 更新统计信息
++stat.n_pages_read;
return block;
}
/**
* 获取空闲页面块
*/
buf_block_t* get_free_block() {
buf_block_t* block;
// 1. 尝试从空闲链表获取
mutex_enter(&free_list_mutex);
if (!UT_LIST_GET_LEN(free) == 0) {
buf_page_t* bpage = UT_LIST_GET_FIRST(free);
UT_LIST_REMOVE(free, bpage);
block = reinterpret_cast<buf_block_t*>(bpage);
mutex_exit(&free_list_mutex);
return block;
}
mutex_exit(&free_list_mutex);
// 2. 空闲链表为空,尝试LRU驱逐
ulint freed = LRU_scan_and_free_block(BUF_LRU_SEARCH_SCAN_THRESHOLD);
if (freed > 0) {
return get_free_block(); // 递归重试
}
// 3. 无法获取空闲页面
return nullptr;
}
};
3.2 缓冲池读写时序图
sequenceDiagram
participant Executor as 执行器
participant Handler as 存储引擎接口
participant InnoDB as InnoDB内核
participant BufPool as 缓冲池
participant FilIO as 文件I/O
participant FileSystem as 文件系统
Note over Executor,FileSystem: 页面读取(命中/未命中)
Executor->>Handler: 1. 读取记录
Handler->>InnoDB: 2. 获取页面
InnoDB->>BufPool: 3. 在缓冲池查找页面
alt 命中
BufPool-->>InnoDB: 4a. 返回页面(加锁/固定)
else 未命中
BufPool-->>InnoDB: 4b. 未命中
InnoDB->>BufPool: 5. 申请空闲块或LRU驱逐
InnoDB->>FilIO: 6. 发起异步I/O读取
FilIO->>FileSystem: 7. 读取页面
FileSystem-->>FilIO: 8. 返回页面数据
FilIO-->>InnoDB: 9. I/O完成回调
InnoDB->>BufPool: 10. 将页面插入哈希与LRU
end
InnoDB-->>Handler: 11. 返回页面帧
Handler-->>Executor: 12. 返回记录
sequenceDiagram
participant PageCleaner as 页清理线程
participant InnoDB as InnoDB内核
participant BufPool as 缓冲池
participant RedoLog as Redo日志
participant FilIO as 文件I/O
participant FileSystem as 文件系统
Note over PageCleaner,FileSystem: 脏页刷新(flush list)
PageCleaner->>BufPool: 1. 选择可刷新的脏页
InnoDB->>RedoLog: 2. 确保LSN持久(write+fsync)
RedoLog-->>InnoDB: 3. LSN满足刷写条件
InnoDB->>FilIO: 4. 发起页面写入
FilIO->>FileSystem: 5. 写入页面
FileSystem-->>FilIO: 6. 写入完成
FilIO-->>InnoDB: 7. I/O完成回调
InnoDB->>BufPool: 8. 标记页面为干净并从flush list更新位置
4. 锁系统深度解析
4.1 锁系统核心数据结构
/**
* InnoDB锁系统实现
* 支持表锁、行锁、意向锁和间隙锁
*/
/**
* 锁类型和模式定义
*/
enum lock_mode {
LOCK_IS = 0, ///< 意向共享锁
LOCK_IX = 1, ///< 意向排他锁
LOCK_S = 2, ///< 共享锁
LOCK_X = 3, ///< 排他锁
LOCK_AUTO_INC = 4, ///< 自增锁
LOCK_NONE = 5, ///< 无锁(用于锁升级)
LOCK_NUM = 6, ///< 锁模式数量
LOCK_NONE_UNSET = 255 ///< 未设置锁模式
};
/**
* 锁类型标志
*/
enum lock_type_flags {
LOCK_TABLE = 16, ///< 表锁
LOCK_REC = 32, ///< 行锁
LOCK_TYPE_MASK = 240, ///< 锁类型掩码
// 行锁子类型
LOCK_WAIT = 256, ///< 等待锁
LOCK_GAP = 512, ///< 间隙锁
LOCK_REC_NOT_GAP = 1024, ///< 记录锁(非间隙)
LOCK_INSERT_INTENTION = 2048 ///< 插入意向锁
};
/**
* 锁结构体
* 表示单个锁实例
*/
struct lock_t {
trx_t* trx; ///< 拥有此锁的事务
UT_LIST_NODE_T(lock_t) trx_locks; ///< 事务锁链表节点
dict_table_t* table; ///< 锁定的表
dict_index_t* index; ///< 锁定的索引(行锁时使用)
union {
// 表锁信息
struct {
UT_LIST_NODE_T(lock_t) locks; ///< 表锁链表节点
} tab_lock;
// 行锁信息
struct {
space_id_t space; ///< 表空间ID
page_no_t page_no; ///< 页面编号
ulint n_bits; ///< 位图大小
UT_LIST_NODE_T(lock_t) locks; ///< 页面锁链表节点
} rec_lock;
} un_member;
uint32_t type_mode; ///< 锁类型和模式
// 行锁位图(紧跟在结构体后面)
// byte bitmap[variable_size];
public:
/**
* 检查锁模式兼容性
* @param mode1 锁模式1
* @param mode2 锁模式2
* @return true表示兼容,false表示冲突
*/
static bool modes_compatible(ulint mode1, ulint mode2) {
// 锁兼容性矩阵
static const bool compat_matrix[LOCK_NUM][LOCK_NUM] = {
// IS IX S X AI
/*IS*/ {true, true, true, false, true},
/*IX*/ {true, true, false, false, true},
/*S*/ {true, false, true, false, false},
/*X*/ {false, false, false, false, false},
/*AI*/ {true, true, false, false, false}
};
if (mode1 >= LOCK_NUM || mode2 >= LOCK_NUM) {
return false;
}
return compat_matrix[mode1][mode2];
}
/**
* 检查是否为间隙锁
*/
bool is_gap_lock() const {
return (type_mode & LOCK_GAP) != 0;
}
/**
* 检查是否为记录锁
*/
bool is_record_lock() const {
return (type_mode & LOCK_REC_NOT_GAP) != 0;
}
/**
* 检查是否为等待锁
*/
bool is_waiting() const {
return (type_mode & LOCK_WAIT) != 0;
}
/**
* 获取锁模式
*/
ulint mode() const {
return type_mode & LOCK_MODE_MASK;
}
/**
* 获取锁类型
*/
ulint type() const {
return type_mode & LOCK_TYPE_MASK;
}
/**
* 设置位图中的位
* @param heap_no 堆编号
*/
void set_bitmap_bit(ulint heap_no) {
ut_ad(type() & LOCK_REC);
byte* bitmap = reinterpret_cast<byte*>(this + 1);
ulint byte_index = heap_no / 8;
ulint bit_index = heap_no % 8;
bitmap[byte_index] |= (1 << bit_index);
}
/**
* 检查位图中的位
* @param heap_no 堆编号
* @return true表示位已设置
*/
bool test_bitmap_bit(ulint heap_no) const {
ut_ad(type() & LOCK_REC);
const byte* bitmap = reinterpret_cast<const byte*>(this + 1);
ulint byte_index = heap_no / 8;
ulint bit_index = heap_no % 8;
return (bitmap[byte_index] & (1 << bit_index)) != 0;
}
};
/**
* 锁系统管理器
* 管理全局锁信息和死锁检测
*/
class lock_sys_t {
private:
// 锁哈希表
hash_table_t* rec_hash; ///< 行锁哈希表
hash_table_t* table_hash; ///< 表锁哈希表
// 等待管理
srv_slot_t* waiting_threads; ///< 等待线程数组
srv_slot_t* last_slot; ///< 最后一个槽位
// 死锁检测
ib_mutex_t wait_mutex; ///< 等待队列互斥锁
lock_t* wait_lock; ///< 当前等待的锁
bool deadlock_found; ///< 是否发现死锁
// 统计信息
ulint n_lock_max_wait_time; ///< 最大等待时间
ulint n_lock_timeouts; ///< 锁超时次数
public:
/**
* 初始化锁系统
*/
void init() {
// 1. 创建锁哈希表
rec_hash = hash_create(LOCK_HASH_SIZE);
table_hash = hash_create(LOCK_HASH_SIZE);
// 2. 初始化等待线程管理
waiting_threads = static_cast<srv_slot_t*>(
ut_zalloc_nokey(OS_THREAD_MAX_N * sizeof(srv_slot_t))
);
last_slot = waiting_threads;
// 3. 初始化互斥锁
mutex_create(LATCH_ID_LOCK_WAIT, &wait_mutex);
// 4. 初始化统计信息
n_lock_max_wait_time = 0;
n_lock_timeouts = 0;
deadlock_found = false;
}
/**
* 请求表锁
* @param table 表对象
* @param mode 锁模式
* @param trx 事务对象
* @return 锁请求结果
*/
dberr_t lock_table(dict_table_t* table, ulint mode, trx_t* trx) {
// 1. 检查是否已经持有兼容的锁
lock_t* existing_lock = table_has_lock(table, trx);
if (existing_lock != nullptr) {
if (modes_compatible(existing_lock->mode(), mode)) {
return DB_SUCCESS; // 已有兼容锁
}
// 需要锁升级
return upgrade_table_lock(existing_lock, mode);
}
// 2. 检查与其他事务的锁冲突
if (table_lock_conflicts(table, mode, trx)) {
// 有冲突,需要等待
return create_waiting_table_lock(table, mode, trx);
}
// 3. 立即授予锁
lock_t* lock = create_table_lock(table, mode, trx, false);
if (lock == nullptr) {
return DB_OUT_OF_MEMORY;
}
return DB_SUCCESS;
}
/**
* 请求行锁
* @param index 索引对象
* @param block 页面块
* @param heap_no 记录堆编号
* @param mode 锁模式
* @param type 锁类型(间隙锁等)
* @param trx 事务对象
* @return 锁请求结果
*/
dberr_t lock_record(dict_index_t* index,
buf_block_t* block,
ulint heap_no,
ulint mode,
ulint type,
trx_t* trx) {
page_no_t page_no = block->page.id.page_no();
space_id_t space_id = block->page.id.space();
// 1. 检查是否已经持有锁
lock_t* existing_lock = rec_has_lock(space_id, page_no, heap_no, trx);
if (existing_lock != nullptr &&
lock_mode_stronger_or_eq(existing_lock->mode(), mode)) {
return DB_SUCCESS;
}
// 2. 检查锁冲突
if (rec_lock_conflicts(space_id, page_no, heap_no, mode, type, trx)) {
// 有冲突,创建等待锁
return create_waiting_record_lock(index, block, heap_no,
mode, type, trx);
}
// 3. 立即授予锁
lock_t* lock = create_record_lock(index, block, heap_no,
mode, type, trx, false);
if (lock == nullptr) {
return DB_OUT_OF_MEMORY;
}
return DB_SUCCESS;
}
/**
* 死锁检测算法
* 使用深度优先搜索检测等待图中的环
* @param trx 起始事务
* @return true表示发现死锁
*/
bool deadlock_check(trx_t* trx) {
mutex_enter(&wait_mutex);
// 初始化搜索状态
deadlock_found = false;
std::set<trx_t*> visited;
std::set<trx_t*> path;
bool result = deadlock_check_recursive(trx, visited, path);
mutex_exit(&wait_mutex);
return result;
}
private:
/**
* 递归死锁检测
*/
bool deadlock_check_recursive(trx_t* trx,
std::set<trx_t*>& visited,
std::set<trx_t*>& path) {
// 检查是否已访问
if (visited.find(trx) != visited.end()) {
return false;
}
// 检查是否在当前路径中(发现环)
if (path.find(trx) != path.end()) {
deadlock_found = true;
return true;
}
visited.insert(trx);
path.insert(trx);
// 获取事务等待的锁
lock_t* wait_lock = trx->lock.wait_lock;
if (wait_lock == nullptr) {
path.erase(trx);
return false;
}
// 遍历持有冲突锁的事务
if (wait_lock->type() & LOCK_TABLE) {
// 表锁冲突检查
dict_table_t* table = wait_lock->table;
for (lock_t* lock = UT_LIST_GET_FIRST(table->locks);
lock != nullptr;
lock = UT_LIST_GET_NEXT(locks, lock)) {
if (lock->trx != trx &&
!modes_compatible(wait_lock->mode(), lock->mode())) {
if (deadlock_check_recursive(lock->trx, visited, path)) {
return true;
}
}
}
} else {
// 行锁冲突检查
ulint space_id = wait_lock->un_member.rec_lock.space;
ulint page_no = wait_lock->un_member.rec_lock.page_no;
lock_t* lock = lock_rec_get_first_on_page_addr(space_id, page_no);
while (lock != nullptr) {
if (lock->trx != trx && rec_locks_conflict(wait_lock, lock)) {
if (deadlock_check_recursive(lock->trx, visited, path)) {
return true;
}
}
lock = lock_rec_get_next_on_page(lock);
}
}
path.erase(trx);
return false;
}
/**
* 创建等待锁并处理死锁
*/
dberr_t create_waiting_record_lock(dict_index_t* index,
buf_block_t* block,
ulint heap_no,
ulint mode,
ulint type,
trx_t* trx) {
// 1. 创建等待锁
lock_t* lock = create_record_lock(index, block, heap_no,
mode, type | LOCK_WAIT, trx, true);
if (lock == nullptr) {
return DB_OUT_OF_MEMORY;
}
// 2. 设置事务等待状态
trx->lock.wait_lock = lock;
trx->lock.wait_started = time(nullptr);
// 3. 死锁检测
if (deadlock_check(trx)) {
// 发现死锁,回滚当前事务
lock_cancel_waiting_and_release(lock);
return DB_DEADLOCK;
}
// 4. 等待锁获得
return wait_for_lock(lock, trx);
}
/**
* 等待锁获得
*/
dberr_t wait_for_lock(lock_t* lock, trx_t* trx) {
ulint wait_time = 0;
const ulint max_wait_time = trx->mysql_thd ?
thd_lock_wait_timeout(trx->mysql_thd) :
innodb_lock_wait_timeout;
while (lock->is_waiting()) {
// 检查超时
if (wait_time >= max_wait_time) {
lock_cancel_waiting_and_release(lock);
++n_lock_timeouts;
return DB_LOCK_WAIT_TIMEOUT;
}
// 休眠等待
os_thread_sleep(100000); // 100ms
wait_time += 100;
// 检查事务是否被杀死
if (trx_is_interrupted(trx)) {
lock_cancel_waiting_and_release(lock);
return DB_INTERRUPTED;
}
}
return DB_SUCCESS;
}
};
5. 事务系统深度解析
5.1 MVCC多版本并发控制
/**
* InnoDB多版本并发控制(MVCC)实现
* 通过Read View机制实现非锁定一致性读
*/
/**
* Read View结构
* 表示事务开始时的系统快照
*/
class ReadView {
private:
trx_id_t m_low_limit_id; ///< 视图创建时的最大事务ID + 1
trx_id_t m_up_limit_id; ///< 视图创建时最小活跃事务ID
trx_id_t m_creator_trx_id; ///< 创建此视图的事务ID
trx_ids_t m_ids; ///< 视图创建时的活跃事务ID数组
m_closed; ///< 视图是否已关闭
public:
/**
* 创建Read View快照
* @param trx 创建视图的事务
*/
void open(trx_t* trx) {
m_creator_trx_id = trx->id;
m_closed = false;
// 获取事务系统锁以创建一致性快照
trx_sys_mutex_enter();
// 设置上限:最小活跃事务ID
trx_t* trx_iter = UT_LIST_GET_FIRST(trx_sys->rw_trx_list);
if (trx_iter != nullptr) {
m_up_limit_id = trx_iter->id;
} else {
m_up_limit_id = trx_sys->max_trx_id;
}
// 设置下限:下一个事务ID
m_low_limit_id = trx_sys->max_trx_id;
// 复制活跃事务列表
m_ids.clear();
for (; trx_iter != nullptr;
trx_iter = UT_LIST_GET_NEXT(trx_list, trx_iter)) {
if (trx_iter->id != m_creator_trx_id) {
m_ids.push_back(trx_iter->id);
}
}
// 对活跃事务ID排序,便于二分查找
std::sort(m_ids.begin(), m_ids.end());
trx_sys_mutex_exit();
}
/**
* 判断事务对当前视图是否可见
* @param trx_id 事务ID
* @return true表示可见,false表示不可见
*/
bool changes_visible(trx_id_t trx_id) const {
ut_ad(!m_closed);
// 1. 自己的修改总是可见
if (trx_id == m_creator_trx_id) {
return true;
}
// 2. 事务ID小于最小活跃事务ID,必定已提交,可见
if (trx_id < m_up_limit_id) {
return true;
}
// 3. 事务ID大于等于下限,必定在视图创建后开始,不可见
if (trx_id >= m_low_limit_id) {
return false;
}
// 4. 在活跃事务列表中,不可见
bool found = std::binary_search(m_ids.begin(), m_ids.end(), trx_id);
return !found;
}
/**
* 关闭Read View
*/
void close() {
m_closed = true;
m_ids.clear();
}
/**
* 检查视图是否为空(没有活跃事务)
*/
bool empty() const {
return m_ids.empty();
}
};
/**
* 事务结构体
* 包含事务的所有状态信息
*/
struct trx_t {
// 事务标识
trx_id_t id; ///< 事务ID
trx_id_t no; ///< 提交序号
// 事务状态
trx_state_t state; ///< 事务状态
ReadView* read_view; ///< 一致性读视图
// 隔离级别
trx_isolation_level_t isolation_level; ///< 隔离级别
bool check_foreigns; ///< 是否检查外键
bool check_unique_secondary; ///< 是否检查唯一索引
// 锁信息
struct {
ulint n_active_thrs; ///< 活跃线程数
lock_t* wait_lock; ///< 等待的锁
bool was_chosen_as_deadlock_victim; ///< 是否被选为死锁牺牲品
time_t wait_started; ///< 等待开始时间
que_thr_t* wait_thr; ///< 等待的查询线程
mem_heap_t* lock_heap; ///< 锁内存堆
UT_LIST_BASE_NODE_T(lock_t) trx_locks; ///< 事务持有的锁列表
} lock;
// 回滚信息
trx_undo_t* insert_undo; ///< 插入undo日志
trx_undo_t* update_undo; ///< 更新undo日志
undo_no_t undo_no; ///< 下一个undo记录编号
space_id_t undo_rseg_space; ///< 回滚段表空间
// 修改计数
ib_uint64_t undo_no_arr[TRX_UNDO_N_SLOTS]; ///< undo编号数组
// MySQL连接信息
THD* mysql_thd; ///< MySQL线程句柄
const char* mysql_log_file_name; ///< binlog文件名
ib_int64_t mysql_log_offset; ///< binlog偏移量
// 事务链表节点
UT_LIST_NODE_T(trx_t) trx_list; ///< 全局事务链表
UT_LIST_NODE_T(trx_t) mysql_trx_list; ///< MySQL事务链表
public:
/**
* 开始事务
* @param thd MySQL线程句柄
* @param read_write 是否为读写事务
*/
void start(THD* thd, bool read_write) {
mysql_thd = thd;
// 分配事务ID(读写事务)
if (read_write) {
trx_sys_mutex_enter();
id = trx_sys_get_new_trx_id();
trx_sys_mutex_exit();
state = TRX_STATE_ACTIVE;
} else {
// 只读事务暂不分配ID
id = 0;
state = TRX_STATE_NOT_STARTED;
}
// 设置隔离级别
isolation_level = static_cast<trx_isolation_level_t>(
thd_get_trx_isolation(thd));
// 初始化锁相关结构
lock.n_active_thrs = 0;
lock.wait_lock = nullptr;
lock.was_chosen_as_deadlock_victim = false;
lock.lock_heap = mem_heap_create(1024);
UT_LIST_INIT(lock.trx_locks, &lock_t::trx_locks);
// 初始化undo信息
insert_undo = nullptr;
update_undo = nullptr;
undo_no = 0;
// 创建一致性读视图(如果需要)
if (isolation_level == TRX_ISO_REPEATABLE_READ ||
isolation_level == TRX_ISO_SERIALIZABLE) {
read_view = read_view_open_now(this);
}
}
/**
* 提交事务
* @param mtr 迷你事务
*/
void commit(mtr_t* mtr) {
// 1. 检查事务状态
if (state != TRX_STATE_ACTIVE) {
return; // 事务已提交或回滚
}
// 2. 释放所有锁
lock_release_all_locks(this);
// 3. 写入提交记录到undo日志
if (insert_undo != nullptr || update_undo != nullptr) {
trx_write_serialisation_history(this, mtr);
}
// 4. 更新事务状态
state = TRX_STATE_COMMITTED_IN_MEMORY;
// 5. 关闭一致性读视图
if (read_view != nullptr) {
read_view_close_for_trx(this, read_view);
read_view = nullptr;
}
// 6. 清理事务资源
cleanup_transaction_resources();
}
/**
* 回滚事务
* @param partial_rollback 是否为部分回滚
* @param savept_name 回滚点名称
*/
void rollback(bool partial_rollback = false,
const char* savept_name = nullptr) {
// 1. 应用undo日志进行回滚
if (insert_undo != nullptr) {
trx_rollback_insert_undo_recs(this);
}
if (update_undo != nullptr) {
trx_rollback_update_undo_recs(this);
}
// 2. 释放回滚中获得的锁
if (!partial_rollback) {
lock_release_all_locks(this);
}
// 3. 更新事务状态
if (!partial_rollback) {
state = TRX_STATE_ABORTED;
// 关闭一致性读视图
if (read_view != nullptr) {
read_view_close_for_trx(this, read_view);
read_view = nullptr;
}
// 清理资源
cleanup_transaction_resources();
}
}
/**
* 根据记录版本链判断记录可见性
* @param rec 记录指针
* @param index 索引对象
* @return true表示可见
*/
bool is_record_visible(const rec_t* rec, dict_index_t* index) const {
if (read_view == nullptr) {
return true; // READ UNCOMMITTED
}
// 获取记录的事务ID
trx_id_t trx_id = row_get_rec_trx_id(rec, index);
return read_view->changes_visible(trx_id);
}
private:
/**
* 清理事务资源
*/
void cleanup_transaction_resources() {
// 清理锁内存堆
if (lock.lock_heap != nullptr) {
mem_heap_free(lock.lock_heap);
lock.lock_heap = nullptr;
}
// 清理undo段
if (insert_undo != nullptr) {
trx_undo_insert_cleanup(this);
insert_undo = nullptr;
}
if (update_undo != nullptr) {
trx_undo_update_cleanup(this);
update_undo = nullptr;
}
}
};
6. 性能优化与监控
6.1 InnoDB性能指标监控
/**
* InnoDB性能监控系统
* 提供全面的性能指标收集和分析
*/
/**
* InnoDB性能指标结构
*/
struct innodb_performance_metrics {
// 缓冲池指标
struct {
ulint pool_size; ///< 缓冲池总大小(页数)
ulint free_pages; ///< 空闲页面数
ulint dirty_pages; ///< 脏页面数
ulint lru_len; ///< LRU链表长度
// 命中率统计
ulint reads; ///< 逻辑读次数
ulint read_requests; ///< 读请求次数
double hit_rate; ///< 命中率
// 刷新统计
ulint pages_flushed; ///< 刷新页面数
ulint pages_made_young; ///< 年轻化页面数
ulint pages_not_made_young; ///< 未年轻化页面数
} buffer_pool;
// 事务指标
struct {
ulint active_transactions; ///< 活跃事务数
ulint committed_transactions; ///< 已提交事务数
ulint rolled_back_transactions; ///< 已回滚事务数
ulint read_views; ///< Read View数量
} transaction;
// 锁指标
struct {
ulint lock_waits; ///< 锁等待次数
ulint lock_timeouts; ///< 锁超时次数
ulint deadlocks; ///< 死锁次数
double avg_lock_wait_time; ///< 平均锁等待时间
} locking;
// I/O指标
struct {
ulint data_reads; ///< 数据读取次数
ulint data_writes; ///< 数据写入次数
ulint log_writes; ///< 日志写入次数
ulint fsync_calls; ///< fsync调用次数
double avg_read_time; ///< 平均读取时间
double avg_write_time; ///< 平均写入时间
} io_stats;
// B+树指标
struct {
ulint index_searches; ///< 索引搜索次数
ulint index_reads; ///< 索引读取次数
ulint index_page_splits; ///< 页面分裂次数
ulint index_page_merges; ///< 页面合并次数
} index_stats;
};
/**
* 性能监控管理器
*/
class Performance_monitor {
private:
innodb_performance_metrics current_metrics;
innodb_performance_metrics previous_metrics;
std::chrono::steady_clock::time_point last_update;
public:
/**
* 更新性能指标
*/
void update_metrics() {
auto now = std::chrono::steady_clock::now();
previous_metrics = current_metrics;
// 更新缓冲池指标
update_buffer_pool_metrics();
// 更新事务指标
update_transaction_metrics();
// 更新锁指标
update_lock_metrics();
// 更新I/O指标
update_io_metrics();
// 更新索引指标
update_index_metrics();
last_update = now;
}
/**
* 获取性能报告
*/
std::string get_performance_report() const {
std::ostringstream report;
report << "=== InnoDB Performance Metrics ===\n\n";
// 缓冲池报告
report << "Buffer Pool:\n";
report << " Pool Size: " << current_metrics.buffer_pool.pool_size << " pages\n";
report << " Free Pages: " << current_metrics.buffer_pool.free_pages << "\n";
report << " Dirty Pages: " << current_metrics.buffer_pool.dirty_pages << "\n";
report << " Hit Rate: " << std::fixed << std::setprecision(2)
<< current_metrics.buffer_pool.hit_rate * 100 << "%\n\n";
// 事务报告
report << "Transactions:\n";
report << " Active: " << current_metrics.transaction.active_transactions << "\n";
report << " Committed: " << current_metrics.transaction.committed_transactions << "\n";
report << " Rolled Back: " << current_metrics.transaction.rolled_back_transactions << "\n\n";
// 锁报告
report << "Locking:\n";
report << " Lock Waits: " << current_metrics.locking.lock_waits << "\n";
report << " Lock Timeouts: " << current_metrics.locking.lock_timeouts << "\n";
report << " Deadlocks: " << current_metrics.locking.deadlocks << "\n";
report << " Avg Wait Time: " << std::fixed << std::setprecision(2)
<< current_metrics.locking.avg_lock_wait_time << " ms\n\n";
return report.str();
}
private:
void update_buffer_pool_metrics() {
// 遍历所有缓冲池实例
ulint total_pool_size = 0;
ulint total_free_pages = 0;
ulint total_dirty_pages = 0;
ulint total_reads = 0;
ulint total_read_requests = 0;
for (ulint i = 0; i < srv_buf_pool_instances; i++) {
buf_pool_t* pool = buf_pool_from_array(i);
total_pool_size += pool->curr_pool_size;
total_free_pages += UT_LIST_GET_LEN(pool->free);
total_dirty_pages += UT_LIST_GET_LEN(pool->flush_list);
total_reads += pool->stat.n_pages_read;
total_read_requests += pool->stat.n_page_gets;
}
current_metrics.buffer_pool.pool_size = total_pool_size;
current_metrics.buffer_pool.free_pages = total_free_pages;
current_metrics.buffer_pool.dirty_pages = total_dirty_pages;
// 计算命中率
if (total_read_requests > 0) {
current_metrics.buffer_pool.hit_rate =
1.0 - (static_cast<double>(total_reads) / total_read_requests);
}
}
void update_transaction_metrics() {
trx_sys_mutex_enter();
current_metrics.transaction.active_transactions =
UT_LIST_GET_LEN(trx_sys->rw_trx_list);
// 其他事务指标需要从全局计数器获取
current_metrics.transaction.committed_transactions =
trx_sys->committed_trx_count;
current_metrics.transaction.rolled_back_transactions =
trx_sys->rolled_back_trx_count;
trx_sys_mutex_exit();
}
void update_lock_metrics() {
mutex_enter(&lock_sys->wait_mutex);
// 从锁系统获取统计信息
current_metrics.locking.lock_waits = lock_sys->n_lock_waits;
current_metrics.locking.lock_timeouts = lock_sys->n_lock_timeouts;
current_metrics.locking.deadlocks = lock_sys->n_deadlocks;
if (lock_sys->n_lock_waits > 0) {
current_metrics.locking.avg_lock_wait_time =
static_cast<double>(lock_sys->total_wait_time) /
lock_sys->n_lock_waits / 1000.0; // 转换为毫秒
}
mutex_exit(&lock_sys->wait_mutex);
}
};
7. 生产环境调优实战
7.1 InnoDB性能调优最佳实践
基于《MySQL InnoDB生产环境调优指南》和《高并发MySQL实战》的经验总结:
7.1.1 缓冲池调优案例
-- 监控缓冲池命中率
SELECT
VARIABLE_NAME,
VARIABLE_VALUE,
CASE VARIABLE_NAME
WHEN 'Innodb_buffer_pool_read_requests' THEN @read_requests := VARIABLE_VALUE
WHEN 'Innodb_buffer_pool_reads' THEN @reads := VARIABLE_VALUE
END
FROM PERFORMANCE_SCHEMA.GLOBAL_STATUS
WHERE VARIABLE_NAME IN ('Innodb_buffer_pool_read_requests', 'Innodb_buffer_pool_reads');
-- 计算命中率
SELECT
CONCAT(ROUND(100 - (@reads / @read_requests * 100), 2), '%') AS buffer_pool_hit_rate;
-- 缓冲池状态监控
SELECT
pool_id,
pool_size,
free_buffers,
database_pages,
old_database_pages,
modified_database_pages,
pending_reads,
pending_writes_lru,
pending_writes_flush_list,
pages_made_young,
pages_not_made_young,
pages_made_young_rate,
pages_made_not_young_rate,
number_pages_read,
number_pages_created,
number_pages_written,
pages_read_rate,
pages_create_rate,
pages_written_rate,
number_pages_get,
hit_rate,
young_make_per_thousand_gets,
not_young_make_per_thousand_gets,
pages_read_ahead,
pages_read_ahead_evicted,
read_ahead_rate,
read_ahead_evicted_rate,
lru_io_total,
lru_io_current,
uncompress_total,
uncompress_current
FROM INFORMATION_SCHEMA.INNODB_BUFFER_POOL_STATS;
7.1.2 InnoDB调优参数优化
/**
* InnoDB调优参数管理器
* 基于系统负载动态调整关键参数
*/
class InnoDB_tuning_manager {
private:
struct tuning_metrics {
double buffer_pool_hit_rate; ///< 缓冲池命中率
ulint lock_waits_per_second; ///< 每秒锁等待次数
ulint deadlocks_per_hour; ///< 每小时死锁次数
double avg_row_lock_time; ///< 平均行锁时间
ulint dirty_pages_pct; ///< 脏页百分比
double log_waits_per_second; ///< 每秒日志等待次数
};
public:
/**
* 智能参数调优
* 根据当前系统状态自动调整InnoDB参数
*/
void auto_tune_parameters() {
tuning_metrics metrics = collect_current_metrics();
// 1. 缓冲池大小调优
if (metrics.buffer_pool_hit_rate < 0.95) {
suggest_buffer_pool_increase(metrics);
}
// 2. 日志缓冲区调优
if (metrics.log_waits_per_second > 1.0) {
suggest_log_buffer_increase(metrics);
}
// 3. 刷新策略调优
if (metrics.dirty_pages_pct > 75) {
suggest_flush_method_optimization(metrics);
}
// 4. 锁超时调优
if (metrics.lock_waits_per_second > 10.0) {
suggest_lock_timeout_adjustment(metrics);
}
}
/**
* 监控关键性能指标
*/
tuning_metrics collect_current_metrics() {
tuning_metrics metrics;
// 获取缓冲池统计
metrics.buffer_pool_hit_rate = calculate_buffer_pool_hit_rate();
// 获取锁统计
metrics.lock_waits_per_second = lock_sys->n_lock_waits.load() /
(time(nullptr) - system_start_time);
metrics.avg_row_lock_time = lock_sys->total_wait_time.load() /
std::max(1UL, lock_sys->n_lock_waits.load());
// 获取死锁统计
metrics.deadlocks_per_hour = lock_sys->n_deadlocks.load() * 3600 /
(time(nullptr) - system_start_time);
// 获取脏页比例
metrics.dirty_pages_pct = calculate_dirty_pages_percentage();
// 获取日志等待
metrics.log_waits_per_second = log_sys->n_log_waits.load() /
(time(nullptr) - system_start_time);
return metrics;
}
private:
void suggest_buffer_pool_increase(const tuning_metrics &metrics) {
LogInfo(TUNING_LOG, "缓冲池命中率较低(%.2f%%),建议增加innodb_buffer_pool_size",
metrics.buffer_pool_hit_rate * 100);
}
void suggest_log_buffer_increase(const tuning_metrics &metrics) {
LogInfo(TUNING_LOG, "日志等待频繁(%.2f次/秒),建议增加innodb_log_buffer_size",
metrics.log_waits_per_second);
}
};
7.2 InnoDB故障诊断案例
案例1:缓冲池异常诊断
-- 诊断缓冲池异常的SQL查询
-- 1. 检查缓冲池基本状态
SHOW ENGINE INNODB STATUS\G
-- 2. 分析缓冲池页面分布
SELECT
page_type,
COUNT(*) as page_count,
ROUND(COUNT(*) * 100.0 / (SELECT COUNT(*) FROM INFORMATION_SCHEMA.INNODB_BUFFER_PAGE), 2) as percentage
FROM INFORMATION_SCHEMA.INNODB_BUFFER_PAGE
GROUP BY page_type
ORDER BY page_count DESC;
-- 3. 查找热点表
SELECT
IFNULL(schema_name, 'Unknown') as schema_name,
IFNULL(table_name, 'Unknown') as table_name,
COUNT(*) as pages_in_buffer,
ROUND(COUNT(*) * 16 / 1024, 2) as size_mb
FROM INFORMATION_SCHEMA.INNODB_BUFFER_PAGE
WHERE table_name IS NOT NULL
GROUP BY schema_name, table_name
ORDER BY pages_in_buffer DESC
LIMIT 10;
案例2:锁等待分析
-- 实时锁等待分析
SELECT
r.trx_id AS waiting_trx_id,
r.trx_mysql_thread_id AS waiting_thread,
r.trx_query AS waiting_query,
b.trx_id AS blocking_trx_id,
b.trx_mysql_thread_id AS blocking_thread,
b.trx_query AS blocking_query,
l.lock_mode,
l.lock_type,
l.lock_table,
l.lock_index,
l.lock_rec
FROM INFORMATION_SCHEMA.INNODB_LOCK_WAITS w
JOIN INFORMATION_SCHEMA.INNODB_TRX r ON r.trx_id = w.requesting_trx_id
JOIN INFORMATION_SCHEMA.INNODB_TRX b ON b.trx_id = w.blocking_trx_id
JOIN INFORMATION_SCHEMA.INNODB_LOCKS l ON l.lock_id = w.blocking_lock_id;
8. 总结
8.1 核心技术优势
InnoDB存储引擎的核心优势体现在:
- B+树索引结构:高效的范围查询和点查询能力
- 多版本并发控制:无锁读取,高并发性能
- 完整的事务支持:ACID特性,数据一致性保证
- 智能缓冲池管理:LRU算法和预读机制优化I/O
- 复杂锁机制:行级锁定,细粒度并发控制
- 生产级可靠性:经过大规模生产环境验证
8.2 设计模式应用
InnoDB广泛应用了多种设计模式:
- 策略模式:不同的页面替换策略、刷新策略
- 观察者模式:事务状态变化通知、锁释放通知
- 命令模式:undo/redo日志操作封装
- 状态模式:事务状态转换管理
- 工厂模式:页面、锁对象的创建
通过深入理解InnoDB的核心技术实现,我们能够更好地进行数据库调优、故障诊断和架构设计,充分发挥InnoDB在高并发和事务处理场景中的优势。
附录A:关键函数核心代码与功能说明
- btr_cur_t::search_to_nth_level: 自上而下定位到叶子层的页面/记录(见前文实现)。
- btr_cur_t::optimistic_insert: 乐观插入当前页空间足够时的快速路径(见前文实现)。
- btr_page_split_t::execute_split: 页空间不足触发分裂、搬移记录与父节点指针更新(见前文实现)。
- buf_pool_t::get_page/flush_dirty_pages: 页面读取与脏页刷新核心流程(见前文实现)。
- lock_sys_t::lock_record/lock_table: 行/表锁申请与冲突处理(见前文实现)。
- trx_t::start/commit/rollback: 事务生命周期与日志写入(见前文实现)。
附录B:统一调用链
- 索引点查(读)
- btr_cur_t::search_to_nth_level() → buf_pool_t::get_page() → [命中/未命中读盘] → 返回记录
- 插入(含页分裂)
- btr_cur_t::optimistic_insert() → [不足] btr_page_split_t::execute_split() → move_records_to_new_page() → update_page_links() → insert_node_pointer_to_parent()
- 脏页刷新
- buf_pool_t::flush_dirty_pages() → buf_flush_page() → fil_io() → 标记干净/更新 flush list
- 锁等待与死锁
- lock_sys_t::lock_record() → [冲突] create_waiting_record_lock() → deadlock_check() → wait_for_lock_grant()/回滚
- 事务提交(2PC 对齐)
- trx_t::commit() → 写 redo/undo → 刷盘策略 → 状态更新与资源清理
附录C:关键函数时序图(补充:乐观插入/页分裂)
sequenceDiagram
participant Cur as btr_cur_t
participant Page as Page(Frame)
participant Split as btr_page_split_t
participant Buf as buf_pool_t
Cur->>Page: optimistic_insert()
alt 页面可容纳
Page-->>Cur: 插入成功
else 空间不足
Cur->>Split: execute_split()
Split->>Buf: 分配新页/写链接
Split-->>Cur: 分裂完成
Cur->>Page: 重新插入
end
附录D:关键结构体类结构图与继承关系
classDiagram
class btr_cur_t
class btr_page_split_t
class buf_pool_t
class lock_sys_t
class trx_t
btr_cur_t ..> buf_pool_t : get_page
btr_cur_t ..> btr_page_split_t : split
trx_t ..> lock_sys_t : lock
trx_t ..> buf_pool_t : flush/read