判断PostgreSQL中以SELECT开头的查询是否为只读是否可靠?
仅靠"SELECT开头"判断只读查询的问题分析
直接通过SQL语句是否以SELECT开头来判断是否为只读查询,存在不少漏洞,会导致误判,具体问题包括:
一、漏判只读查询
很多不会修改数据的查询并不以SELECT开头,比如:
- 系统查询类:
SHOW timezone、EXPLAIN ANALYZE SELECT * FROM users - 数据导出类:
COPY users TO '/tmp/users.csv' - 其他只读语句:
FETCH ALL FROM my_cursor、VALUES (1, 'a'), (2, 'b')
这些语句都不会修改数据库,但按你的判断逻辑会被错误分发到主库,浪费主库资源。
二、误判写操作
有些以SELECT开头的语句实际会修改数据库状态,不能在只读副本执行:
- 建表插入类:
SELECT id, name INTO new_users FROM old_users(会创建new_users表并插入数据) - 调用修改类函数:
SELECT pg_advisory_lock(123)(会获取排他锁,修改系统状态) - 加锁查询类:
SELECT * FROM users FOR UPDATE(会对行加排他锁,副本不支持这类写操作)
如果把这些语句分发到副本,会直接执行失败。
三、注释干扰
如果查询开头带注释,比如:
/* 业务查询:获取用户列表 */ SELECT * FROM users WHERE status = 1
你的判断逻辑会因为开头不是SELECT而误判为非只读,导致本该走副本的查询跑到主库。
改进方案
语法解析判断
用PostgreSQL内置的pg_parse_query()函数解析SQL语句,通过语法树判断语句类型。比如可以结合pg_stat_statements查看语句的命令类型:SELECT queryid, commandtype FROM pg_stat_statements WHERE query LIKE '%SELECT%';或者在应用端使用PostgreSQL语法解析库(比如libpg_query的封装),识别出
SELECTStmt、ShowStmt、ExplainStmt等只读类型,同时排除带FOR UPDATE/SHARE的SELECT语句、SELECT INTO这类写操作。补充业务规则过滤
除了语法解析,还要结合业务场景调整:- 对强一致性要求的查询(比如用户刚提交订单后查询订单状态),强制走主库,避免副本数据延迟导致的不一致
- 维护一份需要强制走主库的表/语句清单,作为兜底规则
利用只读连接属性兜底
给副本连接设置default_transaction_read_only = on,即使误发了写操作,PostgreSQL会直接拒绝执行,避免破坏副本数据,但这只是兜底手段,核心还是要做好语句识别。
内容的提问来源于stack exchange,提问作者szamani20
相关产品推荐
相关产品推荐

