From 6a7b6eadaf32a3d125f638e3d99d595f5b24ddd1 Mon Sep 17 00:00:00 2001 From: kevinyao0901 <kevinyao0901@163.com> Date: Tue, 10 Dec 2024 12:59:25 +0800 Subject: [PATCH] update Put & delete with writebatch to ensuring Atomicity --- README.md | 103 ++++++++++++++++++++++++++++++++++++++++++++++++++++++ db/db_impl.cc | 110 ++++++++++++++++++++++++++++++++-------------------------- 2 files changed, 164 insertions(+), 49 deletions(-) diff --git a/README.md b/README.md index 68f0724..344a195 100644 --- a/README.md +++ b/README.md @@ -165,6 +165,109 @@ 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.**数据插入与删除原子性的实现** + +为确保主数据库 (`DBImpl`) 和二级索引数据库 (`indexDb_`) 的一致性,我们在 `Put` 和 `Delete` 方法中采用了事务处理机制 (`WriteBatch`),以实现原子性操作。具体实现如下: + +#### **插入数据的实现** + +1. **主数据写入**: + 在 `Put` 方法中,首先将主数据写入操作 (`key` 和 `val`) 添加到事务批次 (`WriteBatch`) 中。 +2. **解析字段值更新索引**: + 遍历 `fieldWithIndex_`(即需要建立索引的字段列表),在 `val` 中提取对应字段的值。如果字段值非空,则构建索引键值对,例如 `fieldName:fieldValue -> key`,并将该索引插入到 `indexDb_` 中。 +3. **事务提交**: + 利用 `WriteBatch` 将主数据库写入操作和索引更新操作一并提交。通过 `this->Write` 确保事务的原子性,即所有写入操作成功或全部失败。 + +```cpp +WriteBatch batch; // 创建事务 +batch.Put(key, val); // 写入主数据库 + +for (const auto& field : fieldWithIndex_) { + std::string fieldValue = 提取字段值(val, field); + if (!fieldValue.empty()) { + std::string indexKey + std::string indexValue + batch.Put(indexKey, indexValue); // 添加索引写入操作 + } +} + +Status s = this->Write(o, &batch); // 事务提交 +``` + +#### **删除数据的实现** + +1. **主数据删除**: + 在 `Delete` 方法中,将主数据库的删除操作加入事务。 +2. **解析字段值删除索引**: + 遍历 `fieldWithIndex_`,根据 `key` 提取字段值,并构建对应的索引键。例如,从 `key` 提取 `fieldValue`,构建索引键 `fieldName:fieldValue`,将其从 `indexDb_` 中删除。 +3. **事务提交**: + 将主数据库删除操作和索引删除操作合并为一个事务,通过 `this->Write` 一并提交。 + +```cpp +WriteBatch batch; // 创建事务 +batch.Delete(key); // 删除主数据库记录 + +for (const auto& field : fieldWithIndex_) { + std::string fieldValue = 提取字段值(key, field); + if (!fieldValue.empty()) { + std::string indexKey ; + batch.Delete(indexKey); // 添加索引删除操作 + } +} + +Status s = this->Write(options, &batch); // 事务提交 +``` + +#### **事务处理的优点** + +- **原子性**:通过 `WriteBatch`,可以将主数据的更新和索引的更新/删除操作捆绑为一个原子事务,避免因系统崩溃导致的不一致性问题。 +- **简化代码逻辑**:事务批次的使用使得多步骤操作整合到统一的提交过程,降低了代码的复杂性。 +- **一致性保障**:如果某个步骤失败,整个事务会回滚,保证数据库状态的一致性。 + +通过这种设计,我们实现了主数据库和二级索引的紧密联动,确保了在插入和删除操作中的数据一致性。 + + +--- + ### 示例流程 1. 插入原始数据: ``` diff --git a/db/db_impl.cc b/db/db_impl.cc index 1d42842..54cfc61 100644 --- a/db/db_impl.cc +++ b/db/db_impl.cc @@ -1236,67 +1236,79 @@ void DBImpl::ReleaseSnapshot(const Snapshot* snapshot) { // Convenience methods Status DBImpl::Put(const WriteOptions& o, const Slice& key, const Slice& val) { //ToDo + WriteBatch batch; // 创建事务 Status s; - // 遍历fieldWithIndex_,检查是否需要更新索引 - for (const auto& field : fieldWithIndex_) { - // 提取字段值 - size_t field_pos = val.ToString().find(field + ":"); - if (field_pos != std::string::npos) { - size_t value_start = field_pos + field.size() + 1; // 跳过 "fieldName:" - size_t value_end = val.ToString().find("|", value_start); // 查找下一个分隔符 - if (value_end == std::string::npos) { - value_end = val.ToString().size(); - } - std::string fieldValue = val.ToString().substr(value_start, value_end - value_start); + // 在主数据库写入数据 + batch.Put(key, val); + + // 遍历fieldWithIndex_,检查是否需要更新索引 + for (const auto& field : fieldWithIndex_) { + size_t field_pos = val.ToString().find(field + ":"); + if (field_pos != std::string::npos) { + size_t value_start = field_pos + field.size() + 1; // 跳过 "fieldName:" + size_t value_end = val.ToString().find("|", value_start); // 查找下一个分隔符 + if (value_end == std::string::npos) { + value_end = val.ToString().size(); + } + std::string fieldValue = val.ToString().substr(value_start, value_end - value_start); - if (!fieldValue.empty()) { - // 构建索引键 (例如:fieldValue -> key) - std::string indexKey = field + ":" + fieldValue; - std::string indexValue = key.ToString(); - - // 在indexDb_中插入二级索引 - s = indexDb_->Put(o, Slice(indexKey), Slice(indexValue)); - if (!s.ok()) { - return s; - } - } - } - } + if (!fieldValue.empty()) { + std::string indexKey = field + ":" + fieldValue; + std::string indexValue = key.ToString(); + + // 将索引插入操作加入事务 + batch.Put(Slice(indexKey), Slice(indexValue)); + } + } + } + + // 使用 `this->Write` 提交事务 + s = this->Write(o, &batch); + if (!s.ok()) { + return s; + } + return Status::OK(); //ToDo end - return DB::Put(o, key, val); + //return DB::Put(o, key, val); } Status DBImpl::Delete(const WriteOptions& options, const Slice& key) { //ToDo + WriteBatch batch; // 创建事务 Status s; - // 遍历fieldWithIndex_,检查是否需要删除索引 - for (const auto& field : fieldWithIndex_) { - // 假设key是包含字段的格式,提取字段值 - size_t field_pos = key.ToString().find(field + ":"); - if (field_pos != std::string::npos) { - size_t value_start = field_pos + field.size() + 1; - size_t value_end = key.ToString().find("|", value_start); - if (value_end == std::string::npos) { - value_end = key.ToString().size(); - } - std::string fieldValue = key.ToString().substr(value_start, value_end - value_start); + // 在主数据库删除数据 + batch.Delete(key); + + // 遍历fieldWithIndex_,检查是否需要删除索引 + for (const auto& field : fieldWithIndex_) { + size_t field_pos = key.ToString().find(field + ":"); + if (field_pos != std::string::npos) { + size_t value_start = field_pos + field.size() + 1; + size_t value_end = key.ToString().find("|", value_start); + if (value_end == std::string::npos) { + value_end = key.ToString().size(); + } + std::string fieldValue = key.ToString().substr(value_start, value_end - value_start); - if (!fieldValue.empty()) { - // 构建索引键 (例如:fieldValue -> key) - std::string indexKey = field + ":" + fieldValue; - - // 从indexDb_中删除二级索引 - s = indexDb_->Delete(options, Slice(indexKey)); - if (!s.ok()) { - return s; - } - } - } - } + if (!fieldValue.empty()) { + std::string indexKey = field + ":" + fieldValue; + + // 将索引删除操作加入事务 + batch.Delete(Slice(indexKey)); + } + } + } + + // 使用 `this->Write` 提交事务 + s = this->Write(options, &batch); + if (!s.ok()) { + return s; + } + return Status::OK(); //ToDo end - return DB::Delete(options, key); + //return DB::Delete(options, key); } Status DBImpl::Write(const WriteOptions& options, WriteBatch* updates) {