不能。只读事务仅限制写操作,无法防御SQL注入,攻击者仍可执行SELECT、UNION、延时函数等恶意查询;防注入核心是参数化查询与最小权限账户配合。

只读事务真能防 SQL 注入吗
不能。只读事务 SET TRANSACTION READ ONLY 或 START TRANSACTION READ ONLY 仅限制写操作,对 SQL 注入毫无防御力——攻击者照样能执行 SELECT、UNION、EXECUTE(如 PostgreSQL 的 pg_sleep())甚至带副作用的函数。它防的是误写,不是恶意拼接。
常见错误现象:上线后开了只读事务,结果仍被扫出 1' OR SLEEP(5)-- 延时注入;日志里看到大量 SELECT ... FROM users WHERE id = '1' UNION SELECT password FROM admins ——这些查询在只读事务里照常执行。
真正起作用的其实是参数化查询 + 权限隔离
防注入的核心从来不是事务模式,而是让 SQL 结构和数据彻底分离。只读事务只有配合最小权限账户,才构成一层有效防线。
- 数据库用户必须只拥有
SELECT权限(禁止CREATE、EXECUTE、LOAD等) - 所有查询必须用预编译语句:
PreparedStatement(Java)、pg_query_params()(PHP/PgSQL)、cursor.execute("SELECT * FROM t WHERE id = %s", [user_id])(Python/psycopg2) - 禁用字符串拼接构造 SQL,尤其是拼接表名、字段名、排序方向等动态部分——这些无法参数化,需白名单校验
PostgreSQL / MySQL 中只读事务的实际用途
它主要解决一致性与并发问题,不是安全机制。比如长查询期间避免被其他事务的写操作阻塞,或确保多次 SELECT 看到同一快照。
使用场景:
- 报表导出:需要 5 秒跑完的聚合查询,要求中间不被
UPDATE干扰 - 跨多表校验:比如检查订单状态和库存是否匹配,需原子性快照
- 备库查询:MySQL 从库默认只读,但这是靠
read_only=ON全局配置,不是单个事务级设置
注意:BEGIN READ ONLY 在 PostgreSQL 中支持快照隔离;MySQL 8.0+ 的 START TRANSACTION READ ONLY 要求引擎为 InnoDB,且会拒绝任何 DML ——但 SELECT ... INTO OUTFILE 或调用含副作用的函数(如 SLEEP())仍可执行。
容易被忽略的“只读”陷阱
很多团队以为开了只读事务就高枕无忧,结果在测试环境漏掉三类关键点:
- 应用连接池复用连接,未显式重置事务状态,导致后续请求意外继承
READ ONLY模式,写操作静默失败或报错ERROR: cannot execute INSERT in a read-only transaction - ORM 框架(如 Django 的
transaction.atomic(using='replica', readonly=True))底层可能没真正发SET TRANSACTION READ ONLY,只是逻辑标记,需抓包验证 - 某些云数据库代理层(如阿里云 RDS Proxy)会忽略客户端发送的只读事务指令,实际仍路由到主库——得靠账号权限+网络策略双重控制
最稳妥的做法:把只读事务当性能/一致性工具用,防注入这件事,盯死 execute() 调用方式和数据库账号权限就行。

















