应明确列出SELECT字段而非使用*,尤其涉及敏感信息时;需在应用层按权限白名单裁剪字段,避免SQL层硬编码逻辑,并注意JOIN时的字段冲突与别名泄露风险。

SELECT 里别写 *,尤其在涉及用户表或权限表时
直接用 SELECT * 是最常见也最危险的起点。数据库表一旦加了敏感字段(比如 password_hash、id_card、email、phone),查询结果就自动“带出去”了,后端没做字段过滤的话,前端或日志里可能直接暴露。
实操建议:
- 明确列出需要的字段,一个一个写,哪怕多敲几下键盘
- 对每个字段问一句:“这个字段会不会被前端渲染?会不会进日志?有没有权限校验?”
- 如果表结构常变,考虑用视图封装安全字段集,比如
CREATE VIEW user_public AS SELECT id, name, avatar FROM users - ORM 场景下,避免用
Model.objects.all()(Django)或User.find()(TypeORM)直接取全量——改用.values('id', 'name')或.select(['id', 'name'])
用 EXCEPT 或子查询剔除敏感字段不现实
有人想“先选全部,再减掉几个”,比如 SELECT * FROM users EXCEPT SELECT password_hash FROM users —— 这语法根本不存在。EXCEPT 是集合差集,不是字段筛选,它要求左右两边列数、类型、顺序完全一致,没法用来“删字段”。
真实限制:
-
SELECT列表只能是显式字段、表达式或常量,没有“排除某几列”的语法糖 - PostgreSQL 有
SELECT * EXCEPT (col1, col2)(v14+),但 MySQL、SQL Server、SQLite 都不支持,别依赖 - 试图用 JSON 函数动态拼字段(如
JSON_KEYS)属于高风险 hack,性能差、不可读、难维护
后端代码里做字段裁剪比 SQL 层更可控
SQL 层做不到“按角色动态剔除”,但应用层可以。比如查出完整数据后,根据当前用户权限决定返回哪些字段。
关键点:
- 不要在 SQL 里硬编码角色逻辑(如
CASE WHEN role='admin' THEN email END),容易漏判、难测试 - 用白名单方式裁剪:定义好每类接口允许返回的字段集,比如
public_fields = ['id', 'name', 'avatar'],然后用dict(filter(lambda kv: kv[0] in public_fields, row.items()))(Python)或pick(row, public_fields)(Lodash) - 注意 NULL 字段也要裁——有些 ORM 会把未查询字段设为
None或空字符串,但依然占序列化体积 - 如果用了 GraphQL,天然支持字段按需选取,
password_hash根本不会进 resolver 参数
警惕 JOIN 后的字段名冲突和意外泄露
多表 JOIN 时,两个表都有 id 或 created_at,用 SELECT * 就会重复、覆盖,甚至把权限表里的 is_deleted 或 deleted_by 暴露出去。
正确做法:
- 所有字段加表别名前缀:
SELECT u.id, u.name, o.status FROM users u JOIN orders o ON u.id = o.user_id - 敏感字段即使不显式 SELECT,也要确认没被别名“偷偷带出来”,比如
SELECT u.*, o.status中的u.*仍含u.password_hash - 用
DESCRIBE或EXPLAIN看实际返回列(MySQL);PostgreSQL 可用\d+ table_name查字段注释,辅助识别敏感列
事情说清了就结束。真正难的不是“怎么删字段”,而是每次写 SQL 前,得真花三秒想清楚:这行结果,下一个环节是谁在用?它有没有能力处理敏感数据?

















