案例-20260101分区数据更新失败

现象

12月30日发现业务报错,数据无法更新:

c 复制代码
ERROR:  55000: cannot update table "tablzl_202601" because it does not have a replica identity and publishes updates
HINT:  To enable updating the table, set REPLICA IDENTITY using ALTER TABLE.
LOCATION:  CheckCmdReplicaIdentity, execReplication.c:575

临时恢复

因为报错信息充足,没有复制标识,表是一个分区表,又是26年的分区,所以直接怀疑新分区没有主键(新表的复制标识是default,default只能用主键作为复制标识,没有主键就会无法更新)。

然后发现,父表没有任何主键和索引,25年以前的老子分区有主键和索引,26年以后的新子分区没有任何主键和索引,且所有子分区有发布。大概如下:

sql 复制代码
p_parent --无主键无索引
p_child_202511 --有主键有索引,有发布
p_child_202512 --有主键有索引,有发布
p_child_202601 --无主键无索引,有发布
p_child_202602 --无主键无索引,有发布

因为父表什么都没有,所以partition of添加子分区也会什么都没有,只有自己准备子分区的主键和索引,所以推测新分区创建的有问题,老分区新建时应该是处理过的。

另外,通过父表发布分区表是PG13才开始支持,以前不能通过父表发布,只能通过子表发布,该库版本是PG11。

Allow partitioned tables to be logically replicated via publications (Amit Langote) § §

Previously, partitions had to be replicated individually. Now a partitioned table can be published explicitly, causing all its partitions to be published automatically. Addition/removal of a partition causes it to be likewise added to or removed from the publication. The CREATE PUBLICATION option publish_via_partition_root controls whether changes to partitions are published as their own changes or their parent's.

初步知道问题后,因为情况紧急,可以通过3个办法来临时解决:

  • 26年新分区添加主键
  • 26年新分区设置replica identity full
  • 26年新分区取消发布表

因为恢复时间都差不多,所以从减少运维成本的角度,通过添加主键恢复,至少先让业务不报错。

根因分析

问题看似清晰,就是"没有复制标识+发布过+没有主键"导致不能更新,但是还有一些问题需要解答。

疑问一:为什么update没有更新202601的数据(新分区根本没有数据),仍然报错

拿到SQL文本如下:

sql 复制代码
UPDATE tablzl_202601
         SET idid = $1,...
            date_updated = now()
            WHERE mykey = $4

tablzl_202601表的分区键是created_date,SQL条件就没有分区键,所以去更新202601分区发现连个主键都没有所以就报错了。

至于检查是否有行和检查复制标识谁先,从ExecSimpleRelationUpdate可以看到逻辑,这个函数在不同的PG版本中变化极小:

c 复制代码
/*
 * Find the searchslot tuple and update it with data in the slot,
 * update the indexes, and execute any constraints and per-row triggers.
 *
 * Caller is responsible for opening the indexes.
 */
void
ExecSimpleRelationUpdate(EState *estate, EPQState *epqstate,
						 TupleTableSlot *searchslot, TupleTableSlot *slot)
{
...
	CheckCmdReplicaIdentity(rel, CMD_UPDATE);  //检查复制标识

	/* BEFORE ROW UPDATE Triggers */ //before ROW UPDATE trigger
	if (resultRelInfo->ri_TrigDesc &&
		resultRelInfo->ri_TrigDesc->trig_update_before_row)
	{
		slot = ExecBRUpdateTriggers(estate, epqstate, resultRelInfo,
									&searchslot->tts_tuple->t_self,
									NULL, slot);

		if (slot == NULL)		/* "do nothing" */
			skip_tuple = true;
	}

	if (!skip_tuple)
	{
		List	   *recheckIndexes = NIL;

		/* Check the constraints of the tuple */ //检查约束
		if (rel->rd_att->constr)
			ExecConstraints(resultRelInfo, slot, estate);
		if (resultRelInfo->ri_PartitionCheck)
			ExecPartitionCheck(resultRelInfo, slot, estate, true);

		/* Materialize slot into a tuple that we can scribble upon. */
		tuple = ExecMaterializeSlot(slot);

		/* OK, update the tuple and index entries for it */ //更新行
		simple_heap_update(rel, &searchslot->tts_tuple->t_self,
						   slot->tts_tuple);

		if (resultRelInfo->ri_NumIndices > 0 &&  //插入索引条目
			!HeapTupleIsHeapOnly(slot->tts_tuple))
			recheckIndexes = ExecInsertIndexTuples(slot, &(tuple->t_self),
												   estate, false, NULL,
												   NIL);

		/* AFTER ROW UPDATE Triggers */  //after ROW UPDATE trigger
		ExecARUpdateTriggers(estate, resultRelInfo,
							 &searchslot->tts_tuple->t_self,
							 NULL, tuple, recheckIndexes, NULL);

		list_free(recheckIndexes);
	}
}

ExecSimpleRelationUpdate大致过程:

  1. 检查复制标识
  2. before ROW UPDATE trigger
  3. 检查约束(包括非分区约束和分区约束)
  4. 更新行
  5. 插入索引条目
  6. after ROW UPDATE trigger

所以pg逻辑就是先检查复制标识,然后才是更新行等等操作。

虽然SQL条件没有分区键,但是加上分区键就会裁剪吗?答案是可能不会。

分区裁剪的版本提升:

因为pg11不支持now()裁剪,如果业务SQL加now()条件不会触发裁剪,仍然会报错。但是如果业务通过绑定变量传入,是会触发裁剪的,也就不会报错。注意,这个报错指的是更新202512的数据不会在202601分区上报错,更新202601的数据该报错还是报错。

疑问二:表是20251226创建的,为什么是10月30日发现有问题

这个就更简单了,"没有复制标识+发布过+没有主键"是且的关系。

虽然新分区建的比较早,但发布表的时间是12月29日晚20:47

sql 复制代码
cat  postgresql-12-29.csv.bak |grep "alter publication"
2025-12-29 20:48:07.730 CST,"userlzlreplication","lzldb",xxx"statement: alter publication publzl add table ""public"".""tablzl_202601"", ""public"".""tablzl_202602"",...,,,,,,,,,""

首次报错时间是12月29日晚22:26,也就是1个半小时左右以后:

sql 复制代码
 cat  postgresql-12-29.csv.bak |grep "REPLICA IDENTITY"
2025-12-29 22:26:01.404 CST,"userlzlreplication","lzldb",375121,xxx,"cannot update table ""tablzl_202601"" because it does not have a replica identity and publishes updates",,"To enable updating the table, set REPLICA IDENTITY using ALTER TABLE.",,,,"UPDATE tablzl

总结

原因概述:如果父表没有主键,新分区partition of创建时当然也不会有主键,老的子分区通过子分区主键实现,新的子分区没有去主动创建主键,导致202601主键缺失。链路同步需要依赖主键(default)作复制标识,没有复制标识会导致无法同步给下游,也会导致发布以后update/delete语句无法正常执行。在PG 11中,update SQL本身即使包含分区键条件,也可能会去访问新分区。

运气成分:因为各种原因,这个库提前发现了这个问题。在31号我们有1天的buffer来补救所有数据库实例,至少保证1号新分区数据update别报错,不然大概率2026年1月1日多个系统将原地爆炸。

临时措施(三选一):

  • 26年新分区添加主键
  • 26年新分区设置replica identity full
  • 26年新分区取消发布表

对于复制链路的优化:

  • 应该提前发现没有主键的表,不然直接发布的话,可能会导致业务侧更新失败

对于分区管理的策略:

PG的分区表非常灵活,而且一般开发弄不懂怎么去正确创建分区,再加上分区表在大概PG10-15之间有较多的重要新特性,加上PG分区表没有interval分区功能,分区表可能弄的乱七八糟,所以规范管理分区表显得尤为重要。分区表的特性和运维技巧见:PostgreSQL分区表

针对管理手段就不说了,

针对管理目标:

  • 以主表结构为标准结构,即主表面向开发,它上面应该有主键、索引、复制标识(pg版本不支持除外)
  • 主表与子表保持一致,使用partition of创建新分区(是的,我不推荐attach)
  • 子表与子表保持一致
  • 提前创建新分区,分区数据量不宜过多
  • default分区不建议创建,如果创建必须监控其写入情况
  • 频繁访问的表的SQL必须包含分区键,使用分区裁剪,不然改造为普通表

ref

https://www.postgresql.org/docs/release/10.0/

https://www.postgresql.org/docs/release/11.0/

https://www.postgresql.org/docs/release/12.0/

https://www.postgresql.org/docs/release/13.0/

https://www.postgresql.org/docs/release/14.0/

src/backend/executor/execReplication.c

PostgreSQL分区表

相关推荐
济6177 小时前
linux 系统移植(第十八期)----根文件系统简介---- Ubuntu20.04
数据库·postgresql
数据知道7 小时前
PostgreSQL 实战:数组的增删改查与索引优化详解
数据库·postgresql
数据知道11 小时前
PostgreSQL 实战:行级安全策略(RLS)详解
数据库·postgresql
玄同76511 小时前
SQLAlchemy 会话管理终极指南:close、commit、refresh、rollback 的正确打开方式
数据库·人工智能·python·sql·postgresql·自然语言处理·知识图谱
gis分享者12 小时前
使用postgresql、postgis数据库作为存储仓库,发布geoserver矢量切片服务(pbf切片)、矢量切片图层组服务
postgresql·geoserver·postgis·矢量切片·服务·pbf·图层组
重生之绝世牛码1 天前
Linux软件安装 —— PostgreSQL高可用集群安装(postgreSQL + repmgr主从复制 + keepalived故障转移)
大数据·linux·运维·数据库·postgresql·软件安装·postgresql高可用
数据知道1 天前
PostgreSQL 实战:详解 UPSERT(INSERT ON CONFLICT)
数据库·python·postgresql
数据知道1 天前
PostgreSQL 实战:一文掌握如何优雅的进行递归查询?
大数据·数据库·postgresql
数据知道1 天前
PostgreSQL 实战:索引的设计原则详解
数据库·postgresql
重生之绝世牛码1 天前
Linux软件安装 —— PostgreSQL集群安装(主从复制集群)
大数据·linux·运维·数据库·postgresql·软件安装·postgresql主从集群