|
|
@ -5,7 +5,7 @@ |
|
|
|
## 目录 |
|
|
|
|
|
|
|
- [一,实验目的](#一实验目的) |
|
|
|
- [二,背景简述](#二背景简述) |
|
|
|
- [二,项目背景概述](#二项目背景概述) |
|
|
|
- [1. **背景与需求**](#1-背景与需求) |
|
|
|
- [2. **设计目标**](#2-设计目标) |
|
|
|
- [三,`LevelDB`二级索引设计思路](#三leveldb二级索引设计思路) |
|
|
@ -36,6 +36,7 @@ |
|
|
|
- [五,性能测试](#五性能测试) |
|
|
|
- [1. 测试流程](#1-测试流程) |
|
|
|
- [2. 结果分析](#2-结果分析) |
|
|
|
- [六,问题与解决方案](#六问题与解决方案) |
|
|
|
- [总结](#总结) |
|
|
|
|
|
|
|
|
|
|
@ -45,7 +46,7 @@ |
|
|
|
|
|
|
|
--- |
|
|
|
|
|
|
|
## 二,背景简述 |
|
|
|
## 二,项目背景概述 |
|
|
|
|
|
|
|
#### 1. **背景与需求** |
|
|
|
`LevelDB` 是一个高性能、轻量级的键值存储引擎,但其查询能力仅限于主键。在许多应用场景中,需要支持基于非主键字段的高效查询(例如按用户 ID 或类别查询数据)。因此,设计并实现二级索引系统,为 LevelDB 增强多字段查询能力,成为一个核心需求。 |
|
|
@ -552,5 +553,77 @@ Level Files Size(MB) Time(sec) Read(MB) Write(MB) |
|
|
|
|
|
|
|
--- |
|
|
|
|
|
|
|
## 六,问题与解决方案 |
|
|
|
|
|
|
|
### 1. **问题:如何避免** `**indexDb_**` **的递归调用?** |
|
|
|
|
|
|
|
在实现 `Put` 和 `Delete` 方法时,由于 `indexDb_` 也调用了 `DBImpl` 的方法,可能导致递归调用的问题。具体表现为在 `indexDb_` 内部操作时仍会试图更新索引。 |
|
|
|
|
|
|
|
#### **解决方案:** |
|
|
|
|
|
|
|
在 `Put` 和 `Delete` 方法中,添加检查逻辑。如果当前对象是 `indexDb_`,则仅对主数据库进行操作,而不再更新索引。例如: |
|
|
|
|
|
|
|
``` |
|
|
|
if (indexDb_ != nullptr) { |
|
|
|
// 仅更新主数据库的事务 |
|
|
|
} else { |
|
|
|
// 更新索引 |
|
|
|
} |
|
|
|
``` |
|
|
|
|
|
|
|
### 2. **问题:二级索引的事务回滚机制如何设计?** |
|
|
|
|
|
|
|
在二级索引更新失败时,需要确保主数据库的修改也能回滚,以保持数据一致性。 |
|
|
|
|
|
|
|
#### **解决方案:** |
|
|
|
|
|
|
|
使用 `WriteBatch` 记录每次操作。在二级索引更新失败后,通过读取原始值或删除新插入的键,恢复主数据库的状态。示例代码如下: |
|
|
|
|
|
|
|
``` |
|
|
|
if (!s.ok()) { |
|
|
|
for (const auto& insertedKey : keysInserted) { |
|
|
|
if (!originalValue.empty()) { |
|
|
|
batch.Put(insertedKey, Slice(originalValue)); |
|
|
|
} else { |
|
|
|
batch.Delete(insertedKey); |
|
|
|
} |
|
|
|
} |
|
|
|
this->Write(o, &batch); // 执行回滚 |
|
|
|
} |
|
|
|
``` |
|
|
|
|
|
|
|
### 3. **问题:如何高效管理多字段的动态索引?** |
|
|
|
|
|
|
|
如果需要为多个字段动态创建和删除索引,可能导致额外的开销以及管理复杂性。 |
|
|
|
|
|
|
|
#### **解决方案:** |
|
|
|
|
|
|
|
1. 使用 `fieldWithIndex_` 字段集中管理所有需要创建索引的字段。 |
|
|
|
2. 在动态操作中,通过扫描主数据库快速生成或删除指定字段的索引条目。 |
|
|
|
3. 提供统一的接口,用于添加和移除字段。 |
|
|
|
|
|
|
|
### 4. **问题:**`**Put**` **和** `**Delete**` **方法如何确保原子性?** |
|
|
|
|
|
|
|
在更新主数据库和二级索引时,如果某一步骤失败,可能导致不一致。 |
|
|
|
|
|
|
|
#### **解决方案:** |
|
|
|
|
|
|
|
通过事务 (`WriteBatch`) 确保多个操作要么全部成功,要么全部回滚。例如: |
|
|
|
|
|
|
|
``` |
|
|
|
s = this->Write(o, &batch); |
|
|
|
if (!s.ok()) { |
|
|
|
return s; // 确保写入失败时停止后续操作 |
|
|
|
} |
|
|
|
s = indexDb_->Write(o, &indexBatch); |
|
|
|
if (!s.ok()) { |
|
|
|
// 回滚主数据库操作 |
|
|
|
} |
|
|
|
``` |
|
|
|
|
|
|
|
通过以上方案,有效解决了实验中遇到的问题,并提高了系统的稳定性和一致性。 |
|
|
|
|
|
|
|
--- |
|
|
|
|
|
|
|
## 总结 |
|
|
|
本实验通过在 `DBImpl` 中集成索引管理功能,实现了对二级索引的创建、查询和删除。二级索引数据存储在独立的 `indexDb_` 中,通过高效的键值映射提升了字段值查询的效率。 |