You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

PostgreSQL实现各客户下项目ID独立自增的方法咨询

问题背景

你需要实现客户维度独立递增的项目编号规则:

  • 客户与项目为1:n一对多关系
  • 每个客户名下的项目ID独立从101开始递增,不同客户下允许出现相同项目ID
  • 最终通过客户ID.项目ID的组合唯一标识项目,对应关系示例:
    • 1001.101 -> 项目A
    • 1001.102 -> 项目B
    • 1002.101 -> 项目C
    • 1002.102 -> 项目D

当前实现的核心问题是project表的id字段使用全局共享的serial序列,所有客户的项目ID会全局连续累加,无法按客户维度独立计数。现有建表及初始化SQL如下:

CREATE TABLE customer 
(
    id serial PRIMARY KEY,
    company_name VARCHAR(255) UNIQUE NOT NULL
);

CREATE TABLE project 
(
    customer_id INT NOT NULL,
    id serial,
    project_name VARCHAR(255) NOT NULL,

    FOREIGN KEY(customer_id) REFERENCES customer (id),
    PRIMARY KEY(customer_id, id)
);

SELECT setval('customer_id_seq', 1000);
SELECT setval('project_id_seq', 100);

注:原表设计的(customer_id, id)复合主键完全符合组合唯一标识的需求,不需要调整,只需要修改项目ID的生成逻辑即可。

可落地方案

方案1:计数表+行锁触发器(生产环境首选,并发安全、无重复)

这个方案通过单独维护每个客户的项目计数,插入时自动加行锁,保证高并发场景下也不会生成重复ID,ID一旦生成永久固定,支持删除项目后保留编号缺口,符合绝大多数业务场景要求,实现步骤如下:

  1. 移除原project表的全局自增序列依赖:
ALTER TABLE project ALTER COLUMN id DROP DEFAULT;
DROP SEQUENCE project_id_seq;
  1. 创建客户项目计数表,记录每个客户当前已分配的最大项目ID,初始值设为100,首次分配+1后正好是起始值101:
CREATE TABLE customer_project_seq (
    customer_id INT PRIMARY KEY REFERENCES customer(id) ON DELETE CASCADE,
    current_project_id INT NOT NULL DEFAULT 100
);
  1. 编写插入项目时自动生成客户维度独立ID的触发器函数:
CREATE OR REPLACE FUNCTION generate_customer_project_id()
RETURNS TRIGGER AS $$
BEGIN
    -- 新客户首次插入项目时自动初始化计数
    INSERT INTO customer_project_seq(customer_id)
    VALUES(NEW.customer_id)
    ON CONFLICT (customer_id) DO NOTHING;

    -- 对当前客户的计数行加行锁,更新计数并返回新的项目ID
    -- 行锁会保证同客户并发插入时ID不会重复
    UPDATE customer_project_seq
    SET current_project_id = current_project_id + 1
    WHERE customer_id = NEW.customer_id
    RETURNING current_project_id INTO NEW.id;

    RETURN NEW;
END;
$$ LANGUAGE plpgsql VOLATILE;
  1. 给project表绑定前置插入触发器:
CREATE TRIGGER trg_project_id_generate
BEFORE INSERT ON project
FOR EACH ROW
EXECUTE FUNCTION generate_customer_project_id();

如果是有存量项目数据的老库,在绑定触发器前需要先初始化计数表,把每个客户现有最大项目ID同步到customer_project_seq中,避免新生成ID和存量数据冲突。

方案2:查询时动态计算编号(轻量无侵入,适合非固定编号场景)

如果不需要项目ID永久固定、对编号连续性没有要求,可以不提前存储ID,查询时通过窗口函数按客户分组动态生成编号:

SELECT 
    customer_id,
    100 + ROW_NUMBER() OVER (PARTITION BY customer_id ORDER BY created_at) AS project_seq,
    project_name
FROM project;

这个方案不需要修改表结构和写入逻辑,但是缺点很明显:如果删除了客户下的中间项目,后续查询生成的编号会自动前移,无法保留历史编号,仅适合临时统计、不需要持久化编号的场景。

避坑提醒

不要采用「给每个客户单独创建一个数据库序列」的实现方式:当客户量级增长后会产生大量零散的序列对象,后续客户注销、序列清理的维护成本极高,可维护性和性能都很差。

内容的提问来源于stack exchange,提问作者coemu

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.09.03 00:31:07