递归CTE是SQL中处理树形结构(如组织架构、权限继承)的标准方案,需包含锚点成员(根节点查询)和递归成员(自引用JOIN),并限制层级防死循环;SQL Server不支持RECURSIVE关键字,须用UNION ALL连接且CTE名必须被递归引用,而PostgreSQL/MySQL 8.0+支持WITH RECURSIVE语法。

子查询在 WHERE 中做权限过滤,为什么不能直接用 IN?
因为 IN 对空结果集返回 false(不是 null),导致整个条件为假,用户查不到任何数据——哪怕他本该有权限。比如:WHERE user_id IN (SELECT user_id FROM access_control WHERE role = 'admin'),当子查询没返回任何行时,IN 表达式结果是 FALSE,而非 UNKNOWN,这会把合法用户也过滤掉。
更稳妥的做法是用 EXISTS,它对空子查询返回 FALSE,但语义更清晰、行为更可控:
-
EXISTS只关心子查询是否返回至少一行,不依赖值比较,天然规避空结果陷阱 - 子查询里必须关联外层表(如加
AND t.user_id = ac.user_id),否则变成非相关子查询,可能误判 - MySQL 8.0+ 和 PostgreSQL 都支持
LATERAL(或JOIN LATERAL)做动态权限展开,但多数场景用EXISTS更轻量
多级组织架构下,如何用递归 CTE 做向上继承的权限判断?
当权限按部门树继承(比如“能看本部门及所有子部门数据”),硬编码 OR dept_id IN (..., ..., ...) 不可维护。PostgreSQL 或 SQL Server 可用递归 CTE 构建路径,MySQL 8.0+ 也支持。
关键点是:递归部分必须引用自身别名,并限制层级防止死循环:
- 起始查询选用户直属部门:
SELECT dept_id FROM user_dept WHERE user_id = 123 - 递归查询 join
dept_tree表找下级:SELECT dt.child_dept FROM dept_tree dt JOIN dept_path dp ON dt.parent_dept = dp.dept_id - 加
WHERE level (或类似限制)避免无限递归 - 最终主查询用
EXISTS (SELECT 1 FROM dept_path WHERE dept_path.dept_id = data.dept_id)关联
性能差?检查子查询是否走了索引,尤其注意 correlated 子查询的执行次数
相关子查询(correlated subquery)每行都执行一次,如果外层扫描 10 万行,子查询就跑 10 万次——哪怕它只查一个 user_role 表。慢的根本原因常在这里。
优化方向很实际:
- 确保子查询中
WHERE条件字段(如user_id、resource_id)有联合索引,例如CREATE INDEX idx_user_perm ON user_permission (user_id, resource_type) - 把子查询改写成
JOIN(尤其当权限表不大时),让优化器一次性 hash join,比反复 lookup 快得多 - PostgreSQL 可用
MATERIALIZED强制物化子查询结果(9.3+),但仅适用于结果集稳定、复用率高的场景 - 避免在子查询里调用函数(如
get_user_groups(user_id)),函数无法走索引,且可能被多次求值
跨库或微服务场景下,SQL 子查询没法直接查权限服务,怎么办?
纯 SQL 解决不了跨进程权限校验。这时候子查询得退场,换成应用层预过滤或代理层注入。
可行路径只有三条:
- 应用启动时拉取用户权限快照(如角色 ID 列表),缓存在内存或 Redis,SQL 里用
WHERE role_id IN (:cached_roles)—— 注意要定期刷新,避免权限延迟 - 用数据库视图封装权限逻辑(如
CREATE VIEW user_data AS SELECT * FROM raw_table t WHERE EXISTS (...)),但视图仍受限于同库,跨库无效 - 在 API 网关或 ORM 层拦截 SQL,动态拼接权限条件(如 MyBatis 的
<where>+<foreach>),把权限决策从 SQL 拆到服务端
真正难的不是写对子查询,而是权衡一致性、延迟和耦合度——比如缓存过期窗口设 30 秒,意味着权限变更后最多要等半分钟才生效。

















