不能用裸指针管理页锁,因其无法表达所有权、生命周期和线程安全语义,易导致指针失效、重复/漏释放、事务锁表注册失败及异常时死锁;必须用RAII锁句柄(如PageLockGuard)封装,确保自动加锁/解锁、异常安全与作用域绑定。

数据库内核中不直接用裸指针管理数据页锁——这是危险且不可维护的做法;现代实现(如 PostgreSQL、MySQL InnoDB)全部基于 RAII 封装的锁持有者对象,裸 char* 或 Page* 指针仅用于底层页地址计算,绝不参与锁生命周期管理。
为什么不能用裸指针持有或传递页锁
裸指针无法表达所有权、生命周期和线程安全语义,而页锁必须严格绑定到具体页实例、事务上下文和释放时机:
-
Page*可能被 buffer pool 驱逐或重映射,指针值失效但无提示 - 多个线程对同一
Page*调用lock()/unlock()极易漏释放或重复释放 - 事务回滚时需批量释放所有持有的页锁,裸指针无法自动注册到事务锁表中
- C++ 异常抛出路径下,裸指针不会自动析构,必然导致死锁
正确做法:用 RAII 锁句柄包装页锁
以类比 InnoDB 的 buf_block_t + rw_lock_t 为例,实际代码中应定义类似 PageLockGuard 的栈对象:
class PageLockGuard {
Page* page_;
rw_lock_t* lock_;
public:
explicit PageLockGuard(Page* p) : page_(p), lock_(&p->lock_) {
rw_lock_s_lock(lock_); // 或 x_lock
}
~PageLockGuard() {
if (lock_) rw_lock_s_unlock(lock_);
}
PageLockGuard(const PageLockGuard&) = delete;
PageLockGuard& operator=(const PageLockGuard&) = delete;
};使用时只需:
立即学习“C++免费学习笔记(深入)”;
{
PageLockGuard guard(buf_pool_get_page(page_id));
// 在此区间内 page 安全可读/写
memcpy(dst, guard.page_->data, PAGE_SIZE);
}离开作用域自动解锁,异常安全,且无法绕过构造函数获取裸锁指针。
页地址计算可用指针,但锁操作必须隔离
buffer pool 中页的物理定位确实依赖指针运算(例如哈希桶查找),但该指针只用于瞬时寻址,不保存、不传递、不跨函数边界:
- 查页:用
hash_table_lookup(&buf_pool->page_hash, page_id)返回buf_block_t*,立刻构造PageLockGuard - 禁止把
buf_block_t*存入全局容器或传给异步任务——页可能被重定位 - 若需跨阶段持锁,应保存
page_id和事务 ID,在下次访问时重新 lookup + lock -
reinterpret_cast<char>(page_ptr) + offset</char>可用于字段偏移访问,但绝不用于锁操作
真正难的不是怎么写指针运算,而是让每个锁持有者都明确知道“我什么时候必须存在、谁负责销毁我、异常时是否还在”。一旦把锁和裸指针耦合,调试死锁的时间会远超写一百个 RAII 类。


















