前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >PostgreSQL创建表分析

PostgreSQL创建表分析

作者头像
用户4700054
发布2022-08-17 12:31:06
1.7K0
发布2022-08-17 12:31:06
举报
文章被收录于专栏:存储内核技术交流
脚本准备
  • 创建表的脚本
代码语言:javascript
复制
CREATE DATABASE sampledb OWNER perrynzhou;
GRANT ALL PRIVILEGES ON DATABASE sampledb TO perrynzhou;
  • 数据登录脚本
代码语言:javascript
复制
psql -h 127.0.0.1 -d sampledb
sampledb=# CREATE TABLE stu_xx_01(NAME      TEXT  NOT NULL,AGE      INT   NOT NULL);
表创建分析
表创建过程概述
  • 服务进程接受SQL语句,解析SQL语句
  • 取出其中定义的表名称、列名称
  • 检查表的名称和列名、列的数据结构
  • 打开pg_class表,返回一个未被使用的oid作为创建表的oid
  • 基于表的oid来创建表的磁盘文件
  • 针对新创建的表创造对应的对象类型
  • 在pg_class中注册新表的信息
  • 在pg_attribute中注册新表的colume信息
  • 关闭表的对应relation,同时返回oid
物理文件的创建函数执行路径
代码语言:javascript
复制
1.PostgresMain:服务端监听到有客户端连接到PG,PG服务端会fork一个子进程来服务,这个子进程会传输postgre二进制名称和一些参数进行子进程的初始化,以便执行fork后的后续操作。PostgresMain就是这个子进程执行后续操作,当客户端通过网络发出sql,PostgresMain根据PG协议判断执行后续的SQL语句执行。
2.exec_simple_query:根据客户端请求的SQL语句执行SQL
3.PortalRun: 根据sql语句初始化Portal结构来封装SQL语句的执行
4.PortalRunMulti:根据portal->strategy的类型执行PortalRunMulti函数
5.PortalRunUtility:解析portal中的sql为解析树,然后执行portal中的非select的语句
6.ProcessUtility:根据解析树开始执行sql语句
7.standard_ProcessUtility:在ProcessUtility内执行standard_ProcessUtility方法继续向后执行SQL语句执行过程
/******如下是一个表创建的比较核心的函数*******/
8.ProcessUtilitySlow:根据parsetree开始执行create table的过程。
9.DefineRelation:返回一个表的ObjectAddr,其中包括pg_class中的oid,这个表对象的oid,这个表中column中的sub oid
10.heap_create_with_catalog:表创建函数
11.heap_create:表创建
12.table_relation_set_new_filenode:创建表的函数指针
13.heapam_relation_set_new_filenode:实际的执行标创建的函数
14.RelationCreateStorage:构建磁盘的表文件

// 如果是根据tablespace oid,database oid,table oid创建一个数据库表
15.smgrcreate->mdcreate->PathNameOpenFile
核心函数分析
  • ProcessUtilitySlow
代码语言:javascript
复制
// ObjectAddress表示PG中数据库一种类型的对象
typedef struct ObjectAddress
{
	
	Oid			classId;		/* Class Id from pg_class */
	Oid			objectId;		/* OID of the object */
	int32		objectSubId;	/* Subitem within object (eg column), or 0 */
} ObjectAddress;

static void ProcessUtilitySlow(...)
{
	// parsetree->type =T_CreateStmt
	switch (nodeTag(parsetree))
	{
			case T_CreateStmt:
			case T_CreateForeignTableStmt:
				// 解析转换sql语句中的表的定义
				stmts = transformCreateStmt((CreateStmt *) parsetree,
												queryString);
				while (stmts != NIL)
				{
					
					address = DefineRelation(cstmt,
													 RELKIND_RELATION,
													 InvalidOid, NULL,
													 queryString);
				}
	}
	
}

// 获取当前表的表空间
List *transformCreateStmt(CreateStmt *stmt, const char *queryString)
{
	List	   *result;
	CreateStmtContext cxt;
	cxt.stmtType = "CREATE TABLE";
	cxt.isforeign = false;
	
	// 表空间获取
	namespaceid =
		RangeVarGetAndCheckCreationNamespace(stmt->relation, NoLock,
											 &existing_relid);
	stmt->relation->schemaname = get_namespace_name(namespaceid);
	
	// 编译stmt中的表中定义的column,
	foreach(elements, stmt->tableElts)
	{
		// element 中的type=T_ColumnDef
		Node	   *element = lfirst(elements);
		
		//nodeTag(element)=T_ColumnDef
		switch (nodeTag(element))
		{
			// (ColumnDef *)(element)定义了column中的定义
			case T_ColumnDef:
				// 解析column中定义的类型,约束等,把解析的结果全部存储到local的cxt中
				transformColumnDefinition(&cxt, (ColumnDef *) element);
				break;
		}
	 }
	stmt->tableElts = cxt.columns;
	// 把ctx中的数据最终以result呈现
	result = lappend(cxt.blist, stmt);
	result = list_concat(result, cxt.alist);
	result = list_concat(result, save_alist);

	return result;
}
  • DefineRelation
代码语言:javascript
复制
ObjectAddress
DefineRelation(CreateStmt *stmt, char relkind, Oid ownerId,
			   ObjectAddress *typaddress, const char *queryString)
{
	// NAMEDATALEN 规定了PG中表名称的长度为64个字符
	strlcpy(relname, stmt->relation->relname, NAMEDATALEN);
	
	// 构建这个表的所有列的以及相关属性
	
	//最终调用heap_create_with_catalog去创建表
	heap_create_with_catalog(....)
}
  • heap_create_with_catalog
代码语言:javascript
复制
Oid heap_create_with_catalog(args...) {
	
	/*
		sampledb=# select oid,relname,relnamespace from pg_class where oid=1259;
 				oid  | relname  | relnamespace 
				------+----------+--------------
 				1259 | pg_class |           11
	*/
	//打开pg_class表,这里是以行的排他锁打开这个文件
	pg_class_desc = table_open(RelationRelationId, RowExclusiveLock);
	
	// 检查表中每个列的名称和数据类型
	CheckAttributeNamesTypes(tupdesc, relkind,
							 allow_system_table_mods ? CHKATYPE_ANYARRAY : 0);
							 
	
	// 在static CatCache *SysCache中查找是否当前新增的表的名称是否存在于当前SysCache中,如果不存在则返回无效的oid
	// relname = "stu_xx_01",relnamespace=2200
	existing_relid = get_relname_relid(relname, relnamespace);
	
	// 从pg_class表中针对当前表返回一个可用oid
	relid = GetNewRelFileNode(reltablespace, pg_class_desc,
									  relpersistence);
	
	// 最终进入这个方法,来创建表的磁盘文件
	new_rel_desc = heap_create(relname,
							   relnamespace,
							   reltablespace,
							   relid,
							   InvalidOid,
							   accessmtd,
							   tupdesc,
							   relkind,
							   relpersistence,
							   shared_relation,
							   mapped_relation,
							   allow_system_table_mods,
							   &relfrozenxid,
							   &relminmxid);
	
	// 针对当前表创建一种类型
	TypeCreate(new_array_oid,	/* force the type's OID to this */
				   relarrayname,	/* Array type name */
				   relnamespace,	/* Same namespace as parent */
				   InvalidOid,	/* Not composite, no relationOid */
				   0,			/* relkind, also N/A here */
				   ownerid,		/* owner's ID */
				   -1,			/* Internal size (varlena) */
				   TYPTYPE_BASE,	/* Not composite - typelem is */
				   TYPCATEGORY_ARRAY,	/* type-category (array) */
				   false,		/* array types are never preferred */
				   DEFAULT_TYPDELIM,	/* default array delimiter */
				   F_ARRAY_IN,	/* array input proc */
				   F_ARRAY_OUT, /* array output proc */
				   F_ARRAY_RECV,	/* array recv (bin) proc */
				   F_ARRAY_SEND,	/* array send (bin) proc */
				   InvalidOid,	/* typmodin procedure - none */
				   InvalidOid,	/* typmodout procedure - none */
				   F_ARRAY_TYPANALYZE,	/* array analyze procedure */
				   F_ARRAY_SUBSCRIPT_HANDLER,	/* array subscript procedure */
				   new_type_oid,	/* array element type - the rowtype */
				   true,		/* yes, this is an array type */
				   InvalidOid,	/* this has no array type */
				   InvalidOid,	/* domain base type - irrelevant */
				   NULL,		/* default value - none */
				   NULL,		/* default binary representation */
				   false,		/* passed by reference */
				   TYPALIGN_DOUBLE, /* alignment - must be the largest! */
				   TYPSTORAGE_EXTENDED, /* fully TOASTable */
				   -1,			/* typmod */
				   0,			/* array dimensions for typBaseType */
				   false,		/* Type NOT NULL */
				   InvalidOid); /* rowtypes never have a collation */

	// 在pg_class中注册新创建的表
	AddNewRelationTuple(pg_class_desc,
						new_rel_desc,
						relid,
						new_type_oid,
						reloftypeid,
						ownerid,
						relkind,
						relfrozenxid,
						relminmxid,
						PointerGetDatum(relacl),
						reloptions);
	
	// 在pg_attribute中注册表的colume的信息
	AddNewAttributeTuples(relid, new_rel_desc->rd_att, relkind);
}
  • heap_create
代码语言:javascript
复制
Relation
heap_create(const char *relname,
			Oid relnamespace,
			Oid reltablespace,
			Oid relid,
			Oid relfilenode,
			Oid accessmtd,
			TupleDesc tupDesc,
			char relkind,
			char relpersistence,
			bool shared_relation,
			bool mapped_relation,
			bool allow_system_table_mods,
			TransactionId *relfrozenxid,
			MultiXactId *relminmxid)
{
	
	// 传入relation表的相关定义,然后构建表Cache,最终插入到Cache中

	rel = RelationBuildLocalRelation(relname,
									 relnamespace,
									 tupDesc,
									 relid,
									 accessmtd,
									 relfilenode,
									 reltablespace,
									 shared_relation,
									 mapped_relation,
									 relpersistence,
									 relkind);
	
	 // 这个函数实际调用的是 heapam_relation_set_new_filenode 这个函数
	 table_relation_set_new_filenode(rel, &rel->rd_node,
												relpersistence,
												relfrozenxid, relminmxid);
												

}
  • heapam_relation_set_new_filenode
代码语言:javascript
复制
// rel 是创建表的定义,newrnode是{表空间oid、数据库oid、表文件oid}结构
static void heapam_relation_set_new_filenode(Relation rel,
								 const RelFileNode *newrnode,
								 char persistence,
								 TransactionId *freezeXid,
								 MultiXactId *minmulti)
		
{
	// 	实际创建表的阶段								 
	srel = RelationCreateStorage(*newrnode, persistence);
	
}

SMgrRelation
RelationCreateStorage(RelFileNode rnode, char relpersistence)
{
	// 在Cache中查找SMgrRelation
	srel = smgropen(rnode, backend);
	// 创建表的磁盘文件,smgrcreate实际调用的是mdcreate
	smgrcreate(srel, MAIN_FORKNUM, false);
}

void
mdcreate(SMgrRelation reln, ForkNumber forkNum, bool isRedo)
{
	//relpath最终调用GetRelationPath,返回当前创建表的路径(基于数据库目录的)
	path = relpath(reln->smgr_rnode, forkNum);
	// 创建一个数据库表的文件
	fd = PathNameOpenFile(path, O_RDWR | O_CREAT | O_EXCL | PG_BINARY);
}
gdb跟踪调试
  • gdb日志到文件设置
代码语言:javascript
复制
//设置输出的文件名称
(gdb) set logging file <文件名>

//输入这个命令后,此后的调试信息将输出到指定文件
(gdb) set logging on

//打印说有线程栈信息
(gdb) thread apply all bt

// 输入这个命令,关闭到指定文件的输出
(gdb) set logging off

  • 客户端调试样例
代码语言:javascript
复制
$ ps -ef|grep -v grep|grep postgres
perrynz+  1525     1  0 08:37 ?        00:00:00 /usr/local/postgres/bin/postgres -D /postgres/data
perrynz+  1529  1525  0 08:37 ?        00:00:00 postgres: checkpointer 
perrynz+  1530  1525  0 08:37 ?        00:00:00 postgres: background writer 
perrynz+  1531  1525  0 08:37 ?        00:00:00 postgres: walwriter 
perrynz+  1532  1525  0 08:37 ?        00:00:00 postgres: autovacuum launcher 
perrynz+  1533  1525  0 08:37 ?        00:00:00 postgres: stats collector 
perrynz+  1534  1525  0 08:37 ?        00:00:00 postgres: logical replication launcher 
perrynz+  1536  1525  0 08:37 ?        00:00:00 postgres: perrynzhou sampledb 127.0.0.1(60390) idle

// gdb attach 到进程pid=1536
$ gdb /usr/local/postgres/bin/postgres
(gdb) attach 1536
(gdb) set print pretty on
(gdb) set print array on
(gdb) br PostgresMain
(gdb) br exec_simple_query
(gdb) br PortalRun
(gdb) br PortalRunMulti
(gdb) br PortalRunUtility
(gdb) br ProcessUtility
(gdb) br standard_ProcessUtility
(gdb) br ProcessUtilitySlow
(gdb) br DefineRelation
(gdb) br heap_create_with_catalog
(gdb) br heap_create
(gdb) br table_relation_set_new_filenode
(gdb) br heapam_relation_set_new_filenode
(gdb) br RelationCreateStorage
(gdb) br smgrcreate
(gdb) br mdcreate
(gdb) br PathNameOpenFile
本文参与 腾讯云自媒体同步曝光计划,分享自微信公众号。
原始发表:2021-11-03,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 存储内核技术交流 微信公众号,前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 脚本准备
  • 表创建分析
    • 表创建过程概述
      • 物理文件的创建函数执行路径
        • 核心函数分析
          • gdb跟踪调试
          领券
          问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档