读写分离不能防SQL注入,真正有效的是参数化查询、权限最小化和只读库账户隔离;需禁用危险权限、关闭危险函数、杜绝SQL拼接,并确保只读流量正确路由至从库。

读写分离真能防SQL注入攻击?
不能。读写分离本身不加固SQL系统,它只是把查询和修改操作分到不同数据库节点,对注入攻击毫无防御能力——攻击者照样能往SELECT语句里塞恶意payload,只要连的是只读库,照样执行成功。
真正起作用的是:参数化查询 + 权限最小化 + 只读库账户隔离。读写分离只有配合这些,才能让攻击者“打进来也干不了几件事”。
- 只读库账号必须禁用
INSERT、UPDATE、DELETE、DROP、EXECUTE等权限,连LOAD_FILE()这类危险函数也要关掉 - 应用层绝不能拼接SQL,所有用户输入必须走
PreparedStatement(Java)、pg_query_params()(PHP/PgSQL)或ORM的参数绑定接口 - 如果用了中间件(如
MyCat或ProxySQL),要确认它没开启allow_sql_injection类配置项——有些老版本默认开
MySQL主从架构下,如何让只读流量真的落到从库?
很多团队配了主从,但SELECT还是全打到主库,根本原因是应用没做路由控制,或者ORM/驱动自动降级回主库了。
关键不在数据库配置,而在客户端怎么发请求:
- Spring Boot +
ShardingSphere-JDBC:必须显式配置readwrite-splitting规则,并在@Select方法上加@ReadOnly注解(或通过Hint强制路由) - PHP PDO:不能只靠
PDO::ATTR_EMULATE_PREPARES = false,得用mysqlnd_ms扩展或自己实现连接池+读写标记,否则SELECT仍走主库 - Node.js +
mysql2:需手动维护两个连接池(masterPool/slavePool),并在业务逻辑中明确调用slavePool.execute(),别依赖任何“自动识别SELECT”的中间层
常见错误现象:SHOW PROCESSLIST里看到大量SELECT在主库运行;监控显示从库QPS长期接近0。
PostgreSQL用pgbouncer做连接池时,读写分离怎么不出错?
pgbouncer默认是事务级池化,而读写分离要求语句级路由——一个事务里混用SELECT和UPDATE,就会因目标库不一致直接报错ERROR: cannot execute INSERT in a read-only transaction。
解决方案只有两个,且必须二选一:
- 改用
session池模式(pool_mode = session),并确保每个HTTP请求只连一个库——但这会丧失连接复用优势,连接数暴涨 - 彻底放弃
pgbouncer做读写路由,改用pgpool-II,它支持load_balance_mode = on且能解析SQL类型,但要注意:它不支持PostgreSQL 15+的某些协议特性,升级前务必验证
另一个坑:pgbouncer的ignore_startup_parameters若设为all,可能让应用层设置的application_name丢失,导致基于该字段的路由策略失效。
为什么WAF或SQL防火墙拦不住绕过读写分离的注入?
因为WAF规则依赖SQL语法特征,而现代注入常走UNION SELECT查只读库数据、或用SLEEP()/BENCHMARK()做盲注——这些在只读库完全合法,WAF无法区分“正常报表查询”和“探测表结构的SELECT table_name FROM information_schema.tables”。
更麻烦的是:如果应用把ORDER BY字段名直接拼进SQL(如ORDER BY $_GET['sort']),WAF通常放过,但攻击者可注入id, (SELECT password FROM users LIMIT 1),只读库照单全收。
- 真正有效的做法是:在只读库账号上禁用
information_schema和pg_catalog的SELECT权限(PostgreSQL需REVOKE SELECT ON ALL TABLES IN SCHEMA pg_catalog FROM readonly_user) - MySQL 8.0+ 可用
ROLE机制限制只读账号只能查指定视图,而非原始表 - 别指望WAF替代应用层输入校验——它只是最后一道沙袋,不是防洪堤
最易被忽略的一点:从库延迟可能导致攻击者看到过期数据,但反过来,这也意味着他们无法实时确认注入是否成功,所以延迟本身算一种被动干扰。不过别依赖这个——它不稳定,也不解决根本问题。

















