|
|
@ -1,13 +1,56 @@ |
|
|
|
# 实验报告:在 LevelDB 中构建二级索引的设计与实现 |
|
|
|
|
|
|
|
## 实验目的 |
|
|
|
--- |
|
|
|
|
|
|
|
## 目录 |
|
|
|
|
|
|
|
- [一,实验目的](#一实验目的) |
|
|
|
- [二,背景简述](#二背景简述) |
|
|
|
- [1. **背景与需求**](#1-背景与需求) |
|
|
|
- [2. **设计目标**](#2-设计目标) |
|
|
|
- [三,`LevelDB`二级索引设计思路](#三leveldb二级索引设计思路) |
|
|
|
- [1. **设计结构**](#1-设计结构) |
|
|
|
- [2.1 **核心组件**](#21-核心组件) |
|
|
|
- [2.2 **数据结构**](#22-数据结构) |
|
|
|
- [2.3 **字段管理**](#23-字段管理) |
|
|
|
- [2.4 **数据结构关系图**](#24-数据结构关系图) |
|
|
|
- [3. **计划实现细节**](#3-计划实现细节) |
|
|
|
- [3.1 数据插入流程 (`Put`)](#31-数据插入流程-put) |
|
|
|
- [3.2 数据删除流程 (`Delete`)](#32-数据删除流程-delete) |
|
|
|
- [3.3 查询流程](#33-查询流程) |
|
|
|
- [4. **动态索引管理**](#4-动态索引管理) |
|
|
|
- [4.1 动态创建索引](#41-动态创建索引) |
|
|
|
- [4.2 动态删除索引](#42-动态删除索引) |
|
|
|
- [5. **事务与回滚机制**](#5-事务与回滚机制) |
|
|
|
- [5.1 事务设计](#51-事务设计) |
|
|
|
- [5.2 回滚机制](#52-回滚机制) |
|
|
|
- [6. **设计的优势**](#6-设计的优势) |
|
|
|
- [7. **未来优化方向**](#7-未来优化方向) |
|
|
|
- [四,具体实现](#四具体实现) |
|
|
|
- [1. **DBImpl 类的设计**](#1-dbimpl-类的设计) |
|
|
|
- [2. **二级索引的创建**](#2-二级索引的创建) |
|
|
|
- [3. **二级索引的查询**](#3-二级索引的查询) |
|
|
|
- [4. **二级索引的删除**](#4-二级索引的删除) |
|
|
|
- [5. **`Put` 和 `Delete` 方法的内容**](#5-put-和-delete-方法的内容) |
|
|
|
- [6. 数据插入与删除的原子性实现](#6-数据插入与删除的原子性实现) |
|
|
|
- [五,性能测试](#五性能测试) |
|
|
|
- [1. 测试流程](#1-测试流程) |
|
|
|
- [2. 结果分析](#2-结果分析) |
|
|
|
- [总结](#总结) |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
## 一,实验目的 |
|
|
|
在 LevelDB 的基础上设计和实现一个支持二级索引的功能,优化特定字段的查询效率。通过此功能,用户能够根据字段值高效地检索对应的数据记录,而不需要遍历整个数据库。 |
|
|
|
|
|
|
|
--- |
|
|
|
|
|
|
|
## 实现思路 |
|
|
|
## 二,背景简述 |
|
|
|
|
|
|
|
#### 1. **背景与需求** |
|
|
|
`LevelDB` 是一个高性能、轻量级的键值存储引擎,但其查询能力仅限于主键。在许多应用场景中,需要支持基于非主键字段的高效查询(例如按用户 ID 或类别查询数据)。因此,设计并实现二级索引系统,为 LevelDB 增强多字段查询能力,成为一个核心需求。 |
|
|
|
|
|
|
|
### 1. **二级索引的概念** |
|
|
|
### **二级索引的概念** |
|
|
|
二级索引是一种额外的数据结构,用于加速某些特定字段的查询。在 LevelDB 中,键值对的存储是以 `key:value` 的形式。通过创建二级索引,我们将目标字段的值与原始 `key` 建立映射关系,存储在独立的索引数据库中,从而支持基于字段值的快速查询。 |
|
|
|
|
|
|
|
例如,原始数据如下: |
|
|
@ -23,14 +66,149 @@ name:Customer#000000001-k_3 : k_3 |
|
|
|
name:Customer#000000002-k_2 : k_2 |
|
|
|
``` |
|
|
|
|
|
|
|
### 2. **设计目标** |
|
|
|
- **创建索引**:扫描数据库中的所有记录,基于指定字段提取值,并将字段值和原始 `key` 编码后写入二级索引数据库 `indexDb_`。 |
|
|
|
- **查询索引**:在二级索引数据库中快速定位字段值对应的原始 `key`。 |
|
|
|
- **删除索引**:移除二级索引数据库中所有与目标字段相关的条目。 |
|
|
|
|
|
|
|
--- |
|
|
|
|
|
|
|
## 具体实现 |
|
|
|
#### 2. **设计目标** |
|
|
|
- **高效性**:二级索引查询性能接近主键查询。 |
|
|
|
- **一致性**:保证主数据库与二级索引的一致性,支持事务和回滚机制。 |
|
|
|
- **灵活性**:允许用户指定需要创建索引的字段,支持动态创建和删除索引。 |
|
|
|
- **易用性**:通过统一接口隐藏索引管理的复杂性,保持与原始 LevelDB 类似的用户体验。 |
|
|
|
|
|
|
|
--- |
|
|
|
|
|
|
|
## 三,`LevelDB`二级索引设计思路 |
|
|
|
|
|
|
|
#### 1. **设计结构** |
|
|
|
在 ·LevelDB· 的基础上扩展,补充并实现以下组件: |
|
|
|
|
|
|
|
##### 2.1 **核心组件** |
|
|
|
1. **主数据库(DBImpl)**: |
|
|
|
存储用户原始数据的键值对,提供 `Put`、`Delete` 和 `Get` 方法。 |
|
|
|
|
|
|
|
2. **二级索引数据库(indexDb_)**: |
|
|
|
专门存储索引数据,键为 `fieldName:fieldValue`,值为主数据库中对应的主键。 |
|
|
|
|
|
|
|
##### 2.2 **数据结构** |
|
|
|
1. **主数据库键格式**: |
|
|
|
使用字符串表示,例如:`userID:123|name:JohnDoe`,包含多个字段。 |
|
|
|
|
|
|
|
2. **索引键格式**: |
|
|
|
例如:`userID:123`,方便通过字段值快速查询。 |
|
|
|
|
|
|
|
3. **映射关系**: |
|
|
|
二级索引数据库的值存储主数据库的主键,用于指向完整数据记录。 |
|
|
|
|
|
|
|
##### 2.3 **字段管理** |
|
|
|
- `fieldWithIndex_`:一个集合,用于管理需要创建索引的字段,支持动态增删。 |
|
|
|
|
|
|
|
#### 2.4 **数据结构关系图** |
|
|
|
|
|
|
|
以下是主数据库和二级索引数据库的逻辑关系示意图: |
|
|
|
|
|
|
|
``` |
|
|
|
lessCopy code 主数据库 (DBImpl) |
|
|
|
+-------------------------------------------------------+ |
|
|
|
| key | value | |
|
|
|
+-------+-----------------------------------------------+ |
|
|
|
| k_1 | name:Customer#000000001|address:IVhzIApeRb|.. | |
|
|
|
| k_2 | name:Customer#000000002|address:XSTf4,NCwDVaW | |
|
|
|
+-------+-----------------------------------------------+ |
|
|
|
|
|
|
|
二级索引数据库 (indexDb_) |
|
|
|
+----------------------------------------+-------------+ |
|
|
|
| indexKey | indexValue | |
|
|
|
+----------------------------------------+-------------+ |
|
|
|
| name:Customer#000000001-k_1 | k_1 | |
|
|
|
| name:Customer#000000001-k_3 | k_3 | |
|
|
|
| name:Customer#000000002-k_2 | k_2 | |
|
|
|
+----------------------------------------+-------------+ |
|
|
|
|
|
|
|
数据关联关系 |
|
|
|
主数据库 <-------------> 二级索引数据库 |
|
|
|
(key) 映射到字段值 (fieldName:fieldValue) |
|
|
|
``` |
|
|
|
|
|
|
|
--- |
|
|
|
|
|
|
|
#### 3. **计划实现细节** |
|
|
|
|
|
|
|
##### 3.1 数据插入流程 (`Put`) |
|
|
|
1. 用户调用 `Put` 将数据插入到主数据库。 |
|
|
|
2. 从用户数据中解析需要创建索引的字段及其值。 |
|
|
|
3. 构造二级索引的键值对,并插入到二级索引数据库中。 |
|
|
|
4. 如果任意数据库的写入失败,通过事务回滚保证一致性。 |
|
|
|
|
|
|
|
**关键点**: |
|
|
|
- 需要先提交主数据库事务,再提交二级索引数据库事务。 |
|
|
|
- 索引更新时要考虑覆盖旧索引的场景。 |
|
|
|
|
|
|
|
##### 3.2 数据删除流程 (`Delete`) |
|
|
|
1. 用户调用 `Delete` 从主数据库删除数据。 |
|
|
|
2. 在删除前,读取原始数据以提取相关字段的索引键。 |
|
|
|
3. 删除主数据库中的数据。 |
|
|
|
4. 删除对应的二级索引键。 |
|
|
|
5. 如果任意数据库的删除失败,通过事务回滚恢复数据。 |
|
|
|
|
|
|
|
**关键点**: |
|
|
|
- 删除前必须读取原始数据以提取相关索引信息。 |
|
|
|
- 回滚时需恢复原始主数据库记录。 |
|
|
|
|
|
|
|
##### 3.3 查询流程 |
|
|
|
1. 用户指定查询条件(字段名和字段值)。 |
|
|
|
2. 从二级索引数据库中获取与查询条件匹配的主键。 |
|
|
|
3. 使用主键从主数据库获取完整记录。 |
|
|
|
|
|
|
|
--- |
|
|
|
|
|
|
|
#### 4. **动态索引管理** |
|
|
|
##### 4.1 动态创建索引 |
|
|
|
- 提供接口 `CreateIndex(fieldName)`,用于动态为字段创建索引: |
|
|
|
- 遍历主数据库的所有记录。 |
|
|
|
- 根据指定字段生成索引键值对并插入到二级索引数据库。 |
|
|
|
- 将字段名添加到 `fieldWithIndex_` 集合。 |
|
|
|
|
|
|
|
##### 4.2 动态删除索引 |
|
|
|
- 提供接口 `DeleteIndex(fieldName)`,用于动态删除字段索引: |
|
|
|
- 遍历二级索引数据库,删除与该字段相关的索引键。 |
|
|
|
- 从 `fieldWithIndex_` 集合中移除字段名。 |
|
|
|
|
|
|
|
--- |
|
|
|
|
|
|
|
#### 5. **事务与回滚机制** |
|
|
|
##### 5.1 事务设计 |
|
|
|
- 使用 `WriteBatch` 封装多个操作(如 `Put` 和 `Delete`)。 |
|
|
|
- 在主数据库和二级索引数据库上分别维护独立事务。 |
|
|
|
|
|
|
|
##### 5.2 回滚机制 |
|
|
|
- 在主数据库操作失败时直接返回错误,不影响索引。 |
|
|
|
- 在二级索引操作失败时,回滚主数据库的写入或删除操作: |
|
|
|
- 对 `Put` 操作,删除已插入的数据。 |
|
|
|
- 对 `Delete` 操作,恢复已删除的数据。 |
|
|
|
|
|
|
|
--- |
|
|
|
|
|
|
|
#### 6. **设计的优势** |
|
|
|
1. **数据一致性强**:通过事务和回滚机制,确保主数据库和二级索引数据库始终保持一致。 |
|
|
|
2. **查询高效**:支持基于字段的快速查询,二级索引性能接近主键查询。 |
|
|
|
3. **易于扩展**:动态索引创建和删除机制使得系统适应性更强。 |
|
|
|
4. **兼容性好**:用户接口保持与原始 LevelDB 类似,降低学习成本。 |
|
|
|
|
|
|
|
--- |
|
|
|
|
|
|
|
#### 7. **未来优化方向** |
|
|
|
1. **多字段联合索引**:支持对多个字段的联合索引,提高复杂查询的效率。 |
|
|
|
2. **异步索引更新**:通过异步任务队列优化索引构建和更新的性能。 |
|
|
|
3. **空间优化**:采用压缩技术减少二级索引数据库的存储占用。 |
|
|
|
4. **并发支持**:优化写锁机制以提高高并发场景下的性能。 |
|
|
|
|
|
|
|
--- |
|
|
|
|
|
|
|
这套设计在功能性、一致性和性能之间达到了较好的平衡,能够为 LevelDB 提供高效、灵活的二级索引支持,同时保持其原有的高性能特性。 |
|
|
|
|
|
|
|
------ |
|
|
|
|
|
|
|
## 四,具体实现 |
|
|
|
|
|
|
|
### 1. **DBImpl 类的设计** |
|
|
|
在 LevelDB 的核心类 `DBImpl` 中,增加了对二级索引的支持,包括: |
|
|
@ -281,7 +459,8 @@ Status DBImpl::Delete(const WriteOptions& options, const Slice& key) { |
|
|
|
} |
|
|
|
``` |
|
|
|
|
|
|
|
#### 6.数据插入与删除的原子性实现 |
|
|
|
### 6. **数据插入与删除的原子性实现** |
|
|
|
|
|
|
|
通过以下策略确保数据插入与删除操作的原子性: |
|
|
|
1. **事务机制**: |
|
|
|
- 主数据库和二级索引数据库的写入操作分别使用 `WriteBatch` 封装,并在提交前记录必要的数据以支持回滚。 |
|
|
@ -293,8 +472,11 @@ Status DBImpl::Delete(const WriteOptions& options, const Slice& key) { |
|
|
|
这种设计确保了主数据库和二级索引数据库的一致性,即便在部分写入或删除操作失败的情况下,仍能通过回滚机制保证数据的完整性和原子性。 |
|
|
|
|
|
|
|
--- |
|
|
|
## 五,性能测试 |
|
|
|
### 1.测试流程 |
|
|
|
|
|
|
|
**单元测试:** |
|
|
|
|
|
|
|
### 示例流程 |
|
|
|
1. 插入原始数据: |
|
|
|
``` |
|
|
|
k_1 : name:Customer#000000001|address:IVhzIApeRb|phone:25-989-741-2988 |
|
|
@ -315,9 +497,13 @@ Status DBImpl::Delete(const WriteOptions& options, const Slice& key) { |
|
|
|
|
|
|
|
![error](Report/png/test_result.png) |
|
|
|
|
|
|
|
**性能测试:** |
|
|
|
|
|
|
|
**Benchmark测试运行结果及分析:** |
|
|
|
![error](./Report/png/benchmark.png) |
|
|
|
|
|
|
|
### 2.结果分析 |
|
|
|
|
|
|
|
1. **插入时间 (Insertion time for 100001 entries: 516356 microseconds)** |
|
|
|
|
|
|
|
这个时间(516356 微秒,约 516 毫秒)看起来是合理的,特别是对于 100001 条记录的插入操作。如果数据插入过程没有特别复杂的计算或操作,这个时间应该是正常的,除非硬件性能或其他因素导致延迟。 |
|
|
|