|
|
@ -192,71 +192,108 @@ Status DBImpl::DeleteIndex(const std::string& fieldName) { |
|
|
|
|
|
|
|
--- |
|
|
|
|
|
|
|
### 5. **对 `Put` 和 `Delete` 方法的内容更新描述** |
|
|
|
|
|
|
|
为了在 `Put` 和 `Delete` 操作中同步更新二级索引,我们对代码进行了以下扩展: |
|
|
|
|
|
|
|
#### **Put 方法** |
|
|
|
|
|
|
|
在 `Put` 方法中,新增逻辑检查并更新字段索引: |
|
|
|
|
|
|
|
1. **字段值提取与检查** |
|
|
|
- 遍历所有已创建索引的字段列表 (`fieldWithIndex_`)。 |
|
|
|
- 检查待插入数据值 (`val`) 中是否包含当前字段。 |
|
|
|
- 如果字段存在,提取该字段的值 (`fieldValue`)。 |
|
|
|
|
|
|
|
2. **构建索引键与插入索引数据库** |
|
|
|
- 使用字段名和字段值组合构建索引键 (`field:fieldValue`)。 |
|
|
|
- 将该索引键与原始键 (`key`) 写入二级索引数据库 `indexDb_`。 |
|
|
|
- 如果写入操作失败,立即返回错误状态。 |
|
|
|
|
|
|
|
此逻辑保证在 `Put` 方法中,对 `fieldWithIndex_` 中的每个字段都可以维护最新的索引关系。 |
|
|
|
|
|
|
|
#### **Delete 方法** |
|
|
|
|
|
|
|
在 `Delete` 方法中,新增逻辑检查并移除相关字段索引: |
|
|
|
|
|
|
|
1. **字段值提取与检查** |
|
|
|
- 遍历所有已创建索引的字段列表 (`fieldWithIndex_`)。 |
|
|
|
- 检查待删除数据键 (`key`) 中是否包含当前字段。 |
|
|
|
- 如果字段存在,提取该字段的值 (`fieldValue`)。 |
|
|
|
|
|
|
|
2. **构建索引键与删除索引条目** |
|
|
|
- 使用字段名和字段值组合构建索引键 (`field:fieldValue`)。 |
|
|
|
- 从二级索引数据库 `indexDb_` 中删除该索引键。 |
|
|
|
- 如果删除操作失败,立即返回错误状态。 |
|
|
|
|
|
|
|
此逻辑确保在 `Delete` 操作中能够正确移除已删除记录对应的二级索引条目。 |
|
|
|
|
|
|
|
--- |
|
|
|
|
|
|
|
### 6. **数据插入与删除原子性的实现** |
|
|
|
|
|
|
|
#### **插入数据的实现** |
|
|
|
|
|
|
|
在数据插入操作中,我们使用 `WriteBatch` 来确保对主数据库 (`DBImpl`) 和二级索引数据库 (`indexDb_`) 的操作能够作为一个原子事务提交。首先,将数据插入主数据库,然后根据预定义的字段集合 (`fieldWithIndex_`) 生成索引信息,并将这些索引数据插入到二级索引数据库中。如果插入操作中的任何一个步骤失败(例如,主数据库或索引数据库插入失败),整个事务将回滚,确保数据库的一致性。 |
|
|
|
|
|
|
|
### 5. **`Put` 和 `Delete` 方法的内容** |
|
|
|
以下是实验报告中对 `Put` 和 `Delete` 方法的描述,以及如何通过事务和回滚机制实现数据插入与删除的原子性。 |
|
|
|
|
|
|
|
#### `Put` 方法描述 |
|
|
|
`Put` 方法用于向主数据库和二级索引数据库中插入或更新数据。其关键步骤如下: |
|
|
|
1. **主数据库写入**:首先尝试向主数据库插入或更新数据。 |
|
|
|
2. **二级索引更新**:遍历需要创建索引的字段 (`fieldWithIndex_`),从新值中提取字段对应的索引键和值,并将索引插入到二级索引数据库。 |
|
|
|
3. **提交事务**: |
|
|
|
- 提交主数据库的写入操作。 |
|
|
|
- 提交二级索引数据库的写入操作。 |
|
|
|
4. **回滚机制**:如果二级索引数据库的写入失败,会回滚主数据库的插入操作以确保数据一致性。 |
|
|
|
|
|
|
|
关键代码: |
|
|
|
```cpp |
|
|
|
WriteBatch batch; // 创建事务 |
|
|
|
|
|
|
|
// 在主数据库插入数据 |
|
|
|
batch.Put(key, val); |
|
|
|
|
|
|
|
// 遍历并生成二级索引数据 |
|
|
|
for (const auto& field : fieldWithIndex_) { |
|
|
|
std::string fieldValue = 提取字段值(val, field); |
|
|
|
if (!fieldValue.empty()) { |
|
|
|
std::string indexKey = field + ":" + fieldValue; |
|
|
|
std::string indexValue = key.ToString(); |
|
|
|
batch.Put(Slice(indexKey), Slice(indexValue)); // 将索引数据加入事务 |
|
|
|
} |
|
|
|
Status DBImpl::Put(const WriteOptions& o, const Slice& key, const Slice& val) { |
|
|
|
... |
|
|
|
// 在主数据库写入新数据 |
|
|
|
batch.Put(key, val); |
|
|
|
|
|
|
|
// 遍历字段并更新索引 |
|
|
|
for (const auto& field : fieldWithIndex_) { |
|
|
|
... |
|
|
|
indexBatch.Put(Slice(indexKey), Slice(indexValue)); |
|
|
|
} |
|
|
|
|
|
|
|
// 提交主数据库事务 |
|
|
|
s = this->Write(o, &batch); |
|
|
|
if (!s.ok()) { |
|
|
|
return s; |
|
|
|
} |
|
|
|
|
|
|
|
// 提交二级索引数据库事务 |
|
|
|
s = indexDb_->Write(o, &indexBatch); |
|
|
|
if (!s.ok()) { |
|
|
|
// 如果二级索引写入失败,回滚主数据库写入 |
|
|
|
for (const auto& insertedKey : keysInserted) { |
|
|
|
batch.Delete(insertedKey); |
|
|
|
} |
|
|
|
this->Write(o, &batch); |
|
|
|
return s; |
|
|
|
} |
|
|
|
... |
|
|
|
} |
|
|
|
``` |
|
|
|
|
|
|
|
// 提交事务 |
|
|
|
Status s = this->Write(o, &batch); |
|
|
|
#### `Delete` 方法描述 |
|
|
|
`Delete` 方法用于从主数据库和二级索引数据库中删除数据。其关键步骤如下: |
|
|
|
1. **获取原始数据**:在删除前从主数据库读取原始值,确保在删除失败时可以回滚。 |
|
|
|
2. **主数据库删除**:从主数据库中删除目标键。 |
|
|
|
3. **二级索引删除**:遍历字段,计算对应的索引键,并将其从二级索引数据库中删除。 |
|
|
|
4. **提交事务**: |
|
|
|
- 提交主数据库的删除操作。 |
|
|
|
- 提交二级索引数据库的删除操作。 |
|
|
|
5. **回滚机制**:如果二级索引数据库的删除失败,会尝试将主数据库的删除操作回滚为原始状态。 |
|
|
|
|
|
|
|
关键代码: |
|
|
|
```cpp |
|
|
|
Status DBImpl::Delete(const WriteOptions& options, const Slice& key) { |
|
|
|
... |
|
|
|
// 从主数据库删除目标键 |
|
|
|
batch.Delete(key); |
|
|
|
|
|
|
|
// 遍历字段并删除索引 |
|
|
|
for (const auto& field : fieldWithIndex_) { |
|
|
|
... |
|
|
|
indexBatch.Delete(Slice(indexKey)); |
|
|
|
} |
|
|
|
|
|
|
|
// 提交主数据库事务 |
|
|
|
s = this->Write(options, &batch); |
|
|
|
if (!s.ok()) { |
|
|
|
return s; |
|
|
|
} |
|
|
|
|
|
|
|
// 提交二级索引数据库事务 |
|
|
|
s = indexDb_->Write(options, &indexBatch); |
|
|
|
if (!s.ok()) { |
|
|
|
// 如果二级索引删除失败,回滚主数据库删除 |
|
|
|
if (!originalValue.empty()) { |
|
|
|
batch.Put(key, originalValue); |
|
|
|
} else { |
|
|
|
batch.Put(key, ""); |
|
|
|
} |
|
|
|
this->Write(options, &batch); |
|
|
|
return s; |
|
|
|
} |
|
|
|
... |
|
|
|
} |
|
|
|
``` |
|
|
|
|
|
|
|
通过上述方式,插入操作确保了主数据库与二级索引数据库的一致性。如果插入过程中任何一个数据库发生错误,整个事务会回滚,不会进行部分写入,从而避免了数据不一致的情况。 |
|
|
|
#### 6.数据插入与删除的原子性实现 |
|
|
|
通过以下策略确保数据插入与删除操作的原子性: |
|
|
|
1. **事务机制**: |
|
|
|
- 主数据库和二级索引数据库的写入操作分别使用 `WriteBatch` 封装,并在提交前记录必要的数据以支持回滚。 |
|
|
|
2. **错误处理与回滚**: |
|
|
|
- 如果二级索引数据库的写入或删除操作失败,主数据库的写入或删除操作将被回滚。 |
|
|
|
- 在回滚过程中,主数据库会恢复为操作前的状态(插入操作时删除新数据,删除操作时恢复原始数据)。 |
|
|
|
|
|
|
|
#### 实现意义 |
|
|
|
这种设计确保了主数据库和二级索引数据库的一致性,即便在部分写入或删除操作失败的情况下,仍能通过回滚机制保证数据的完整性和原子性。 |
|
|
|
|
|
|
|
--- |
|
|
|
|
|
|
|
#### **删除数据的实现** |
|
|
|
|
|
|
|