|
@ -14,11 +14,11 @@ |
|
|
- [1.2 **数据结构**](#12-数据结构) |
|
|
- [1.2 **数据结构**](#12-数据结构) |
|
|
- [1.3 **字段管理**](#13-字段管理) |
|
|
- [1.3 **字段管理**](#13-字段管理) |
|
|
- [1.4 **数据结构关系图**](#14-数据结构关系图) |
|
|
- [1.4 **数据结构关系图**](#14-数据结构关系图) |
|
|
- [3. **计划实现细节**](#3-计划实现细节) |
|
|
|
|
|
- [4. **动态索引管理**](#4-动态索引管理) |
|
|
|
|
|
- [5. **事务与回滚机制**](#5-事务与回滚机制) |
|
|
|
|
|
- [6. **设计的优势**](#6-设计的优势) |
|
|
|
|
|
- [7. **未来优化方向**](#7-未来优化方向) |
|
|
|
|
|
|
|
|
- [2. **计划实现细节**](#2-计划实现细节) |
|
|
|
|
|
- [3. **动态索引管理**](#3-动态索引管理) |
|
|
|
|
|
- [4. **事务与回滚机制**](#4-事务与回滚机制) |
|
|
|
|
|
- [5. **设计的优势**](#5-设计的优势) |
|
|
|
|
|
- [6. **未来优化方向**](#6-未来优化方向) |
|
|
- [四,具体实现](#四具体实现) |
|
|
- [四,具体实现](#四具体实现) |
|
|
- [1. **DBImpl 类的设计**](#1-dbimpl-类的设计) |
|
|
- [1. **DBImpl 类的设计**](#1-dbimpl-类的设计) |
|
|
- [2. **二级索引的创建**](#2-二级索引的创建) |
|
|
- [2. **二级索引的创建**](#2-二级索引的创建) |
|
@ -26,6 +26,7 @@ |
|
|
- [4. **二级索引的删除**](#4-二级索引的删除) |
|
|
- [4. **二级索引的删除**](#4-二级索引的删除) |
|
|
- [5. **`Put` 和 `Delete` 方法的内容**](#5-put-和-delete-方法的内容) |
|
|
- [5. **`Put` 和 `Delete` 方法的内容**](#5-put-和-delete-方法的内容) |
|
|
- [6. 数据插入与删除的原子性实现](#6-数据插入与删除的原子性实现) |
|
|
- [6. 数据插入与删除的原子性实现](#6-数据插入与删除的原子性实现) |
|
|
|
|
|
- [7.持久化与恢复机制](#7-持久化与恢复机制) |
|
|
- [五,性能测试](#五性能测试) |
|
|
- [五,性能测试](#五性能测试) |
|
|
- [1. 测试流程](#1-测试流程) |
|
|
- [1. 测试流程](#1-测试流程) |
|
|
- [2. 结果分析](#2-结果分析) |
|
|
- [2. 结果分析](#2-结果分析) |
|
@ -144,9 +145,9 @@ lessCopy code 主数据库 (DBImpl) |
|
|
|
|
|
|
|
|
--- |
|
|
--- |
|
|
|
|
|
|
|
|
#### 2. **计划实现细节** |
|
|
|
|
|
|
|
|
### 2. **计划实现细节** |
|
|
|
|
|
|
|
|
##### 2.1 数据插入流程 (`Put`) |
|
|
|
|
|
|
|
|
#### 2.1 数据插入流程 (`Put`) |
|
|
1. 用户调用 `Put` 将数据插入到主数据库。 |
|
|
1. 用户调用 `Put` 将数据插入到主数据库。 |
|
|
2. 从用户数据中解析需要创建索引的字段及其值。 |
|
|
2. 从用户数据中解析需要创建索引的字段及其值。 |
|
|
3. 构造二级索引的键值对,并插入到二级索引数据库中。 |
|
|
3. 构造二级索引的键值对,并插入到二级索引数据库中。 |
|
@ -156,7 +157,7 @@ lessCopy code 主数据库 (DBImpl) |
|
|
- 需要先提交主数据库事务,再提交二级索引数据库事务。 |
|
|
- 需要先提交主数据库事务,再提交二级索引数据库事务。 |
|
|
- 索引更新时要考虑覆盖旧索引的场景。 |
|
|
- 索引更新时要考虑覆盖旧索引的场景。 |
|
|
|
|
|
|
|
|
##### 2.2 数据删除流程 (`Delete`) |
|
|
|
|
|
|
|
|
#### 2.2 数据删除流程 (`Delete`) |
|
|
1. 用户调用 `Delete` 从主数据库删除数据。 |
|
|
1. 用户调用 `Delete` 从主数据库删除数据。 |
|
|
2. 在删除前,读取原始数据以提取相关字段的索引键。 |
|
|
2. 在删除前,读取原始数据以提取相关字段的索引键。 |
|
|
3. 删除主数据库中的数据。 |
|
|
3. 删除主数据库中的数据。 |
|
@ -167,33 +168,33 @@ lessCopy code 主数据库 (DBImpl) |
|
|
- 删除前必须读取原始数据以提取相关索引信息。 |
|
|
- 删除前必须读取原始数据以提取相关索引信息。 |
|
|
- 回滚时需恢复原始主数据库记录。 |
|
|
- 回滚时需恢复原始主数据库记录。 |
|
|
|
|
|
|
|
|
##### 3.3 查询流程 |
|
|
|
|
|
|
|
|
#### 3.3 查询流程 |
|
|
1. 用户指定查询条件(字段名和字段值)。 |
|
|
1. 用户指定查询条件(字段名和字段值)。 |
|
|
2. 从二级索引数据库中获取与查询条件匹配的主键。 |
|
|
2. 从二级索引数据库中获取与查询条件匹配的主键。 |
|
|
3. 使用主键从主数据库获取完整记录。 |
|
|
3. 使用主键从主数据库获取完整记录。 |
|
|
|
|
|
|
|
|
--- |
|
|
--- |
|
|
|
|
|
|
|
|
#### 3. **动态索引管理** |
|
|
|
|
|
##### 3.1 动态创建索引 |
|
|
|
|
|
|
|
|
### 3. **动态索引管理** |
|
|
|
|
|
#### 3.1 动态创建索引 |
|
|
- 提供接口 `CreateIndex(fieldName)`,用于动态为字段创建索引: |
|
|
- 提供接口 `CreateIndex(fieldName)`,用于动态为字段创建索引: |
|
|
- 遍历主数据库的所有记录。 |
|
|
- 遍历主数据库的所有记录。 |
|
|
- 根据指定字段生成索引键值对并插入到二级索引数据库。 |
|
|
- 根据指定字段生成索引键值对并插入到二级索引数据库。 |
|
|
- 将字段名添加到 `fieldWithIndex_` 集合。 |
|
|
- 将字段名添加到 `fieldWithIndex_` 集合。 |
|
|
|
|
|
|
|
|
##### 3.2 动态删除索引 |
|
|
|
|
|
|
|
|
#### 3.2 动态删除索引 |
|
|
- 提供接口 `DeleteIndex(fieldName)`,用于动态删除字段索引: |
|
|
- 提供接口 `DeleteIndex(fieldName)`,用于动态删除字段索引: |
|
|
- 遍历二级索引数据库,删除与该字段相关的索引键。 |
|
|
- 遍历二级索引数据库,删除与该字段相关的索引键。 |
|
|
- 从 `fieldWithIndex_` 集合中移除字段名。 |
|
|
- 从 `fieldWithIndex_` 集合中移除字段名。 |
|
|
|
|
|
|
|
|
--- |
|
|
--- |
|
|
|
|
|
|
|
|
#### 4. **事务与回滚机制** |
|
|
|
|
|
##### 4.1 事务设计 |
|
|
|
|
|
|
|
|
### 4. **事务与回滚机制** |
|
|
|
|
|
#### 4.1 事务设计 |
|
|
- 使用 `WriteBatch` 封装多个操作(如 `Put` 和 `Delete`)。 |
|
|
- 使用 `WriteBatch` 封装多个操作(如 `Put` 和 `Delete`)。 |
|
|
- 在主数据库和二级索引数据库上分别维护独立事务。 |
|
|
- 在主数据库和二级索引数据库上分别维护独立事务。 |
|
|
|
|
|
|
|
|
##### 4.2 回滚机制 |
|
|
|
|
|
|
|
|
#### 4.2 回滚机制 |
|
|
- 在主数据库操作失败时直接返回错误,不影响索引。 |
|
|
- 在主数据库操作失败时直接返回错误,不影响索引。 |
|
|
- 在二级索引操作失败时,回滚主数据库的写入或删除操作: |
|
|
- 在二级索引操作失败时,回滚主数据库的写入或删除操作: |
|
|
- 对 `Put` 操作,删除已插入的数据。 |
|
|
- 对 `Put` 操作,删除已插入的数据。 |
|
@ -201,7 +202,7 @@ lessCopy code 主数据库 (DBImpl) |
|
|
|
|
|
|
|
|
--- |
|
|
--- |
|
|
|
|
|
|
|
|
#### 5. **设计的优势** |
|
|
|
|
|
|
|
|
### 5. **设计的优势** |
|
|
1. **数据一致性强**:通过事务和回滚机制,确保主数据库和二级索引数据库始终保持一致。 |
|
|
1. **数据一致性强**:通过事务和回滚机制,确保主数据库和二级索引数据库始终保持一致。 |
|
|
2. **查询高效**:支持基于字段的快速查询,二级索引性能接近主键查询。 |
|
|
2. **查询高效**:支持基于字段的快速查询,二级索引性能接近主键查询。 |
|
|
3. **易于扩展**:动态索引创建和删除机制使得系统适应性更强。 |
|
|
3. **易于扩展**:动态索引创建和删除机制使得系统适应性更强。 |
|
@ -209,7 +210,7 @@ lessCopy code 主数据库 (DBImpl) |
|
|
|
|
|
|
|
|
--- |
|
|
--- |
|
|
|
|
|
|
|
|
#### 6. **未来优化方向** |
|
|
|
|
|
|
|
|
### 6. **未来优化方向** |
|
|
1. **多字段联合索引**:支持对多个字段的联合索引,提高复杂查询的效率。 |
|
|
1. **多字段联合索引**:支持对多个字段的联合索引,提高复杂查询的效率。 |
|
|
2. **异步索引更新**:通过异步任务队列优化索引构建和更新的性能。 |
|
|
2. **异步索引更新**:通过异步任务队列优化索引构建和更新的性能。 |
|
|
3. **空间优化**:采用压缩技术减少二级索引数据库的存储占用。 |
|
|
3. **空间优化**:采用压缩技术减少二级索引数据库的存储占用。 |
|
@ -227,9 +228,10 @@ lessCopy code 主数据库 (DBImpl) |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
## 四,具体实现 |
|
|
## 四,具体实现 |
|
|
|
|
|
|
|
|
|
|
|
为了便于审阅和维护,在项目中对代码的所有修改均使用统一的注释格式进行标记。具体而言,所有修改的代码块均以 `//ToDo` 开始,并以 `//ToDo end` 结束。通过这种方式,审阅者可以快速定位和识别修改内容,与原始代码进行对比。 |
|
|
|
|
|
|
|
|
### 1. **DBImpl 类的设计** |
|
|
### 1. **DBImpl 类的设计** |
|
|
在 LevelDB 的核心类 `DBImpl` 中,增加了对二级索引的支持,包括: |
|
|
在 LevelDB 的核心类 `DBImpl` 中,增加了对二级索引的支持,包括: |
|
|
- **索引字段管理**:使用成员变量 `fieldWithIndex_` 保存所有已经创建索引的字段名。 |
|
|
- **索引字段管理**:使用成员变量 `fieldWithIndex_` 保存所有已经创建索引的字段名。 |
|
@ -274,22 +276,9 @@ Status DBImpl::CreateIndexOnField(const std::string& fieldName) { |
|
|
std::string value = it->value().ToString(); |
|
|
std::string value = it->value().ToString(); |
|
|
|
|
|
|
|
|
// 提取字段值 |
|
|
// 提取字段值 |
|
|
size_t field_pos = value.find(fieldName + ":"); |
|
|
|
|
|
if (field_pos != std::string::npos) { |
|
|
|
|
|
size_t value_start = field_pos + fieldName.size() + 1; |
|
|
|
|
|
size_t value_end = value.find("|", value_start); |
|
|
|
|
|
if (value_end == std::string::npos) value_end = value.size(); |
|
|
|
|
|
|
|
|
|
|
|
std::string field_value = value.substr(value_start, value_end - value_start); |
|
|
|
|
|
std::string index_key = fieldName + ":" + field_value; |
|
|
|
|
|
|
|
|
|
|
|
// 在索引数据库中创建条目 |
|
|
|
|
|
leveldb::Status s = indexDb_->Put(WriteOptions(), Slice(index_key), Slice(key)); |
|
|
|
|
|
if (!s.ok()) { |
|
|
|
|
|
delete it; |
|
|
|
|
|
return s; |
|
|
|
|
|
} |
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
// ... |
|
|
|
|
|
// 在索引数据库中创建条目 |
|
|
|
|
|
// ... |
|
|
} |
|
|
} |
|
|
|
|
|
|
|
|
delete it; |
|
|
delete it; |
|
@ -373,13 +362,8 @@ Status DBImpl::DeleteIndex(const std::string& fieldName) { |
|
|
|
|
|
|
|
|
for (it_index->SeekToFirst(); it_index->Valid(); it_index->Next()) { |
|
|
for (it_index->SeekToFirst(); it_index->Valid(); it_index->Next()) { |
|
|
std::string index_key = it_index->key().ToString(); |
|
|
std::string index_key = it_index->key().ToString(); |
|
|
if (index_key.find(fieldName + ":") == 0) { |
|
|
|
|
|
Status s = indexDb_->Delete(WriteOptions(), Slice(index_key)); |
|
|
|
|
|
if (!s.ok()) { |
|
|
|
|
|
delete it_index; |
|
|
|
|
|
return s; |
|
|
|
|
|
} |
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
// ... |
|
|
} |
|
|
} |
|
|
|
|
|
|
|
|
delete it_index; |
|
|
delete it_index; |
|
@ -496,6 +480,68 @@ Status DBImpl::Delete(const WriteOptions& options, const Slice& key) { |
|
|
--- |
|
|
--- |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
### 7. **[持久化与恢复机制]()** |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
#### **1. 持久化机制** |
|
|
|
|
|
|
|
|
|
|
|
持久化是确保数据在系统崩溃或断电后依然能够恢复的关键功能。 |
|
|
|
|
|
在二级索引设计中,我们使用 `Write-Ahead Logging (WAL)` 技术和日志同步来实现持久化: |
|
|
|
|
|
|
|
|
|
|
|
- **主数据库持久化:** |
|
|
|
|
|
主数据库的 `Put` 和 `Delete` 操作会先记录到日志文件中,再执行磁盘写入操作。 |
|
|
|
|
|
日志结构如下: |
|
|
|
|
|
|
|
|
|
|
|
``` |
|
|
|
|
|
makefileCopy codeOperation: PUT |
|
|
|
|
|
Key: k_1 |
|
|
|
|
|
Value: name:Customer#000000001|address:IVhzIApeRb|phone:25-989-741-2988 |
|
|
|
|
|
``` |
|
|
|
|
|
|
|
|
|
|
|
- **索引数据库持久化:** |
|
|
|
|
|
对 `indexDb_` 的每一次 `Put` 和 `Delete` 操作也需要写入 WAL。 |
|
|
|
|
|
示例日志: |
|
|
|
|
|
|
|
|
|
|
|
``` |
|
|
|
|
|
makefileCopy codeOperation: PUT |
|
|
|
|
|
Key: name:Customer#000000001-k_1 |
|
|
|
|
|
Value: k_1 |
|
|
|
|
|
``` |
|
|
|
|
|
|
|
|
|
|
|
- **同步写入策略:** |
|
|
|
|
|
|
|
|
|
|
|
- 对主数据库和 `indexDb_` 的写操作采用 **事务** 来实现同步写入,确保一致性。 |
|
|
|
|
|
- 如果主数据库操作失败,回滚索引的更新;反之亦然。 |
|
|
|
|
|
|
|
|
|
|
|
#### **2. 恢复机制** |
|
|
|
|
|
|
|
|
|
|
|
恢复机制在系统崩溃后发挥作用,通过解析日志重建数据: |
|
|
|
|
|
|
|
|
|
|
|
- **主数据库恢复:** |
|
|
|
|
|
通过 WAL 日志文件重放,将最近一次写入操作重新应用到主数据库。 |
|
|
|
|
|
|
|
|
|
|
|
- **索引数据库恢复:** |
|
|
|
|
|
索引数据库的恢复流程如下: |
|
|
|
|
|
|
|
|
|
|
|
1. 检查 `indexDb_` 的日志文件,逐条读取并应用日志操作。 |
|
|
|
|
|
|
|
|
|
|
|
2. 如果`indexDb_`的日志不完整,基于主数据库的快照重新构建索引: |
|
|
|
|
|
|
|
|
|
|
|
- 遍历主数据库中的每条记录,根据 `fieldWithIndex_` 中的字段生成索引条目并写入 `indexDb_`。 |
|
|
|
|
|
|
|
|
|
|
|
- **一致性校验:** |
|
|
|
|
|
恢复完成后,通过校验主数据库和索引数据库的一致性来验证数据完整性: |
|
|
|
|
|
|
|
|
|
|
|
- 对每个索引字段,随机抽样检查索引值是否能正确定位主数据库中的记录。 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
<br> |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
--- |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|