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

PostgreSQL如何创建存储多列MD5值的GENERATED列

问题原因

生成列要求关联的表达式必须为IMMUTABLE(不可变)属性:即相同输入永远返回完全一致的结果,不受会话配置、外部环境变化影响。
原写法中ROW(...)::VARCHAR本质调用了record类型的通用输出函数,该函数在PostgreSQL中被标记为STABLE而非IMMUTABLE——它的输出会受client_encoding、DateStyle、IntervalStyle等多个会话级参数影响,同一条记录在不同会话配置下转换出的字符串可能存在差异,因此不满足生成列的使用要求。

原生GENERATED列实现方案(无扩展、无触发器)

不需要依赖视图、触发器,只要避开非不可变的ROW类型转换逻辑,改用全IMMUTABLE函数实现无歧义字节拼接即可满足要求,建表语句如下:

CREATE TABLE client_cache (
    id            BIGINT PRIMARY KEY GENERATED ALWAYS AS IDENTITY,
    request       VARCHAR COMPRESSION lz4 NOT NULL CHECK (LENGTH (request) <= 10240),
    request_body  BYTEA COMPRESSION lz4 NOT NULL CHECK (LENGTH (request_body) <= 1048576),
    request_hash VARCHAR GENERATED ALWAYS AS (
        MD5(
            -- 拼接规则:每个字段前增加4字节大端序长度标识,再拼接字段二进制内容,彻底避免哈希碰撞
            int4send(octet_length(convert_to(request, 'UTF8'))) 
            || convert_to(request, 'UTF8')
            || int4send(octet_length(request_body)) 
            || request_body
        )
    ) STORED
);

逻辑说明

  • convert_to(request, 'UTF8'):将varchar类型的request以固定UTF8编码转换为二进制bytea,不依赖会话级编码配置,属于IMMUTABLE操作
  • int4send(...):将整数值转换为固定4字节的大端序二进制表示,属于IMMUTABLE操作
  • 每个字段前增加长度标识是为了彻底消除拼接歧义:比如request='ab', request_body='c'和request='a', request_body='bc'这类直接拼接会得到相同二进制串的场景,加长度前缀后会生成完全不同的串,不会出现哈希碰撞
  • 语句中所有用到的函数均为PostgreSQL内置的IMMUTABLE属性函数,完全符合生成列的校验规则,不需要额外安装扩展或编写额外逻辑。
其他可选方案(非优先推荐)
  • 若可接受安装pgcrypto扩展,也可使用扩展提供的哈希函数配合固定转换逻辑实现,但本质和上述原生方案逻辑一致,无明显优势
  • 可自定义函数包装record转字符串逻辑并手动标记为IMMUTABLE,但该方案存在数据一致性风险:必须自行保证函数输出永远不受会话参数影响(比如在函数内部强制固定所有相关GUC参数值),否则一旦会话参数变化导致输出变化,生成列存储的哈希值会和实际内容不匹配
  • 触发器、视图为已知的实现方案,优先级低于原生生成列实现。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.09.03 01:19:07