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

PG8000或Postgres协议是否不支持DDL语句中的绑定参数?

问题描述

我尝试用PG8000创建PostgreSQL新用户,直接写死密码的代码可以正常运行,但密码会被写入服务器日志,存在安全隐患(本场景暂不考虑SQL注入):

with pg8000.dbapi.connect(**CONNECTION_INFO) as conn:
    csr = conn.cursor()
    csr.execute("create user example password 'password-123'")
    conn.commit()

之后用绑定参数查询用户信息是正常的:

with pg8000.dbapi.connect(**CONNECTION_INFO) as conn:
    csr = conn.cursor()
    csr.execute("select * from pg_user where usename = %s", ("example",))
    result = csr.fetchall()

但尝试用绑定参数创建用户时请求失败:

with pg8000.dbapi.connect(**CONNECTION_INFO) as conn:
    csr = conn.cursor()
    csr.execute("create user example password %s", ("password-123",))
    conn.commit()

客户端报错:

DatabaseError: {'S': 'ERROR', 'V': 'ERROR', 'C': '42601', 'M': 'syntax error at or near "$1"', 'P': '30', 'F': 'scan.l', 'L': '1145', 'R': 'scanner_yyerror'}

服务器端日志报错:

2022-08-26 12:30:02.029 UTC [205] LOG:  statement: begin transaction
2022-08-26 12:30:02.029 UTC [205] ERROR:  syntax error at or near "$1" at character 30
2022-08-26 12:30:02.029 UTC [205] STATEMENT:  create user example password $1

使用PG8000原生接口也会出现同样问题。

换成psycopg2后,带参数执行创建命令可以成功,但服务器日志显示客户端已经完成了参数替换,发送的是包含明文密码的完整SQL语句:

2022-08-26 12:30:55.317 UTC [206] LOG:  statement: BEGIN
2022-08-26 12:30:55.317 UTC [206] LOG:  statement: create user example2 password 'password-123'
2022-08-26 12:30:55.317 UTC [206] LOG:  statement: COMMIT

请问PG8000或Postgres客户端-服务器协议是否不支持DDL语句中的绑定参数?

解答

PostgreSQL的服务器端参数绑定(即把参数和SQL语句分开发送给服务器,由服务器完成替换)仅支持在DML语句(如SELECT、INSERT、UPDATE、DELETE)的特定位置使用,DDL语句(如CREATE USER、CREATE TABLE)的语法中,很多位置并不接受参数占位符。

你遇到的报错本质是Postgres解析create user example password $1时,认为$1的位置不符合语法规则——Postgres的DDL语法里,PASSWORD后面需要跟一个字符串字面量,而不是参数占位符,所以服务器会直接抛出语法错误,这和PG8000无关,是Postgres本身的限制。

至于psycopg2能成功执行,是因为它做了客户端侧的参数替换:在本地把占位符换成实际的密码值,再把完整的SQL语句发送给服务器,所以服务器日志里会看到明文密码,本质上和你直接写死密码的方式没有安全区别。

如果要避免密码写入服务器日志,你可以使用PostgreSQL的CREATE USER语句的另一种写法:调用crypt函数处理密码,同时使用参数绑定传递密码明文,示例如下:

with pg8000.dbapi.connect(**CONNECTION_INFO) as conn:
    csr = conn.cursor()
    csr.execute("create user example password crypt(%s, gen_salt('bf'))", ("password-123",))
    conn.commit()

这种写法中,crypt函数的参数可以使用占位符,服务器会正确解析,同时日志里只会记录crypt($1, gen_salt('bf')),不会出现明文密码。


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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.21 03:45:40