PG高速缓冲区(Cache)——catcache构筑SysCache数据结构关系的骨干
catcache代码位于src/backend/utils/cache/catcache.c,包含了对SysCache结构体的初始化和数据结构之间指针关系的链接以及操作。最重要的是提供了两个函数:精确匹配SearchCatCache和部分匹配SearchCatcacheList。提供的静态函数如下,这里不进行详细分析了。
1 static uint32 CatalogCacheComputeHashValue(CatCache *cache, int nkeys, ScanKey cur_skey); 2 static uint32 CatalogCacheComputeTupleHashValue(CatCache *cache, HeapTuple tuple); 3 4 #ifdef CATCACHE_STATS 5 static void CatCachePrintStats(int code, Datum arg); 6 #endif 7 static void CatCacheRemoveCTup(CatCache *cache, CatCTup *ct); 8 static void CatCacheRemoveCList(CatCache *cache, CatCList *cl); 9 static void CatalogCacheInitializeCache(CatCache *cache); 10 static CatCTup *CatalogCacheCreateEntry(CatCache *cache, HeapTuple ntp, uint32 hashValue, Index hashIndex, bool negative); 11 static HeapTuple build_dummy_tuple(CatCache *cache, int nkeys, ScanKey skeys);
首先是提供两个初始化函数InitCatCache、InitCatCachePhase2、CatalogCacheInitializeCache。和创建内存上下文相关的函数CreateCacheMemoryContext。一些工具函数有CatalogCacheIdInvalidate、CatalogCacheCreateEntry、ResetCatalogCache、ResetCatalogCaches、CatalogCacheFlushRelation、ReleaseCatCache、build_dummy_tuple、ReleaseCatCacheList、PrepareToInvalidateCacheTuple
在CatCache中查找元组有两种方式:精确匹配SearchCatCache和部分匹配SearchCatcacheList。前者用于给定CatCache所需的所有键值,并返回CatCache中能完全匹配这个键值的元组;而后者只需要给出部分键值,并将部分匹配的元组以一个CatCList的方式返回。
1 HeapTuple SearchCatCache(CatCache *cache, Datum v1, Datum v2, Datum v3, Datum v4) { 2 ScanKeyData cur_skey[4]; 3 uint32 hashValue; 4 Index hashIndex; 5 Dlelem *elt; 6 CatCTup *ct; 7 Relation relation; 8 SysScanDesc scandesc; 9 HeapTuple ntp; 10 11 if (cache->cc_tupdesc == NULL) 12 CatalogCacheInitializeCache(cache); 13 14 memcpy(cur_skey, cache->cc_skey, sizeof(cur_skey)); 15 cur_skey[0].sk_argument = v1; 16 cur_skey[1].sk_argument = v2; 17 cur_skey[2].sk_argument = v3; 18 cur_skey[3].sk_argument = v4; 19 20 hashValue = CatalogCacheComputeHashValue(cache, cache->cc_nkeys, cur_skey); 21 hashIndex = HASH_INDEX(hashValue, cache->cc_nbuckets); 22 23 for (elt = DLGetHead(&cache->cc_bucket[hashIndex]); elt; 24 elt = DLGetSucc(elt)) { 25 bool res; 26 ct = (CatCTup *) DLE_VAL(elt); 27 if (ct->dead) 28 continue; /* ignore dead entries */ 29 if (ct->hash_value != hashValue) 30 continue; 31 HeapKeyTest(&ct->tuple, cache->cc_tupdesc, cache->cc_nkeys, cur_skey, res); 32 if (!res) 33 continue; 34 35 DLMoveToFront(&ct->cache_elem); 36 if (!ct->negative) { ResourceOwnerEnlargeCatCacheRefs(CurrentResourceOwner); 37 ct->refcount++; 38 ResourceOwnerRememberCatCacheRef(CurrentResourceOwner, &ct->tuple); 39 return &ct->tuple; 40 }else{ 41 return NULL; 42 } 43 } 44 45 relation = heap_open(cache->cc_reloid, AccessShareLock); 46 scandesc = systable_beginscan(relation, cache->cc_indexoid, IndexScanOK(cache, cur_skey), SnapshotNow, cache->cc_nkeys, cur_skey); 47 ct = NULL; 48 while (HeapTupleIsValid(ntp = systable_getnext(scandesc))) 49 { 50 ct = CatalogCacheCreateEntry(cache, ntp, 51 hashValue, hashIndex, 52 false); 53 /* immediately set the refcount to 1 */ 54 ResourceOwnerEnlargeCatCacheRefs(CurrentResourceOwner); 55 ct->refcount++; 56 ResourceOwnerRememberCatCacheRef(CurrentResourceOwner, &ct->tuple); 57 break; /* assume only one match */ 58 } 59 systable_endscan(scandesc); 60 heap_close(relation, AccessShareLock); 61 62 if (ct == NULL) 63 { 64 if (IsBootstrapProcessingMode()) 65 return NULL; 66 ntp = build_dummy_tuple(cache, cache->cc_nkeys, cur_skey); 67 ct = CatalogCacheCreateEntry(cache, ntp, 68 hashValue, hashIndex, 69 true); 70 heap_freetuple(ntp); 71 return NULL; 72 } 73 return &ct->tuple; 74 }
v1\v2\v3\v4都用于查找元组的键值,分别对应该Cache中记录的元组搜索键。在第一次进入该函数时,由于系统表还没有加载到SysCache的相关结构体中,需要调用一次CatalogCacheInitializeCache(cache)。
1)将v1\v2\v3\v4设置到cur_skey数组相应元素中,调用CatalogCacheComputeHashValue(cache, cache->cc_nkeys, cur_skey)计算哈希值,并使用HASH_INDEX宏计算哈希桶的索引,按照该索引得到该CatCache在cc_bucket数组中对应的Hash桶的下标。
2)遍历Hash桶链找到满足查询需求的Dlelem,并将其结构体中dle_val属性强制转换为CatCTup类型,使用HeapKeyTest测试缓存的tuple是否匹配输入的键。如果找到,使用DLMoveToFront将该元组放到Hash桶的首位。如果是正元组,refcount和cc_hits都加1,返回元组。如果为负元组,cc_neg_hits加1,返回NULL。
3)如果没有找到,说明SysCache中没有缓存相应的元组,需要进一步对物理系统表进行扫描,以确认要查找的元组是确实不存在还是没有缓存在CatCache中。如果扫描物理系统表能够找到满足条件的余罪女主,则需要将元组包装成Dlelem之后加入到其对应的Hash桶内链表头部,并返回元组,如果在物理系统表中找不到要查找的元组,则说明该元组确实不存在,此时构建一个只有键值但没有实际元组的负元组,并将它包装好加入到Hash桶内链表头部。
扫描物理系统表的代码如下:
1 relation = heap_open(cache->cc_reloid, AccessShareLock); 2 scandesc = systable_beginscan(relation, cache->cc_indexoid,IndexScanOK(cache, cur_skey), SnapshotNow, cache->cc_nkeys, cur_skey); 3 ct = NULL; 4 while (HeapTupleIsValid(ntp = systable_getnext(scandesc))) 5 { 6 ct = CatalogCacheCreateEntry(cache, ntp, hashValue, hashIndex, false); 7 /* immediately set the refcount to 1 */ ResourceOwnerEnlargeCatCacheRefs(CurrentResourceOwner); 8 ct->refcount++; 9 ResourceOwnerRememberCatCacheRef(CurrentResourceOwner, &ct->tuple); 10 break; /* assume only one match */ 11 } 12 systable_endscan(scandesc); 13 heap_close(relation, AccessShareLock);
部分匹配使用函数SearchCatcacheList,该函数产生一个CatCList结构,其中以链表的方式存放了在Cache中找到的元组。CatCList中的tuple字段记录的是一个负元组,它仅仅用来存放该CatCList所用到的键值,没有其他用户。CatCList中所包含的元组实际通过members字段表示的变长数据来记录,该数组的实际长度由n_members字段记录。SearchCatCacheList函数也会先计算查找键的Hash值,不过该函数首先会在CatCache的cc_lists字段中记录的CatCList链表中查找以前是否缓存了该查找键的结果,该查找过程将使用CatCList中tuple字段指向的元组与查找键进行Hash值比较。如果能够找到匹配该Hash值的CatCList,则cc_lhits加1并将该CatCList移到cc_lists所指向链表的头部,然后返回找到的CatCList。如果在CatCache中找不到CatCList,则扫描物理系统表并构建相应的CatCList并将它加入到cc_lists所指向链表的头部。
1 CatCList *SearchCatCacheList(CatCache *cache, int nkeys, Datum v1, Datum v2, Datum v3, Datum v4) { 2 ScanKeyData cur_skey[4]; 3 uint32 lHashValue; 4 Dlelem *elt; 5 CatCList *cl; 6 CatCTup *ct; 7 List *volatile ctlist; 8 ListCell *ctlist_item; 9 int nmembers; 10 bool ordered; 11 HeapTuple ntp; 12 MemoryContext oldcxt; 13 int i; 14 15 /* 16 * one-time startup overhead for each cache 17 */ 18 if (cache->cc_tupdesc == NULL) 19 CatalogCacheInitializeCache(cache); 20 #ifdef CATCACHE_STATS 21 cache->cc_lsearches++; 22 #endif 23 /* 24 * initialize the search key information 25 */ 26 memcpy(cur_skey, cache->cc_skey, sizeof(cur_skey)); 27 cur_skey[0].sk_argument = v1; 28 cur_skey[1].sk_argument = v2; 29 cur_skey[2].sk_argument = v3; 30 cur_skey[3].sk_argument = v4; 31 lHashValue = CatalogCacheComputeHashValue(cache, nkeys, cur_skey); 32 33 /* scan the items until we find a match or exhaust our list */ 34 for (elt = DLGetHead(&cache->cc_lists); 35 elt; 36 elt = DLGetSucc(elt)) { 37 bool res; 38 cl = (CatCList *) DLE_VAL(elt); 39 40 if (cl->dead) 41 continue; /* ignore dead entries */ 42 43 if (cl->hash_value != lHashValue) 44 continue; /* quickly skip entry if wrong hash val */ 45 46 /* 47 * see if the cached list matches our key. 48 */ 49 if (cl->nkeys != nkeys) 50 continue; 51 HeapKeyTest(&cl->tuple, 52 cache->cc_tupdesc, 53 nkeys, 54 cur_skey, 55 res); 56 if (!res) 57 continue; 58 59 /* 60 * We found a matching list. Move the list to the front of the 61 * cache's list-of-lists, to speed subsequent searches. (We do not 62 * move the members to the fronts of their hashbucket lists, however, 63 * since there's no point in that unless they are searched for 64 * individually.) 65 */ 66 DLMoveToFront(&cl->cache_elem); 67 68 /* Bump the list's refcount and return it */ 69 ResourceOwnerEnlargeCatCacheListRefs(CurrentResourceOwner); 70 cl->refcount++; 71 ResourceOwnerRememberCatCacheListRef(CurrentResourceOwner, cl); 72 73 CACHE2_elog(DEBUG2, "SearchCatCacheList(%s): found list", 74 cache->cc_relname); 75 76 #ifdef CATCACHE_STATS 77 cache->cc_lhits++; 78 #endif 79 80 return cl; 81 } 82 83 /* 84 * List was not found in cache, so we have to build it by reading the 85 * relation. For each matching tuple found in the relation, use an 86 * existing cache entry if possible, else build a new one. 87 * 88 * We have to bump the member refcounts temporarily to ensure they won't 89 * get dropped from the cache while loading other members. We use a PG_TRY 90 * block to ensure we can undo those refcounts if we get an error before 91 * we finish constructing the CatCList. 92 */ 93 ResourceOwnerEnlargeCatCacheListRefs(CurrentResourceOwner); 94 95 ctlist = NIL; 96 97 PG_TRY(); 98 { 99 Relation relation; 100 SysScanDesc scandesc; 101 102 relation = heap_open(cache->cc_reloid, AccessShareLock); 103 104 scandesc = systable_beginscan(relation, 105 cache->cc_indexoid, 106 true, 107 SnapshotNow, 108 nkeys, 109 cur_skey); 110 111 /* The list will be ordered iff we are doing an index scan */ 112 ordered = (scandesc->irel != NULL); 113 114 while (HeapTupleIsValid(ntp = systable_getnext(scandesc))) 115 { 116 uint32 hashValue; 117 Index hashIndex; 118 119 /* 120 * See if there's an entry for this tuple already. 121 */ 122 ct = NULL; 123 hashValue = CatalogCacheComputeTupleHashValue(cache, ntp); 124 hashIndex = HASH_INDEX(hashValue, cache->cc_nbuckets); 125 126 for (elt = DLGetHead(&cache->cc_bucket[hashIndex]); 127 elt; 128 elt = DLGetSucc(elt)) 129 { 130 ct = (CatCTup *) DLE_VAL(elt); 131 132 if (ct->dead || ct->negative) 133 continue; /* ignore dead and negative entries */ 134 135 if (ct->hash_value != hashValue) 136 continue; /* quickly skip entry if wrong hash val */ 137 138 if (!ItemPointerEquals(&(ct->tuple.t_self), &(ntp->t_self))) 139 continue; /* not same tuple */ 140 141 /* 142 * Found a match, but can't use it if it belongs to another 143 * list already 144 */ 145 if (ct->c_list) 146 continue; 147 148 break; /* A-OK */ 149 } 150 151 if (elt == NULL) 152 { 153 /* We didn't find a usable entry, so make a new one */ 154 ct = CatalogCacheCreateEntry(cache, ntp, 155 hashValue, hashIndex, 156 false); 157 } 158 159 /* Careful here: add entry to ctlist, then bump its refcount */ 160 /* This way leaves state correct if lappend runs out of memory */ 161 ctlist = lappend(ctlist, ct); 162 ct->refcount++; 163 } 164 165 systable_endscan(scandesc); 166 167 heap_close(relation, AccessShareLock); 168 169 /* 170 * Now we can build the CatCList entry. First we need a dummy tuple 171 * containing the key values... 172 */ 173 ntp = build_dummy_tuple(cache, nkeys, cur_skey); 174 oldcxt = MemoryContextSwitchTo(CacheMemoryContext); 175 nmembers = list_length(ctlist); 176 cl = (CatCList *) 177 palloc(sizeof(CatCList) + nmembers * sizeof(CatCTup *)); 178 heap_copytuple_with_tuple(ntp, &cl->tuple); 179 MemoryContextSwitchTo(oldcxt); 180 heap_freetuple(ntp); 181 182 /* 183 * We are now past the last thing that could trigger an elog before we 184 * have finished building the CatCList and remembering it in the 185 * resource owner. So it's OK to fall out of the PG_TRY, and indeed 186 * we'd better do so before we start marking the members as belonging 187 * to the list. 188 */ 189 190 } 191 PG_CATCH(); 192 { 193 foreach(ctlist_item, ctlist) 194 { 195 ct = (CatCTup *) lfirst(ctlist_item); 196 Assert(ct->c_list == NULL); 197 Assert(ct->refcount > 0); 198 ct->refcount--; 199 if ( 200 #ifndef CATCACHE_FORCE_RELEASE 201 ct->dead && 202 #endif 203 ct->refcount == 0 && 204 (ct->c_list == NULL || ct->c_list->refcount == 0)) 205 CatCacheRemoveCTup(cache, ct); 206 } 207 208 PG_RE_THROW(); 209 } 210 PG_END_TRY(); 211 212 cl->cl_magic = CL_MAGIC; 213 cl->my_cache = cache; 214 DLInitElem(&cl->cache_elem, cl); 215 cl->refcount = 0; /* for the moment */ 216 cl->dead = false; 217 cl->ordered = ordered; 218 cl->nkeys = nkeys; 219 cl->hash_value = lHashValue; 220 cl->n_members = nmembers; 221 222 i = 0; 223 foreach(ctlist_item, ctlist) 224 { 225 cl->members[i++] = ct = (CatCTup *) lfirst(ctlist_item); 226 Assert(ct->c_list == NULL); 227 ct->c_list = cl; 228 /* release the temporary refcount on the member */ 229 Assert(ct->refcount > 0); 230 ct->refcount--; 231 /* mark list dead if any members already dead */ 232 if (ct->dead) 233 cl->dead = true; 234 } 235 Assert(i == nmembers); 236 237 DLAddHead(&cache->cc_lists, &cl->cache_elem); 238 239 /* Finally, bump the list's refcount and return it */ 240 cl->refcount++; 241 ResourceOwnerRememberCatCacheListRef(CurrentResourceOwner, cl); 242 243 CACHE3_elog(DEBUG2, "SearchCatCacheList(%s): made list of %d members", 244 cache->cc_relname, nmembers); 245 246 return cl; 247 }
AtEOXact_CatCache用于在main事务(commit或abort)结束后清理catcache
1 void 2 AtEOXact_CatCache(bool isCommit) 3 { 4 #ifdef USE_ASSERT_CHECKING 5 if (assert_enabled) 6 { 7 CatCache *ccp; 8 9 for (ccp = CacheHdr->ch_caches; ccp; ccp = ccp->cc_next) 10 { 11 Dlelem *elt; 12 int i; 13 14 /* Check CatCLists */ 15 for (elt = DLGetHead(&ccp->cc_lists); elt; elt = DLGetSucc(elt)) 16 { 17 CatCList *cl = (CatCList *) DLE_VAL(elt); 18 19 Assert(cl->cl_magic == CL_MAGIC); 20 Assert(cl->refcount == 0); 21 Assert(!cl->dead); 22 } 23 24 /* Check individual tuples */ 25 for (i = 0; i < ccp->cc_nbuckets; i++) 26 { 27 for (elt = DLGetHead(&ccp->cc_bucket[i]); 28 elt; 29 elt = DLGetSucc(elt)) 30 { 31 CatCTup *ct = (CatCTup *) DLE_VAL(elt); 32 33 Assert(ct->ct_magic == CT_MAGIC); 34 Assert(ct->refcount == 0); 35 Assert(!ct->dead); 36 } 37 } 38 } 39 } 40 #endif 41 }