SysCache的部分匹配机制
-
- 精确匹配査找
-
- 搜索的主力代码:
-
- 在第一次进入SearchCatCache函数时,由于系统表还没有加载到SysCache的相关结构体中,需要调用一次CatalogCacheInitializeCache(cache)。
- 计算哈希值,得到哈希桶
- SearchCatCache函数途中有调用函数CatalogCacheCompareTuple
- 如果迭代结束都没有找到匹配的元组,就到了SearchCatCacheMiss函数,不只是返回缓存查找失败。
- 对物理表进行扫描时需要调用函数IndexScanOK
- 将元组放入哈希桶,或者创建负元组时,需要调用函数CatalogCacheCreateEntry
- 调用RehashCatCache
- 需要调用函数CatCacheCopyKeys
- 部分查找
catcache代码位于src/backend/utils/cache/catcache.c,包含了对SysCache结构体的初始化和数据结构之间指针关系的链接以及操作。
在CatCache中査找元组
在CatCache中查找元组有两种方式:精确匹配SearchCatCache和部分匹配SearchCatcacheList。前者用于给定CatCache所需的所有键值,并返回CatCache中能完全匹配这个键值的元组;而后者只需要给出部分键值,并将部分匹配的元组以一个CatCList的方式返回。
精确匹配査找
由函数SearchCatCache函数实现,其函数如下:
SearchCatCache(CatCache *cache,//要查找的cacheID
Datum v1, //v1\v2\v3\v4都用于查找元组的键值,分别对应该Cache中记录的元组搜索键。
Datum v2,
Datum v3,
Datum v4)
{
return SearchCatCacheInternal(cache, cache->cc_nkeys, v1, v2, v3, v4);//调用SearchCatCache函数进行查找
}
当参数数量特定时,编译器会选择SearchCatCacheN()内联主体和展开循环,使它们比SearchCatCache()快一些。
//第一种,一个键的部分搜索
HeapTuple
SearchCatCache1(CatCache *cache,Datum v1)
{
return SearchCatCacheInternal(cache, 1, v1, 0, 0, 0);}
//第二种,两个键的部分搜索
HeapTuple
SearchCatCache2(CatCache *cache,Datum v1, Datum v2)
{
return SearchCatCacheInternal(cache, 2, v1, v2, 0, 0);}
//第三种,三个键的部分搜索
HeapTuple
SearchCatCache3(CatCache *cache,Datum v1, Datum v2, Datum v3)
{
return SearchCatCacheInternal(cache, 3, v1, v2, v3, 0);}
//第四种,四个键的部分搜索(和精确查找没有区别)
HeapTuple
SearchCatCache4(CatCache *cache,Datum v1, Datum v2, Datum v3, Datum v4)
{
return SearchCatCacheInternal(cache, 4, v1, v2, v3, v4);}
调用SearchCatCacheInternal函数时,把没有的key用0代替。其余步骤都和精确搜索一样。
搜索的主力代码:
static inline HeapTuple
SearchCatCacheInternal(CatCache *cache,int nkeys,Datum v1,Datum v2,Datum v3,Datum v4)
{
Datum arguments[CATCACHE_MAXKEYS];//参数数组,用于存放键值
uint32 hashValue;
Index hashIndex;
dlist_iter iter;
dlist_head *bucket;
CatCTup *ct;
/* 确保我们在一个精确的行动,即使这最终是一个缓存命中 */
Assert(IsTransactionState());
Assert(cache->cc_nkeys == nkeys);
/* 每个缓存的一次性启动开销 */
if (unlikely(cache->cc_tupdesc == NULL))
CatalogCacheInitializeCache(cache);
#ifdef CATCACHE_STATS
cache->cc_searches++;
#endif
/* 初始化局部参数数组 */
arguments[0] = v1;
arguments[1] = v2;
arguments[2] = v3;
arguments[3] = v4;
/* 计算出哈希值
* 根据哈希值找到对应的哈希桶序号
*/
hashValue = CatalogCacheComputeHashValue(cache, nkeys, v1, v2, v3, v4);
hashIndex = HASH_INDEX(hashValue, cache->cc_nbuckets);
/*
* 根据刚才得到的哈希桶序号获取哈希桶
* Note: 在这里可以使用dlist_foreach,因为即使我们在循环中修改了dlist,之后也不会继续循环。
*/
bucket = &cache->cc_bucket[hashIndex];
dlist_foreach(iter, bucket)//对哈希桶进行迭代
{
ct = dlist_container(CatCTup, cache_elem, iter.cur);
if (ct->dead)
continue; /* 忽略死亡的元组 */
if (ct->hash_value != hashValue)
continue; /* 哈希值错误则快速跳过 */
if (!CatalogCacheCompareTuple(cache, nkeys, ct->keys, arguments))//如果键值不匹配则跳过
continue;
/*
* 能够执行到这里说明在cache中找到了匹配的元组,将该元组移动到该哈希桶的链表头,这
* 是为了加快后续搜索。
* 这种采取了将最近访问最频繁的元素放在链表前部的做法能够使得频繁访问的元素得到快速
* 访问。
*/
dlist_move_head(bucket, &ct->cache_elem);
/* 对该元组进行判断 */
if (!ct->negative)//如果该元组不是负元组
{
ResourceOwnerEnlargeCatCacheRefs(CurrentResourceOwner);
ct->refcount++;//将它的引用技术加一
ResourceOwnerRememberCatCacheRef(CurrentResourceOwner, &ct->tuple);
CACHE_elog(DEBUG2, "SearchCatCache(%s): found in bucket %d",
cache->cc_relname, hashIndex);
#ifdef CATCACHE_STATS
cache->cc_hits++;//缓存命中次数加一
#endif
return &ct->tuple;//返回该元组
}
else//如果是负元组
{
CACHE_elog(DEBUG2, "SearchCatCache(%s): found neg entry in bucket %d",
cache->cc_relname, hashIndex);
#ifdef CATCACHE_STATS
cache->cc_neg_hits++;//缓存的负元组命中加一
#endif
return NULL;//返回未找到
}
}
//如果执行到这里,说明迭代结束都没有找到匹配的元组
return SearchCatCacheMiss(cache, nkeys, hashValue, hashIndex, v1, v2, v3, v4);//缓存查找失败
}
在第一次进入SearchCatCache函数时,由于系统表还没有加载到SysCache的相关结构体中,需要调用一次CatalogCacheInitializeCache(cache)。
/*
* 初始化目录缓存
* 这个函数对catcache进行最终的初始化:获取元组描述符并设置哈希和等式函数链接。
*/
#ifdef CACHEDEBUG
#define CatalogCacheInitializeCache_DEBUG1 \ elog(DEBUG2, "CatalogCacheInitializeCache: cache @%p rel=%u", cache, \ cache->cc_reloid)
#define CatalogCacheInitializeCache_DEBUG2 \ do { \
if (cache->cc_keyno[i] > 0) { \
elog(DEBUG2, "CatalogCacheInitializeCache: load %d/%d w/%d, %u", \
i+1, cache->cc_nkeys, cache->cc_keyno[i], \
TupleDescAttr(tupdesc, cache->cc_keyno[i] - 1)->atttypid); \
} else { \
elog(DEBUG2, "CatalogCacheInitializeCache: load %d/%d w/%d", \
i+1, cache->cc_nkeys, cache->cc_keyno[i]); \
} \
} while(0)
#else
#define CatalogCacheInitializeCache_DEBUG1
#define CatalogCacheInitializeCache_DEBUG2
#endif
/*
* 调用 CatalogCacheInitializeCache 初始化cache相关信息
* (这个时候不会查数据的,但是会把字段类型,hash函数什么的设置好
* 所以这个函数中会调用 heap_open 读取字段类型)
*/
static void
CatalogCacheInitializeCache(CatCache *cache)
{
Relation relation;//存放表
MemoryContext oldcxt;//存储上下文
TupleDesc tupdesc; //永久缓存存储
int i; //用于循环调用,初始化缓存的关键信息
CatalogCacheInitializeCache_DEBUG1;
//打开对应的系统表
relation = table_open(cache->cc_reloid, AccessShareLock);
/* 切换到缓存上下文,这样我们的分配不会在事务结束时消失 */
Assert(CacheMemoryContext != NULL);
oldcxt = MemoryContextSwitchTo(CacheMemoryContext);
/* 将Relcache的元组描述符复制到永久缓存存储中 */
tupdesc = CreateTupleDescCopyConstr(RelationGetDescr(relation));
/* 也保存关系的名称和relisshared标志(cc_relname仅用于调试目的) */
cache->cc_relname = pstrdup(RelationGetRelationName(relation));
cache->cc_relisshared = RelationGetForm(relation)->relisshared;
/* 返回调用者的内存上下文并关闭rel */
MemoryContextSwitchTo(oldcxt);
table_close(relation, AccessShareLock);
CACHE_elog(DEBUG2, "CatalogCacheInitializeCache: %s, %d keys",
cache->cc_relname, cache->cc_nkeys);
/* 初始化缓存的关键信息 */
for (i = 0; i < cache->cc_nkeys; ++i)
{
Oid keytype;
RegProcedure eqfunc;
CatalogCacheInitializeCache_DEBUG2;
if (cache->cc_keyno[i] > 0)
{
Form_pg_attribute attr = TupleDescAttr(tupdesc,
cache->cc_keyno[i] - 1);
keytype = attr->atttypid;
/* 缓存键列应该总是NOT NULL */
Assert(attr->attnotnull);
}
else
{
if (cache->cc_keyno[i] < 0)//在缓存中不支持Sys属性
elog(FATAL, "sys attributes are not supported in caches");
keytype = OIDOID;
}
GetCCHashEqFuncs(keytype,
&cache->cc_hashfunc[i],
&eqfunc,
&cache->cc_fastequal[i]);
/* 进行相等函数的查找(我们假设这将不需要任何支持类型的目录查找) */
fmgr_info_cxt(eqfunc,
&cache->cc_skey[i].sk_func,
CacheMemoryContext);
/* 为HeapKeyTest()和堆扫描适当地初始化sk_attno */
cache->cc_skey[i]

最低0.47元/天 解锁文章
1612

被折叠的 条评论
为什么被折叠?



