使用 url_encode 扩展是最省事的选择,它轻量可靠、支持 utf-8,安装后即可当原生函数使用,避免手写 pl/pgsql 带来的编码错误与性能问题。

直接用 url_encode 扩展是最省事的选择
PostgreSQL 本身不提供 url_encode 或 url_decode 内置函数,但社区扩展 url_encode(来自 https://github.com/okbob/url_encode)封装得足够轻量、可靠,且支持 UTF-8 多字节字符(比如中文、重音符号等)。安装后就能当原生函数用,无需自己写 PL/pgSQL 逻辑。
常见错误现象:手动拼接 % + 十六进制时漏掉空格转 +、或把 + 当普通字符未还原;对非 ASCII 字符按单字节处理导致解码乱码。
- 必须以超级用户身份执行:
CREATE EXTENSION url_encode; - 编码结果兼容 RFC 3986:空格 →
+,非字母数字字符 →%XX,汉字如你好→%E4%BD%A0%E5%A5%BD - 注意它还额外提供
uri_encode/uri_decode:对/、:、@等 URI 保留字符不做编码,适合完整 URL 场景
不用扩展时,用 PL/pgSQL 自建 url_decode 函数要小心字节流拼接
很多团队因权限限制无法装扩展,会选择手写 url_decode。核心难点不是正则匹配 %XX,而是把十六进制串正确还原为 UTF-8 字节再转成文本 —— 错误做法是直接用 convert_from(decode(...), 'utf8'),但 decode() 只接受偶数长度的 hex 字符串,而 % 后可能只有 1 个字符(比如 %2),或遇到非法序列(如 %GG)时静默失败。
真实使用场景:ETL 中清洗从 HTTP 日志或埋点表里拿到的已编码 query 参数,比如 utm_source=app%2Fios。
- 务必在
FOR ... LOOP中逐段提取,用length(byte) = 3判断是否为%XX形式 - 对非
%开头的字符(包括+),先做replace(..., '+', ' ')再转bytea - 最后统一用
convert_from(bin, 'utf8'),不要分段 decode 后拼字符串
url_encode 自定义函数容易在空格和特殊字符上翻车
手写 url_encode 比解码更易出错,因为要区分三类字符:保留字符(如 /、?)、不安全字符(如空格、中文)、安全字符(ASCII 字母数字)。RFC 规定保留字符在 URI 路径中不应编码,但在 query 参数中可以编码 —— 但多数业务只要求 query 安全传输,所以统一编码更稳妥。
性能影响:纯 PL/pgSQL 实现比 C 扩展慢 5–10 倍,尤其对长文本(>1KB);每字符循环 + 正则判断会放大开销。
- 别用
regexp_replace(encode(...), '(.{2})', '%\1', 'g')这种写法:它把整个字符串先转 hex,再补%,但会把 ASCII 字符(如A→41→%41)也编码,违反规范 - 正确逻辑是:先
regexp_matches(input, '([^\w.~-]| )', 'g')提取需编码字符,再对每个匹配项单独encode(x::bytea, 'hex')并加% - 空格必须映射为
+(不是%20),否则部分老客户端(如某些嵌入式 HTTP 库)解析异常
连接字符串里的特殊字符必须 URL 编码,否则解析失败
这不是数据内容编码,而是连接 URL 本身的语法要求。例如密码含 #、@、/ 时,PostgreSQL JDBC 或 libpq 会提前截断 —— 因为这些字符在 URL 里有结构含义(# 表 fragment,@ 分隔 auth 和 host)。
错误现象:postgresql://user:p#ss@host/db 会被解析成 user=user、password=p,后面全丢弃,报错 invalid port 或 could not parse DATA_SOURCE_NAME。
- 必须对整个 password、username、dbname 字段单独编码,不是对整个 URL
- 用
url_encode扩展或语言层工具(如 Java 的URLEncoder.encode(pwd, "UTF-8"))编码后再拼接 - 特别注意:编码后不能再次 encode,比如
%已变成%25,再 encode 就变%2525,连接时解码失败
uri_encode(保留 /)去编码密码会导致 @ 不被转义,依然解析失败。该严格的地方一点都不能妥协。











