快照通过mvcc+原子指针切换实现无锁瞬时创建:仅保存活跃版本根指针,写操作追加新节点,读固定root_ptr访问对应版本;每个versionnode带引用计数,配合事务生命周期跟踪与写放大阈值触发gc;快照隔离依赖统一逻辑时钟g_commit_counter保证时间戳一致性。

快照怎么不阻塞写入
快照必须是瞬时的、无锁的,否则一拍快照整个数据库就卡住。核心思路是用多版本(MVCC)+ 原子指针切换:每次快照只保存当前活跃版本的根指针(比如 std::atomic<versionnode></versionnode>),而不是拷贝所有数据。
常见错误是直接深拷贝内存页或哈希表——这既慢又吃内存,还导致写入线程长时间等待。正确做法是让写操作在旧版本上追加新节点(如跳表插入新层、B+树分裂出新页),读快照则固定住某个 root_ptr 后只访问该版本可达的节点。
- 写操作永远只修改“最新”版本的索引结构,不触碰快照持有的老版本节点
- 每个
VersionNode需带引用计数,快照持有它时 +1;当所有快照释放后才回收 - 避免用
shared_ptr全局管理——原子指针 + 手动 refcount 更可控,也规避了循环引用风险
如何高效回收过期版本
不回收 = 内存泄漏;乱回收 = 快照读到野指针。关键在于区分“逻辑过期”和“物理可回收”:一个版本只有在**所有快照都已释放其 root_ptr 且无进行中的只读事务访问它**时,才能被 GC。
典型陷阱是仅靠引用计数判断——如果有个长时只读查询正遍历老版本,而你把它回收了,就会 crash。必须配合事务生命周期跟踪。
立即学习“C++免费学习笔记(深入)”;
NetShop软件特点介绍: 1、使用ASP.Net(c#)2.0、多层结构开发 2、前台设计不采用任何.NET内置控件读取数据,完全标签化模板处理,加快读取速度3、安全的数据添加删除读取操作,利用存储过程模式彻底防制SQL注入式攻击4、前台架构DIV+CSS兼容IE6,IE7,FF等,有利于搜索引挚收录5、后台内置强大的功能,整合多家网店系统的功能,加以优化。6、支持三种类型的数据库:Acces
- 维护一个全局
std::vector<:atomic>></:atomic>标记各版本是否“正在被读”(每个快照创建时注册,销毁时注销) - GC 线程定期扫描:对每个版本,检查其 refcount == 0 且所有关联的
is_reading标志为 false - 不要用后台线程频繁扫描——改用写放大阈值触发(例如累计 512MB 过期内存才启动一次 GC)
快照隔离级别怎么保证一致性
C++ 没有内置事务语义,所谓“快照隔离”得靠设计约束:每个快照只能看到它创建时刻已提交的所有写入,且不能看到之后的任何变更。这要求写入必须有全局单调递增的 commit_ts,且快照记录自己的 snap_ts。
最容易错的是写入时没对齐时间戳——比如用 std::chrono::steady_clock 生成 snap_ts,却用 system_clock 做 commit_ts,结果时钟漂移导致快照漏读或误读。
- 统一用
std::atomic<uint64_t> g_commit_counter</uint64_t>作为逻辑时钟,每次写入前fetch_add(1) - 快照构造时读取当前
g_commit_counter.load()作为snap_ts,读键值时只接受node.commit_ts 的版本 - 删除操作不是真删,而是写入一个
commit_ts更大的 tombstone 节点,让旧快照自然“看不见”
std::shared_ptr 能不能直接用于节点管理
能编译,但会出问题。表面上 shared_ptr 自动管理生命周期,实际在高并发 MVCC 场景下,它带来的原子控制块竞争和 cache line 伪共享会显著拖慢写吞吐,尤其在小对象(如 16 字节的 ValueNode)密集分配时。
更隐蔽的问题是:shared_ptr 的析构不是即时的——它可能延迟到任意线程执行,导致内存回收时机不可控,干扰 GC 判断。
- 改用裸指针 + 手动 refcount(
std::atomic<uint32_t></uint32_t>)+ 对象池(ObjectPool<node></node>) - refcount 只增减,不依赖析构;回收由 GC 线程统一调用
pool.free(node) - 如果坚持用智能指针,至少用
std::unique_ptr配合 move 语义,在写路径中避免共享









