至此,將插入流程以及壓縮流程都已介紹完畢了,本篇主要介紹查詢流程。
一、查詢流程
首先來看一下查詢接口具體實現內容:
/**
* 查詢
* @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具體實現。