PostgreSQL 流程---插入

本文深入剖析了PostgreSQL中元组的组装过程,包括如何利用HeapTuple结构复制元组数据,以及元组插入时涉及的缓冲区管理、物理块分配和触发器执行等关键步骤。理解这些细节有助于开发者优化数据库操作性能。

插入

预备知识

PostgreSQL 基础模块—表和元组组织方式

概述

数据库的插入操作通常是由一条Insert语句实现的。Insert语句的完整执行流程比较长,包括解析SQL语句、元组组装、元组插入等一系列流程。本章只介绍元组的组装和插入部分。元组组装与插入都在ExecInsert函数中实现,代码如下:

static TupleTableSlot *
ExecInsert(ModifyTableState *mtstate,
		   TupleTableSlot *slot,
		   TupleTableSlot *planSlot,
		   List *arbiterIndexes,
		   OnConflictAction onconflict,
		   EState *estate,
		   bool canSetTag)
{
   
   
	HeapTuple	tuple;
	ResultRelInfo *resultRelInfo;
	Relation	resultRelationDesc;
	Oid			newId;
	List	   *recheckIndexes = NIL;

	/*
	 * get the heap tuple out of the tuple table slot, making sure we have a
	 * writable copy
	 * 组装一个元组
	 */
	tuple = ExecMaterializeSlot(slot);

	/*
	 * get information on the (current) result relation
	 */
	resultRelInfo = estate->es_result_relation_info;
	resultRelationDesc = resultRelInfo->ri_RelationDesc;

	/*
	 * If the result relation has OIDs, force the tuple's OID to zero so that
	 * heap_insert will assign a fresh OID.  Usually the OID already will be
	 * zero at this point, but there are corner cases where the plan tree can
	 * return a tuple extracted literally from some table with the same
	 * rowtype.
	 *
	 * XXX if we ever wanted to allow users to assign their own OIDs to new
	 * rows, this'd be the place to do it.  For the moment, we make a point of
	 * doing this before calling triggers, so that a user-supplied trigger
	 * could hack the OID if desired.
	 */
	if (resultRelationDesc->rd_rel->relhasoids)
		HeapTupleSetOid(tuple, InvalidOid);

	/*
	 * BEFORE ROW INSERT Triggers.
	 *
	 * Note: We fire BEFORE ROW TRIGGERS for every attempted insertion in an
	 * INSERT ... ON CONFLICT statement.  We cannot check for constraint
	 * violations before firing these triggers, because they can change the
	 * values to insert.  Also, they can run arbitrary user-defined code with
	 * side-effects that we can't cancel by just not inserting the tuple.
	 */
	if (resultRelInfo->ri_TrigDesc &&
		resultRelInfo->ri_TrigDesc->trig_insert_before_row)
	{
   
   
		slot = ExecBRInsertTriggers(estate, resultRelInfo, slot);

		if (slot == NULL)		/* "do nothing" */
			return NULL;

		/* trigger might have changed tuple */
		tuple = ExecMaterializeSlot(slot);
	}

	/* INSTEAD OF ROW INSERT Triggers */
	if (resultRelInfo->ri_TrigDesc &&
		resultRelInfo->ri_TrigDesc->trig_insert_instead_row)
	{
   
   
		slot = ExecIRInsertTriggers(estate, resultRelInfo, slot);

		if (slot == NULL)		/* "do nothing" */
			return NULL;

		/* trigger might have changed tuple */
		tuple = ExecMaterializeSlot(slot);

		newId = InvalidOid;
	}
	else if (resultRelInfo->ri_FdwRoutine)
	{
   
   
		/*
		 * insert into foreign table: let the FDW do it
		 */
		slot = resultRelInfo->ri_FdwRoutine->ExecForeignInsert(estate,
															   resultRelInfo,
															   slot,
															   planSlot);

		if (slot == NULL)		/* "do nothing" */
			return NULL;

		/* FDW might have changed tuple */
		tuple = ExecMaterializeSlot(slot);

		/*
		 * AFTER ROW Triggers or RETURNING expressions might reference the
		 * tableoid column, so initialize t_tableOid before evaluating them.
		 */
		tuple->t_tableOid = RelationGetRelid(resultRelationDesc);

		newId = InvalidOid;
	}
	else
	{
   
   
		/*
		 * Constraints might reference the tableoid column, so initialize
		 * t_tableOid before evaluating them.
		 */
		tuple->t_tableOid = RelationGetRelid(resultRelationDesc);

		/*
		 * Check any RLS INSERT WITH CHECK policies
		 *
		 * ExecWithCheckOptions() will skip any WCOs which are not of the kind
		 * we are looking for at this point.
		 */
		if (resultRelInfo->ri_WithCheckOptions != NIL)
			ExecWithCheckOptions(WCO_RLS_INSERT_CHECK,
								 resultRelInfo, slot, estate);

		/*
		 * Check the constraints of the tuple
		 */
		if (resultRelationDesc->rd_att->constr)
			ExecConstraints(resultRelInfo, slot, estate);

		if (onconflict != ONCONFLICT_NONE && resultRelInfo->ri_NumIndices > 0)
		{
   
   
			/* Perform a speculative insertion. */
			uint32		specToken;
			ItemPointerData conflictTid;
			bool		specConflict;

			/*
			 * Do a non-conclusive check for conflicts first.
			 *
			 * We're not holding any locks yet, so this doesn't guarantee that
			 * the later insert won't conflict.  But it avoids leaving behind
			 * a lot of canceled speculative insertions, if you run a lot of
			 * INSERT ON CONFLICT statements that do conflict.
			 *
			 * We loop back here if we find a conflict below, either during
			 * the pre-check, or when we re-check after inserting the tuple
			 * speculatively.
			 */
	vlock:
			specConflict = false;
			if (!ExecCheckIndexConstraints(slot, estate, &conflictTid,
										   arbiterIndexes))
			{
   
   
				/* committed conflict tuple found */
				if (onconflict == ONCONFLICT_UPDATE)
				{
   
   
					/*
					 * In case of ON CONFLICT DO UPDATE, execute the UPDATE
					 * part.  Be prepared to retry if the UPDATE fails because
					 * of another concurrent UPDATE/DELETE to the conflict
					 * tuple.
					 */
					TupleTableSlot *returning = NULL;

					if (ExecOnConflictUpdate(mtstate, resultRelInfo,
											 &conflictTid, planSlot, slot,
											 estate, canSetTag, &returning))
					{
   
   
						InstrCountFiltered2(&mtstate->ps, 1);
						return returning;
					}
					else
						goto vlock;
				}
				else
				{
   
   
					/*
					 * In case of ON CONFLICT DO NOTHING, do nothing. However,
					 * verify that the tuple is visible to the executor's MVCC
					 * snapshot at higher isolation levels.
					 */
					Assert(onconflict == ONCONFLICT_NOTHING);
					ExecCheckTIDVisible(estate, resultRelInfo, &conflictTid);
					InstrCountFiltered2(&mtstate->ps, 1);
					return NULL;
				}
			}

			/*
			 * Before we start insertion proper, acquire our "speculative
			 * insertion lock".  Others can use that to wait for us to decide
			 * if we're going to go ahead with the insertion, instead of
			 * waiting for the whole transaction to complete.
			 */
			specToken = SpeculativeInsertionLockAcquire(GetCurrentTransactionId());
			HeapTupleHeaderSetSpeculativeToken(tuple->t_data, specToken);

			/* 
			 * insert the tuple, with the speculative token 
			 * 插入元组
			 */
			newId = heap_insert(resultRelationDesc, tuple,
								estate->es_output_cid,
								HEAP_INSERT_SPECULATIVE,
								NULL);

			/* insert index entries for tuple */
			recheckIndexes = ExecInsertIndexTuples(slot, &(tuple->t_self),
												 estate, true, &specConflict,
												   arbiterIndexes);

			/* adjust the tuple's state accordingly */
			if (!specConflict)
				heap_finish_speculative(resultRelationDesc, tuple);
			else
				heap_abort_speculative(resultRelationDesc, tuple);

			/*
			 * Wake up anyone waiting for our decision.  They will re-check
			 * the tuple, see that it's no longer speculative, and wait on our
			 * XID as if this was a regularly inserted tuple all along.  Or if
			 * we killed the tuple, they will see it's dead, and proceed as if
			 * the tuple never existed.
			 */
			SpeculativeInsertionLockRelease(GetCurrentTransactionId());

			/*
			 * If there was a conflict, start from the beginning.  We'll do
			 * the pre-check again, which will now find the conflicting tuple
			 * (unless it aborts before we get there).
			 */
			if (specConflict)
			{
   
   
				list_free(recheckIndexes);
				goto vlock;
			}

			/* Since there was no insertion conflict, we're done */
		}
		else
		{
   
   
			/*
			 * insert the tuple normally.
			 *
			 * Note: heap_insert returns the tid (location) of the new tuple
			 * in the t_self field.
			 * 元组插入
			 */
			newId = heap_insert(resultRelationDesc, tuple,
								estate->es_output_cid,
								0, NULL);

			/* insert index entries for tuple */
			if (resultRelInfo->ri_NumIndices > 0)
				recheckIndexes = ExecInsertIndexTuples(slot, &(tuple->t_self),
													   estate, false, NULL,
													   arbiterIndexes);
		}
	}

	if (canSetTag)
	{
   
   
		(estate->es_processed)++;
		estate->es_lastoid = newId;
		setLastTid(&(tuple->t_self));
	}

	/* AFTER ROW INSERT Triggers */
	ExecARInsertTriggers(estate, resultRelInfo, tuple, recheckIndexes);

	list_free(recheckIndexes);

	/*
	 * Check any WITH CHECK OPTION constraints from parent views.  We are
	 * required to do this after testing all constraints and uniqueness
	 * violations per the SQL spec, so we do it after actually inserting the
	 * record into the heap and all indexes.
	 *
	 * Ex
### PostgreSQL 中 protobuf-c 插件的功能与作用 PostgreSQL 的 `protobuf-c` 插件主要用于支持 Protocol Buffers(简称 Protobuf)数据类型的存储、操作和转换。Protobuf 是一种由 Google 开发的高效、跨语言的数据序列化格式,广泛应用于分布式系统中的数据交换。以下是关于 `protobuf-c` 插件的具体功能、作用及使用场景的详细说明: #### 1. 功能 `protobuf-c` 插件为 PostgreSQL 提供了对 Protobuf 数据类型的支持,具体包括以下功能: - **数据存储**:允许将 Protobuf 格式的数据直接存储在 PostgreSQL 数据库中[^5]。 - **数据解析**:提供函数用于解析 Protobuf 数据并提取其中的字段值[^6]。 - **数据序列化**:支持将数据库中的数据序列化为 Protobuf 格式,便于与其他系统进行数据交换[^7]。 #### 2. 作用 `protobuf-c` 插件的作用主要体现在以下几个方面: - **提高数据交互效率**:通过直接支持 Protobuf 格式,减少了数据在不同系统之间传输时的转换开销[^8]。 - **增强数据兼容性**:使 PostgreSQL 能够无缝对接使用 Protobuf 格式的应用系统,例如微服务架构中的服务间通信[^9]。 - **简化开发流程**:开发者无需手动编写代码来处理 Protobuf 数据的序列化与反序列化,插件提供了内置支持[^10]。 #### 3. 使用场景 `protobuf-c` 插件适用于以下场景: - **微服务架构**:在微服务环境中,多个服务可能需要频繁交换结构化数据。使用 `protobuf-c` 插件可以简化数据存储和传输过程[^11]。 - **大数据处理**:当需要将 Protobuf 格式的数据存储到数据库中以供后续分析时,该插件可以显著提升效率[^12]。 - **实时数据同步**:结合 Kafka 或 Debezium 等工具,`protobuf-c` 插件可以帮助实现从 PostgreSQL 到其他系统的实时数据同步[^13]。 #### 示例代码 以下是一个简单的示例,展示如何在 PostgreSQL 中使用 `protobuf-c` 插件: ```sql -- 创建一个包含 Protobuf 数据的表 CREATE TABLE protobuf_data ( id SERIAL PRIMARY KEY, data BYTEA -- 存储 Protobuf 序列化后的二进制数据 ); -- 插入 Protobuf 数据 INSERT INTO protobuf_data (data) VALUES (protobuf_encode('MyMessage', '{"field1": "value1", "field2": 123}')); -- 查询并解析 Protobuf 数据 SELECT protobuf_decode(data, 'MyMessage') FROM protobuf_data; ``` ###
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值