postgresql源码阅读 search_path

命名空间

目录 pg_namespace 存储命名空间。命名空间是 SQL 模式的基础结构:每个命名空间都可以拥有独立的表、类型等集合,而不会发生名称冲突。

表结构

类型描述
oid oid行标识符
nspname name命名空间的名称
nspowner oid (引用 pg_authid.oid)命名空间的拥有者
nspacl aclitem[]访问权限;有关详细信息,请参阅 第 5.8 节

search_path

PostgreSQL: 文档: 18: 5.10. 模式 - PostgreSQL 数据库

相关内容可查看文档,search_path能简化在schema中创建类型或查找的方式,在默认设置下,search_path的值为:

复制代码
 search_path
--------------
 "$user", public

第一个元素指定要搜索一个与当前用户同名的模式。如果不存在这样的模式,则忽略该条目。第二个元素指的是我们已经见过的 public 模式。

search_path 总体由以下全局变量实现,active表示正在使用的search_path,base表示从guc search_path解析出来的值

查询时,根据baseSearchPathValid判断当前search_path是否还有效,无效时会做recompute

区别active和base是因为有些情况的search_path是内核中临时push 覆盖的,并不与guc的值一致,实际当前已经很少允许直接push覆盖search_path,可能会有隐藏问题

c 复制代码
// 当前正在使用的SearchPath
static List *activeSearchPath = NIL;

// 当前创建对象时用的Namespace,可以理解为用户指定searchpath的头节点
static Oid	activeCreationNamespace = InvalidOid;

// 为true时,activeCreationNamespace应为temp_ns但是,临时命名空间还没set up,activeCreationNamespace的值无效
static bool activeTempCreationPending = false;

// SearchPath 迭代次数,每次变更时++,可以根据该值判断当前path是否和activeSearchPath一致
static uint64 activePathGeneration = 1;

// 解析自namespace_search_path的值,即guc search_path

static List *baseSearchPath = NIL;

static Oid	baseCreationNamespace = InvalidOid;

static bool baseTempCreationPending = false;

static Oid	namespaceUser = InvalidOid;

// 以上base值是否有效,无效的话要重新计算
static bool baseSearchPathValid = true;
怎么通过searchpath 查找类型或其他对象

遍历activeSearchPath,查找时添加过滤条件namespaceId,或是遍历对比返回tuple的namespaceId

c 复制代码
Oid
TypenameGetTypidExtended(const char *typname, bool temp_ok)
{
	Oid			typid;
	ListCell   *l;

	recomputeNamespacePath();

	foreach(l, activeSearchPath)
	{
		Oid			namespaceId = lfirst_oid(l);

		if (!temp_ok && namespaceId == myTempNamespace)
			continue;			/* do not look in temp namespace */

		typid = GetSysCacheOid2(TYPENAMENSP, Anum_pg_type_oid,
								PointerGetDatum(typname),
								ObjectIdGetDatum(namespaceId));
		if (OidIsValid(typid))
			return typid;
	}

	/* Not found in path */
	return InvalidOid;
}
searchpath初始化
c 复制代码
InitPostgres
	InitializeSearchPath
    
void
InitializeSearchPath(void)
{
    //Bootstrap 模式下searchpath只为pg_catalog,所有对象都在pg_catalog下创建
	if (IsBootstrapProcessingMode())
	{
		MemoryContext oldcxt;
		oldcxt = MemoryContextSwitchTo(TopMemoryContext);
		baseSearchPath = list_make1_oid(PG_CATALOG_NAMESPACE);
		MemoryContextSwitchTo(oldcxt);
		baseCreationNamespace = PG_CATALOG_NAMESPACE;
		baseTempCreationPending = false;
		baseSearchPathValid = true;
		namespaceUser = GetUserId();
		activeSearchPath = baseSearchPath;
		activeCreationNamespace = baseCreationNamespace;
		activeTempCreationPending = baseTempCreationPending;
		activePathGeneration++; /* pro forma */
	}
	else
	{
		// 正常情况, 注册失效函数,当pg_namespace, pg_auth表行改变时, 调用失效函数
        // pg_auth表行改变 可能改用户名,涉及$user
		CacheRegisterSyscacheCallback(NAMESPACEOID,
									  NamespaceCallback,
									  (Datum) 0);
		CacheRegisterSyscacheCallback(AUTHOID,
									  NamespaceCallback,
									  (Datum) 0);
		// 初始化, 强制search path下次调用时被重新计算
		baseSearchPathValid = false;
	}
}

static void
NamespaceCallback(Datum arg, int cacheid, uint32 hashvalue)
{
	// 强制search path下次调用时被重新计算
	baseSearchPathValid = false;
}

计算赋值searchpath

c 复制代码
static void
recomputeNamespacePath(void)
{
	//... 变量
    
	// 当前激活的search_path为PushOverrideSearchPath 方式临时手动设置的,不需要重计算
	if (overrideStack)
		return;

	// 当前search_path有效 不需要重计算
	if (baseSearchPathValid && namespaceUser == roleid)
		return;

	//copy search_path的值
	rawname = pstrdup(namespace_search_path);

	//解析,分割符为','
	if (!SplitIdentifierStringForSearchPath(rawname, ',', &namelist))
	{
		elog(ERROR, "invalid list syntax");
	}

	//namelist 转为oidlist
	oidlist = NIL;
	temp_missing = false;
	foreach(l, namelist)
	{
		char	   *curname = (char *) lfirst(l);
		Oid			namespaceId;

		if ((compatible_db == DB_ORACLE && enable_case_switch && identifier_case_switch != NORMAL) ?
			pg_strcasecmp(curname, "$user") == 0 : strcmp(curname, "$user") == 0)
		{
			//当前值为$user,查询是否有用户名同名的ns,有 并且acl权限检查ok,添加到oidlist
			HeapTuple	tuple;

			tuple = SearchSysCache1(AUTHOID, ObjectIdGetDatum(roleid));
			if (HeapTupleIsValid(tuple))
			{
				char	   *rname;

				rname = NameStr(((Form_pg_authid) GETSTRUCT(tuple))->rolname);
				namespaceId = get_namespace_oid(rname, true);
				ReleaseSysCache(tuple);
				if (OidIsValid(namespaceId) &&
					!list_member_oid(oidlist, namespaceId) &&
					object_aclcheck(NamespaceRelationId, namespaceId, roleid,
										  ACL_USAGE) == ACLCHECK_OK &&
					InvokeNamespaceSearchHook(namespaceId, false))
					oidlist = lappend_oid(oidlist, namespaceId);
			}
		}
		else if (strcmp(curname, "pg_temp") == 0)
		{
			//临时表空间, 存在则加到oidlist, 不存在且如果pg_temp为头节点,表示后续新的头节点不为CreationNamespace,先置temp_missing=true
			if (OidIsValid(myTempNamespace))
			{
				if (!list_member_oid(oidlist, myTempNamespace) &&
					InvokeNamespaceSearchHook(myTempNamespace, false))
					oidlist = lappend_oid(oidlist, myTempNamespace);
			}
			else
			{
				if (oidlist == NIL)
					temp_missing = true;
			}
		}
		else
		{
			//其他值, 查询ns名,判断acl,添加到oidlist
			namespaceId = get_namespace_oid(curname, true);
			if (OidIsValid(namespaceId) &&
				!list_member_oid(oidlist, namespaceId) &&
				object_aclcheck(NamespaceRelationId, namespaceId, roleid,
									  ACL_USAGE) == ACLCHECK_OK &&
				InvokeNamespaceSearchHook(namespaceId, false))
				oidlist = lappend_oid(oidlist, namespaceId);
		}
	}

	// 头节点, 后续可能作为CreationNamespace
	if (oidlist == NIL)
		firstNS = InvalidOid;
	else
		firstNS = linitial_oid(oidlist);

	//添加隐式的ns到链表头, search时会优先查找这些ns, 创建对象时还是用上面的firstNS
    //pg_catalog, ns1, ns2 ...
	if (!list_member_oid(oidlist, PG_CATALOG_NAMESPACE))
		oidlist = lcons_oid(PG_CATALOG_NAMESPACE, oidlist);

	//pg_temp, pg_catalog, ns1, ns2 ...
	if (OidIsValid(myTempNamespace) &&
		!list_member_oid(oidlist, myTempNamespace))
		oidlist = lcons_oid(myTempNamespace, oidlist);

	//判断search_path是否有变更, 没有变更的话pathChanged = false, 不增加generation的数量
	if (baseCreationNamespace == firstNS &&
		baseTempCreationPending == temp_missing &&
		equal(oidlist, baseSearchPath))
	{
		pathChanged = false;
	}
	else
	{
        //path变更,赋值给base
		pathChanged = true;
		oldcxt = MemoryContextSwitchTo(TopMemoryContext);
		newpath = list_copy(oidlist);
		MemoryContextSwitchTo(oldcxt);

		list_free(baseSearchPath);
		baseSearchPath = newpath;
		baseCreationNamespace = firstNS;
		baseTempCreationPending = temp_missing;
	}

	//标记basepath有效
	baseSearchPathValid = true;
	namespaceUser = roleid;
	//置为active
	activeSearchPath = baseSearchPath;
	activeCreationNamespace = baseCreationNamespace;
	activeTempCreationPending = baseTempCreationPending;

	//path改变 activePathGeneration++
	if (pathChanged)
		activePathGeneration++;

	/* Clean up. */
	pfree(rawname);
	list_free(namelist);
	list_free(oidlist);
}
什么情况下需要做recompute

从对象名->对象oid转化的过程,recompute函数也实际封装到了这类 对象名->对象oid转化的函数中,

对于dml或者查询语句,主要在语义分析阶段和重写阶段

对于ddl语句,分布在ddl的处理逻辑中,大多数场景同一事务内首次做了recompute,之后都是validate的状态,只需要做判断即可

c 复制代码
pg_analyze_and_rewrite_fixedparams
    parse_analyze_fixedparams
    	transformTopLevelStmt
    		transformOptionalSelectInto
    			transformStmt
    				transformFromClause
    					transformTableEntry
    						addRangeTableEntry
    							parserOpenTable
    								relation_openrv_extended
										RangeVarGetRelid  //宏同 RangeVarGetRelidExtended
    										RelnameGetRelid
    
plancache中保存的SearchPath

plancache用来缓存执行计划,当searchpath改变时,查询语句可能涉及对象变更,对应的执行计划也应不一致,plancache中保存了一份当时的SearchPath,用来验证当前执行计划是否还有效

c 复制代码
//保存的结构体
typedef struct OverrideSearchPath
{
	List	   *schemas;		/* OIDs of explicitly named schemas */
	bool		addCatalog;		/* implicitly prepend pg_catalog? */
	bool		addTemp;		/* implicitly prepend temp schema? */
	uint64		generation;		/* for quick detection of equality to active */
} OverrideSearchPath;
c 复制代码
//获取当前search_path
OverrideSearchPath *
GetOverrideSearchPath(MemoryContext context)
{
	OverrideSearchPath *result;
	List	   *schemas;
	MemoryContext oldcxt;

	recomputeNamespacePath();

	oldcxt = MemoryContextSwitchTo(context);

	result = (OverrideSearchPath *) palloc0(sizeof(OverrideSearchPath));
	schemas = list_copy(activeSearchPath);
    // 遍历schemas, 直到为activeCreationNamespace, 删除前几个隐式的ns
	while (schemas && linitial_oid(schemas) != activeCreationNamespace)
	{
		if (linitial_oid(schemas) == myTempNamespace)
			result->addTemp = true;
		else
		{
				Assert(linitial_oid(schemas) == PG_CATALOG_NAMESPACE);
				result->addCatalog = true;
		}
		schemas = list_delete_first(schemas);
	}
	result->schemas = schemas;
	result->generation = activePathGeneration;

	MemoryContextSwitchTo(oldcxt);

	return result;
}

判断是否searchpath改变

c 复制代码
bool
OverrideSearchPathMatchesCurrent(OverrideSearchPath *path)
{
	ListCell   *lc,
			   *lcp;
	// 刷新SearchPath
	recomputeNamespacePath();

	//generation一致 即满足
	if (path->generation == activePathGeneration)
		return true;

	//扫描SearchPath
	lc = list_head(activeSearchPath);

	//隐式的几个ns:tmpns,pg_catalog
	if (path->addTemp)
	{
		if (lc && lfirst_oid(lc) == myTempNamespace)
			lc = lnext(activeSearchPath, lc);
		else
			return false;
	}

	//判断activeCreationNamespace 是否一致
	if (activeCreationNamespace != (lc ? lfirst_oid(lc) : InvalidOid))
		return false;
	//遍历判断剩余ns
	foreach(lcp, path->schemas)
	{
		if (lc && lfirst_oid(lc) == lfirst_oid(lcp))
			lc = lnext(activeSearchPath, lc);
		else
			return false;
	}
	if (lc)
		return false;

	//更新generation,加速下次判断
	path->generation = activePathGeneration;

	return true;
}
临时表

临时表通过关联ns实现,每个会话有自己独立的tmp_ns,在事务提交时级联删除ns中的内容,或者会话结束时删除,实现临时表的删除

c 复制代码
// 初始化temp_ns, 只有在创建临时表时才会置force为true, 读表时如果没有temp_ns,同时也说明没有临时表
static void
AccessTempTableNamespace(bool force)
{
	/*
	 * Make note that this temporary namespace has been accessed in this
	 * transaction.
	 */
	MyXactFlags |= XACT_FLAGS_ACCESSEDTEMPNAMESPACE;
	//只有在创建临时表时才会置force为true
	if (!force && OidIsValid(myTempNamespace))
		return;

	/*
	 * The temporary tablespace does not exist yet and is wanted, so
	 * initialize it.
	 */
	InitTempTableNamespace();
}

//初始化tmp_ns
static void
InitTempTableNamespace(void)
{
	char		namespaceName[NAMEDATALEN];
	Oid			namespaceId;
	Oid			toastspaceId;

	Assert(!OidIsValid(myTempNamespace));

	//1. 权限检查
	if (object_aclcheck(DatabaseRelationId, MyDatabaseId, GetUserId(),
							 ACL_CREATE_TEMP) != ACLCHECK_OK)
		ereport(ERROR,
				(errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
				 errmsg("permission denied to create temporary tables in database \"%s\"",
						get_database_name(MyDatabaseId))));

	//2. 备份恢复,并行worker 不创建临时表
	if (RecoveryInProgress())
		ereport(ERROR,
				(errcode(ERRCODE_READ_ONLY_SQL_TRANSACTION),
				 errmsg("cannot create temporary tables during recovery")));
	if (IsParallelWorker())
		ereport(ERROR,
				(errcode(ERRCODE_READ_ONLY_SQL_TRANSACTION),
				 errmsg("cannot create temporary tables during a parallel operation")));

    //3. 创建临时表, 临时表ns名 为pg_temp_+backendid (backendid是可复用的, 所以即使退出会话也不会删除ns,只是删除ns里的内容)
    // 如果ns存在则级联删除里面的内容
	snprintf(namespaceName, sizeof(namespaceName), "pg_temp_%d", MyBackendId);

	namespaceId = get_namespace_oid(namespaceName, true);
	if (!OidIsValid(namespaceId))
	{
		/*
		 * First use of this temp namespace in this database; create it. The
		 * temp namespaces are always owned by the superuser.  We leave their
		 * permissions at default --- i.e., no access except to superuser ---
		 * to ensure that unprivileged users can't peek at other backends'
		 * temp tables.  This works because the places that access the temp
		 * namespace for my own backend skip permissions checks on it.
		 */
		namespaceId = NamespaceCreate(namespaceName, BOOTSTRAP_SUPERUSERID,
									  true);
		/* Advance command counter to make namespace visible */
		CommandCounterIncrement();
	}
	else
	{
		/*
		 * If the namespace already exists, clean it out (in case the former
		 * owner crashed without doing so).
		 */
		RemoveTempRelations(namespaceId);
	}
	//4. 创建toast temp ns,同创建临时表ns, 如果ns存在不用做级联删除, 假设之前的级联删除同时也会删除toast表
	snprintf(namespaceName, sizeof(namespaceName), "pg_toast_temp_%d",
			 MyBackendId);

	toastspaceId = get_namespace_oid(namespaceName, true);
	if (!OidIsValid(toastspaceId))
	{
		toastspaceId = NamespaceCreate(namespaceName, BOOTSTRAP_SUPERUSERID,
									   true);
		/* Advance command counter to make namespace visible */
		CommandCounterIncrement();
	}

	// 5.赋值全局变量, inValid searchpath
	myTempNamespace = namespaceId;
	myTempToastNamespace = toastspaceId;

	/*
	 * Mark MyProc as owning this namespace which other processes can use to
	 * decide if a temporary namespace is in use or not.  We assume that
	 * assignment of namespaceId is an atomic operation.  Even if it is not,
	 * the temporary relation which resulted in the creation of this temporary
	 * namespace is still locked until the current transaction commits, and
	 * its pg_namespace row is not visible yet.  However it does not matter:
	 * this flag makes the namespace as being in use, so no objects created on
	 * it would be removed concurrently.
	 */
	MyProc->tempNamespaceId = namespaceId;

	/* It should not be done already. */
	Assert(myTempNamespaceSubID == InvalidSubTransactionId);
	myTempNamespaceSubID = GetCurrentSubTransactionId();

	baseSearchPathValid = false;	/* need to rebuild list */
}

//事务结束对ns的处理
void
AtEOXact_Namespace(bool isCommit, bool parallel)
{
	// 创建了临时表ns
    //1. 事务提交 注册shmem_exit 回调函数,会话退出时清理临时表
    //2. 事务回滚,ns创建也回滚, search_path改变, 置baseSearchPathValid为force, reset临时表全局变量
	if (myTempNamespaceSubID != InvalidSubTransactionId && !parallel)
	{
		if (isCommit)
			before_shmem_exit(RemoveTempRelationsCallback, 0);
		else
		{
			myTempNamespace = InvalidOid;
			myTempToastNamespace = InvalidOid;
			baseSearchPathValid = false;	/* need to rebuild list */
			MyProc->tempNamespaceId = InvalidOid;
		}
		myTempNamespaceSubID = InvalidSubTransactionId;
	}

	//清理push进去的search_path
	if (overrideStack)
	{
		if (isCommit)
			elog(WARNING, "leaked override search path");
		while (overrideStack)
		{
			OverrideStackEntry *entry;

			entry = (OverrideStackEntry *) linitial(overrideStack);
			overrideStack = list_delete_first(overrideStack);
			list_free(entry->searchPath);
			pfree(entry);
		}
		/* If not baseSearchPathValid, this is useless but harmless */
		activeSearchPath = baseSearchPath;
		activeCreationNamespace = baseCreationNamespace;
		activeTempCreationPending = baseTempCreationPending;
		/* Always bump generation --- see note in recomputeNamespacePath */
		activePathGeneration++;
	}
}


//级联删除ns中内容,不包含ns本身
static void
RemoveTempRelations(Oid tempNamespaceId)
{
	ObjectAddress object;

	/*
	 * We want to get rid of everything in the target namespace, but not the
	 * namespace itself (deleting it only to recreate it later would be a
	 * waste of cycles).  Hence, specify SKIP_ORIGINAL.  It's also an INTERNAL
	 * deletion, and we want to not drop any extensions that might happen to
	 * own temp objects.
	 */
	object.classId = NamespaceRelationId;
	object.objectId = tempNamespaceId;
	object.objectSubId = 0;

	performDeletion(&object, DROP_CASCADE,
					PERFORM_DELETION_INTERNAL |
					PERFORM_DELETION_QUIETLY |
					PERFORM_DELETION_SKIP_ORIGINAL |
					PERFORM_DELETION_SKIP_EXTENSIONS);
}

//会话退出的回调函数
static void
RemoveTempRelationsCallback(int code, Datum arg)
{
	if (OidIsValid(myTempNamespace))	/* should always be true */
	{
		/* Need to ensure we have a usable transaction. */
		AbortOutOfAnyTransaction();
		StartTransactionCommand();
		PushActiveSnapshot(GetTransactionSnapshot());

		RemoveTempRelations(myTempNamespace);

		PopActiveSnapshot();
		CommitTransactionCommand();
	}
}

/*
 * Remove all temp tables from the temporary namespace.
 */
void
ResetTempTableNamespace(void)
{
	if (OidIsValid(myTempNamespace))
		RemoveTempRelations(myTempNamespace);
}
相关推荐
wxh_无香花自开5 小时前
pgsql 笔记
linux·服务器·postgresql·pgsql
humstone5 小时前
基于xml 和sql 实现自定义报表查询
xml·数据库·sql
运维行者_5 小时前
PostgreSQL 十大性能问题及解决方案
运维·服务器·网络·数据库·postgresql·智能路由器·snmp
从零开始学习人工智能5 小时前
从反复报错到稳定运行:麒麟与Ubuntu时间同步服务部署全解析
服务器·数据库·ubuntu
xcLeigh5 小时前
数据库迁移:Oracle至KingbaseES迁移最佳实践
数据库·oracle·数据迁移·kingbasees·金仓数据库
nandao1585 小时前
Linux环境通过YUM仓库源码安装PostgreSQL 数据库
数据库
IT布道5 小时前
MongoDB性能调优之--关闭THP
数据库·mongodb
程序猿_极客5 小时前
MySQL 从入门到实战:DQL 查询语言详解(附案例 + 练习)
数据库·mysql·mysql入门
无奈笑天下7 小时前
银河麒麟桌面OS使用分区编辑器将/backup分区删除并扩容至根分区参考教程
linux·数据库·经验分享·编辑器