leveldb深度剖析-查询流程

至此,将插入流程以及压缩流程都已介绍完毕了,本篇主要介绍查询流程。

一、查询流程

首先来看一下查询接口具体实现内容:

/**
 * 查询
 * @param options 查询选项
 * @param key 查询key
 * @param value 输出参数 如果找到则赋值给value
 */
Status DBImpl::Get(const ReadOptions& options,
                   const Slice& key,
                   std::string* value) {
  Status s;
  MutexLock l(&mutex_);
  SequenceNumber snapshot;
  if (options.snapshot != NULL) {
    snapshot = reinterpret_cast<const SnapshotImpl*>(options.snapshot)->number_;
  } else {
    snapshot = versions_->LastSequence();//查询操作 不增加序列号
  }

  MemTable* mem = mem_;
  MemTable* imm = imm_;
  Version* current = versions_->current();
  mem->Ref();
  if (imm != NULL) imm->Ref();
  current->Ref();

  bool have_stat_update = false;
  Version::GetStats stats;

  // Unlock while reading from files and memtables
  {
    mutex_.Unlock();
    // First look in the memtable, then in the immutable memtable (if any).
    LookupKey lkey(key, snapshot);
    if (mem->Get(lkey, value, &s)) {//查找mem table
      // Done
    } else if (imm != NULL && imm->Get(lkey, value, &s)) {//查找immutable mem table
      // Done
    } else {
      // 查找ldb文件 最新的版本信息Version都保存在current中
      s = current->Get(options, lkey, value, &stats);
      have_stat_update = true;
    }
    mutex_.Lock();
  }
  //是否需要启动压缩流程
  if (have_stat_update && current->UpdateStats(stats)) {
    MaybeScheduleCompaction();//表示由于某个文件seek次数过多需要合并
  }
  mem->Unref();
  if (imm != NULL) imm->Unref();
  current->Unref();
  return s;
}

查找顺序按照:MemTable -> Immutable MemTable -> ldb文件(sst文件),只要有一个流程中找到则返回。对于查找MemTable流程可参考《leveldb深度剖析-MemTable》。下面介绍在ldb中查询流程

二、ldb查询流程

/**
 * 进入此方法 说明在memtable中没有找到 因此需要在文件中查找
 * @param options 查询流程选项
 * @param k 查询关键字
 * @param value 查询value 输出参数
 * @param 查询结果 输出参数
 */
Status Version::Get(const ReadOptions& options,
                    const LookupKey& k,
                    std::string* value,
                    GetStats* stats) {
  Slice ikey = k.internal_key();
  Slice user_key = k.user_key();
  const Comparator* ucmp = vset_->icmp_.user_comparator();// 比较器
  Status s;

  stats->seek_file = NULL;
  stats->seek_file_level = -1;
  FileMetaData* last_file_read = NULL;
  int last_file_read_level = -1;

  // We can search level-by-level since entries never hop across
  // levels.  Therefore we are guaranteed that if we find data
  // in an smaller level, later levels are irrelevant.
  // 这个循环 是确定文件范围
  std::vector<FileMetaData*> tmp;
  FileMetaData* tmp2;
  for (int level = 0; level < config::kNumLevels; level++) {// 循环遍历每一层文件
    size_t num_files = files_[level].size();
    if (num_files == 0) continue;

    // Get the list of files to search in this level
    FileMetaData* const* files = &files_[level][0];
    if (level == 0) {// level0文件中 key是有重叠的
      // Level-0 files may overlap each other.  Find all files that
      // overlap user_key and process them in order from newest to oldest.
      tmp.reserve(num_files);
      for (uint32_t i = 0; i < num_files; i++) {//循环遍历 当前层次中每个文件
        FileMetaData* f = files[i];
        if (ucmp->Compare(user_key, f->smallest.user_key()) >= 0 &&
            ucmp->Compare(user_key, f->largest.user_key()) <= 0) {
          tmp.push_back(f);
        }
      }
      if (tmp.empty()) continue;//说明key不存在当前level中所有文件
      // 
      std::sort(tmp.begin(), tmp.end(), NewestFirst);
      files = &tmp[0];
      num_files = tmp.size();
    } else {//非level0文件
      // Binary search to find earliest index whose largest key >= ikey.
      uint32_t index = FindFile(vset_->icmp_, files_[level], ikey);
      if (index >= num_files) {//表示没有找到
        files = NULL;
        num_files = 0;
      } else {//当前文件中最大值比key要大 因此只能说明待查找的key可能存在当前文件中 
        tmp2 = files[index];
        if (ucmp->Compare(user_key, tmp2->smallest.user_key()) < 0) {
          // All of "tmp2" is past any data for user_key
          // 要查找的key 比当前文件中最小的key 还要小 说明不在此文件中
          files = NULL;
          num_files = 0;
        } else {// 待查找key 都已经满足文件最大值和最小值  但是此时仍然不能确定是否存在
          files = &tmp2;
          num_files = 1;
        }
      }
    }

    //到这里说明:我们要查找的文件范围已经确定了  具体有没有我们需要的key则需要进一步查询
    //num_files已经重新赋值 当num_files不为0 只能说明待查找key可能存在文件中 需要进一步对比
    for (uint32_t i = 0; i < num_files; ++i) {
      if (last_file_read != NULL && stats->seek_file == NULL) {
        // We have had more than one seek for this read.  Charge the 1st file.
        // 该分支如果进入了 只会进入一次  表示第一个文件不存在我们要查找的key  相当于没有命中 那么我们
        // 就要将其记录下来 并且在UpdateStats函数中对器allow_seek进行减一操作
        stats->seek_file = last_file_read;
        stats->seek_file_level = last_file_read_level;
      }

      FileMetaData* f = files[i];
      last_file_read = f;
      last_file_read_level = level;

      Saver saver;
      saver.state = kNotFound;
      saver.ucmp = ucmp; //比较器
      saver.user_key = user_key;
      saver.value = value; //输出参数 保存value返回给用户
      //构建 table cache 基于局部性原理+LRU算法
      s = vset_->table_cache_->Get(options, f->number, f->file_size,
                                   ikey, &saver, SaveValue);
      if (!s.ok()) {//异常
        return s;
      }
      switch (saver.state) {
        case kNotFound:
          break;      // Keep searching in other files 没有找到继续查找
        case kFound:
          return s;//找到直接返回
        case kDeleted:
          s = Status::NotFound(Slice());  // Use empty error message for speed
          return s;
        case kCorrupt:
          s = Status::Corruption("corrupted key for ", user_key);
          return s;
      }
    }
  }

  return Status::NotFound(Slice());  // Use an empty error message for speed
}

说明:

1)该函数有两个for循环,第一个for循环按照层次遍历文件,凡是符合这个条件: smallest.user_key <= 待查找user_key <= largest.user_key的文件都是我们需要查询的。

2) 第二个for循环,按照第一个for循环确定出来的文件,进行深入查询对比。这里采用cache思想来提升性能。

3)leveldb采用LRU方式管理open的文件,table_cache->Get返回后判断saver状态,如果是kFound/kDeleted则说明找到了直接退出,否则继续查找下一个文件。

三、总结

查找流程其实不是特别复杂,主要逻辑还是在于TableCache这部分。由于TableCache功能比较独立,这里单独放一篇博客,来详细说明TableCache具体实现。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值