MySQL连接级只读需满足InnoDB引擎、5.6+版本及非READ UNCOMMITTED隔离级别;PostgreSQL依赖用户权限与pg_hba.conf组合控制,临时表权限需单独限制。
MySQL 连接设为只读:SET SESSION TRANSACTION READ ONLY 有效但有前提
直接执行 set session transaction read only 确实能让当前连接拒绝写操作,但仅对 innodb 表生效,且要求 mysql 5.6+、事务隔离级别不是 read uncommitted。myisam 表完全不认这个指令,insert into myisam_table 依然能成功。
常见错误现象:ERROR 1792 (HY000): Cannot execute statement in a READ ONLY transaction 看似生效了,但一查发现只是部分表被拦住;或者压根没报错,因为连在 MyISAM 库上。
- 先确认引擎:
SELECT TABLE_NAME, ENGINE FROM information_schema.TABLES WHERE TABLE_SCHEMA = 'your_db'; - 确保会话已开启事务(否则该 SET 语句无效):
BEGIN;后再执行SET SESSION TRANSACTION READ ONLY; - 避免用
SET GLOBAL read_only = ON代替——那是全局只读,影响所有用户,且需要 SUPER 权限,不是单连接防护手段
PostgreSQL 的 pg_hba.conf + 用户权限组合才是可靠方案
PostgreSQL 没有“连接级只读”开关,靠 pg_hba.conf 控制连接来源 + 数据库用户权限控制行为。想让某台服务器的连接只读,本质是限制它所用数据库用户的写权限。
使用场景:应用服务器 A 固定用 app_ro 用户连接,你不想它误删数据,也不希望它因 bug 写脏数据。
- 撤销写权限:
REVOKE INSERT, UPDATE, DELETE, TRUNCATE, REFERENCES ON ALL TABLES IN SCHEMA public FROM app_ro; - 禁用 DDL:
REVOKE CREATE ON SCHEMA public FROM app_ro; - 在
pg_hba.conf中明确限定该用户只能从指定 IP 连接:host yourdb app_ro 192.168.10.5/32 md5 - 注意:权限变更后需
SELECT pg_reload_conf();或重启服务才能对新连接生效
SSH 隧道或代理层做只读拦截?别依赖 shell 权限控制
有人试图通过给连接用户分配 rbash 或限制 ~/.ssh/authorized_keys 中的 command= 来防 SSH 登录后乱跑,但这对数据库连接毫无意义——数据库访问走的是网络协议,不是 shell 命令行。
容易踩的坑:以为禁掉 rm、vi 就安全了,结果应用仍可通过 JDBC/ODBC 直连数据库并执行 DROP TABLE。
- 真正要拦的是数据库协议层的写请求,不是操作系统命令
- 如果必须走 SSH 隧道,应在隧道终点(即数据库服务器)严格配置数据库用户权限,而非在跳板机上做手脚
- 某些 DB 代理(如 ProxySQL、PgBouncer)支持查询重写或拦截,但需额外部署和维护,不是开箱即用方案
应用层连接字符串加 readonly 参数?多数驱动不解析
像 JDBC 的 ?readOnly=true 或 SQLAlchemy 的 connect_args={"readonly": True},只是提示驱动“我打算只读”,不强制数据库执行只读逻辑。PostgreSQL 驱动会忽略它;MySQL Connector/J 5.1+ 会尝试发 SET SESSION TRANSACTION READ ONLY,但如前所述,有引擎和事务前提。
性能影响很小,但别把它当防护手段——它不改变权限模型,也不触发数据库级拦截。
- JDBC 示例:
jdbc:mysql://host/db?readOnly=true&useSSL=false—— 仅建议,非保障 - Python psycopg2 不支持该参数;若硬加,会被静默丢弃
- 真正起作用的永远是数据库用户权限 + 引擎行为,不是连接串里的 flag
最易被忽略的一点:只读设置对 TEMPORARY TABLE、CREATE TEMP TABLE 通常无效,而这类操作可能泄露敏感数据或耗尽磁盘。生产环境若允许建临时表,得单独 REVOKE CREATE ON DATABASE 或限制 temp_tablespaces。










