SQL Server中存储过程“只读”靠权限控制而非代码开关,需显式DENY INSERT/UPDATE/DELETE、移除db_datawriter角色,并禁用TRUSTWORTHY;PostgreSQL须确保函数为SECURITY INVOKER;MySQL应设SQL SECURITY INVOKER或低权限DEFINER。

SQL Server 中让存储过程只读:关键不是改代码,而是改权限
存储过程本身没有「只读模式」开关。所谓“只读”,实际是通过限制执行者对底层表的写权限来实现的——哪怕 sp_executesql 里写了 UPDATE,只要用户没 UPDATE 权,运行就直接报错。
必须收回的三个权限:INSERT / UPDATE / DELETE
只给 EXECUTE 权限远远不够。常见错误是只授权了存储过程执行权,却忘了用户仍可能通过其他方式(比如直连表、或调用另一个有写权限的 SP)修改数据。
- 显式执行
DENY INSERT ON [dbo].[Orders] TO [app_user],比REVOKE更可靠(DENY优先级高于GRANT) - 注意 schema 级别权限:如果用户有
db_datawriter角色,所有表的写权限都自动生效,必须先ALTER ROLE db_datawriter DROP MEMBER [app_user] - 检查是否启用了
TRUSTWORTHY或签名证书——这些可能绕过权限检查,生产环境建议关掉
PostgreSQL 怎么办:FUNCTION 可以设 SECURITY DEFINER,但要小心
PostgreSQL 的函数默认以调用者身份执行(SECURITY INVOKER),所以只要用户没表写权限,函数里写 UPDATE 也会失败。但有人会改成 SECURITY DEFINER 来提权,这就彻底破坏只读目标。
- 确认函数定义里没有
SECURITY DEFINER;如有,改成SECURITY INVOKER(默认值) - 避免用
SET search_path切到高权限 schema;否则可能无意访问到可写的同名表 - PostgreSQL 15+ 支持
CREATE FUNCTION ... WITH (parallel = safe),但这和只读无关,别混淆
MySQL 存储过程权限验证容易漏掉的一点:DEFINER 账户权限
MySQL 存储过程默认以 DEFINER 身份执行。如果 DEFINER='root@%',那无论调用者是谁,都会用 root 权限跑完整个过程——等于完全绕过只读控制。
- 创建时显式指定低权限账户:
CREATE DEFINER='readonly_user@localhost' PROCEDURE... - 或者改用
SQL SECURITY INVOKER(MySQL 5.7+ 支持),强制按调用者权限执行 - 检查现有过程:用
SELECT ROUTINE_DEFINITION, SECURITY_TYPE FROM INFORMATION_SCHEMA.ROUTINES WHERE ROUTINE_NAME = 'your_proc';
最易被忽略的是跨库访问和视图依赖——如果存储过程查的是视图,而该视图底层 JOIN 了用户有写权限的表,权限检查仍会失败。得一层层验清楚实际触达的表和权限链。

















