PostgreSQL插入分区表触发“stack depth limit exceeded”错误求助
问题修复:PostgreSQL分区表插入时栈溢出错误
问题场景
创建了按created_date范围分区的some_data表,完成以下操作后执行插入脚本触发栈溢出错误:
- 启用uuid-ossp扩展
- 创建主表并配置范围分区规则
- 创建2023年度分区表
- 添加索引
- 编写触发器函数及BEFORE INSERT触发器
错误信息:
ERROR: stack depth limit exceeded. Increase the configuration parameter "max_stack_depth"...
相关代码如下:
主表与分区创建
CREATE EXTENSION IF NOT EXISTS "uuid-ossp"; CREATE TABLE IF NOT EXISTS some_data( id uuid default uuid_generate_v4() not null, name text, created_date date not null, constraint "some_data_pk" primary key (id, created_date) ) PARTITION BY RANGE (created_date); -- 2023年分区表 CREATE TABLE some_data_2023 PARTITION OF some_data FOR VALUES FROM ('2023-01-01 00:00:00') TO ('2024-01-01 00:00:00'); -- 创建索引 CREATE INDEX some_data_idx ON some_data(name);
触发器函数与触发器
CREATE OR REPLACE FUNCTION update_some_data_partitions() RETURNS trigger AS $$ BEGIN IF (NEW.created_date >= DATE '2023-01-01 00:00:00' AND NEW.created_date < DATE '2024-01-01 00:00:00') THEN INSERT INTO some_data_2023 VALUES (NEW.*); ELSE RAISE EXCEPTION 'No partition found for created_date %', NEW.created_date; END IF; RETURN NULL; END; $$ LANGUAGE plpgsql; CREATE TRIGGER some_data_trigger BEFORE INSERT ON some_data FOR EACH row EXECUTE FUNCTION update_some_data_partitions();
插入脚本
INSERT INTO some_data(id, name, created_date) VALUES (uuid_generate_v4(), 'name', '2023-05-08 12:00:00');
错误原因
触发器绑定在主表some_data上,当向主表插入数据时,触发器函数会执行INSERT INTO some_data_2023 VALUES (NEW.*)。但some_data_2023是主表的分区表,向分区表插入数据时会再次触发主表的BEFORE INSERT触发器,形成无限递归调用,最终导致栈溢出。
修复方案
方案1:移除多余触发器(推荐)
PostgreSQL的声明式分区会自动将主表的插入数据路由到对应分区,完全不需要手动编写触发器处理数据分发。执行以下语句清理冗余代码:
-- 删除触发器 DROP TRIGGER some_data_trigger ON some_data; -- 删除触发器函数 DROP FUNCTION update_some_data_partitions();
之后直接执行插入脚本,数据会自动进入some_data_2023分区,不会再出现栈溢出问题。
方案2:修改触发器避免递归(适用于动态分区场景)
如果需要动态创建未来年份的分区,可以修改触发器函数,通过禁用局部触发器避免递归:
CREATE OR REPLACE FUNCTION update_some_data_partitions() RETURNS trigger AS $$ BEGIN -- 仅处理主表的插入操作,避免递归触发 IF TG_TABLE_NAME = 'some_data' THEN DECLARE partition_name text := 'some_data_' || to_char(NEW.created_date, 'YYYY'); BEGIN -- 检查分区是否存在,不存在则创建 IF NOT EXISTS (SELECT 1 FROM pg_tables WHERE tablename = partition_name) THEN EXECUTE format('CREATE TABLE %I PARTITION OF some_data FOR VALUES FROM (%L) TO (%L)', partition_name, date_trunc('year', NEW.created_date)::date, date_trunc('year', NEW.created_date)::date + interval '1 year' ); END IF; -- 禁用当前触发器后插入分区,避免递归 SET LOCAL trigger_some_data_trigger TO OFF; EXECUTE format('INSERT INTO %I VALUES ($1.*)', partition_name) USING NEW; RESET trigger_some_data_trigger; END; RETURN NULL; END IF; RETURN NEW; END; $$ LANGUAGE plpgsql;
注:动态分区场景更推荐使用pg_partman扩展,比手动编写触发器更稳定可靠。
内容的提问来源于stack exchange,提问作者Developus
相关产品推荐
相关产品推荐

