真正安全的分页需结合WHERE条件+索引字段,禁用深分页;应用层硬编码最大LIMIT值并校验参数;PostgreSQL推荐FETCH FIRST和statement_timeout兜底;LIMIT不替代行级权限,须配合RLS或业务归属校验。
MySQL 用 LIMIT 最直接,但别只写数字
直接加 limit 1000 能拦住大结果集,但生产环境光这样不够。比如用户传参构造 select * from orders limit 0, 1000000,数据库照样得扫完整张表再截断,io 和内存照吃不误。
真正安全的做法是配合 WHERE 条件 + 索引字段做分页,而不是依赖 LIMIT 抗压。如果必须允许无条件查询,建议在应用层硬编码最大值(如 LIMIT 5000),并拒绝用户传入的 limit 参数。
- 永远避免
LIMIT配合OFFSET做深分页,LIMIT 100000, 20会让 MySQL 扫 100020 行再丢掉前 100000 行 - PostgreSQL 用户注意:
LIMIT行为类似,但FETCH FIRST 100 ROWS ONLY语义更明确,推荐优先用 - ORM 框架(如 Django、SQLAlchemy)通常有
query.limit()方法,但要确认它生成的是 SQL 层的LIMIT,不是 Python 层切片
PostgreSQL 的 statement_timeout 是兜底关键
即使加了 LIMIT,慢查询仍可能卡住连接池或拖垮并发。PostgreSQL 的 statement_timeout 能在服务端强制中断超时查询,比应用层重试更可靠。
它不是防泄露的主力,但能防止一个坏查询把整个 DB 实例拖死。设成 30s 或 60s 比设成 5s 更合理——太短会误杀正常聚合查询;太长又起不到保护作用。
- 全局设置写在
postgresql.conf:statement_timeout = 30000(单位毫秒) - 会话级临时生效:
SET statement_timeout = '30s'; - 注意:这个参数对已开始执行的查询才生效,无法阻止解析或计划阶段的卡死
应用层拦截:别让 LIMIT 参数从 URL 直通 SQL
常见漏洞是前端传 ?limit=9999999,后端不做校验就拼进 SQL。这等于主动给攻击者开全表扫描的门。
必须在 DAO 或查询构建层做白名单或范围限制。比如只接受 1–1000 的 limit,超出就返回 400 错误,而不是默默截断或降级。
- Node.js 示例:
const limit = Math.min(Math.max(parseInt(req.query.limit), 1), 1000); - Java Spring 中用
@Min(1) @Max(1000)注解约束 DTO 字段 - 特别警惕 ORM 的
raw()或execute()方法——它们绕过所有参数校验,limit变量若来自用户输入,极易被注入
防数据泄露:LIMIT 不等于权限控制
LIMIT 只控制返回行数,不控制“能查哪几行”。如果用户能任意改 WHERE user_id = ? 中的 ID,哪怕加了 LIMIT 1,也能遍历所有记录。
真正的防泄露靠行级权限(RLS)或应用层数据归属校验。PostgreSQL 的 RLS 策略可写成:CREATE POLICY select_own ON logs FOR SELECT USING (user_id = current_setting('app.current_user_id')::int);
- MySQL 8.0+ 也有行级安全策略,但需开启
sql_mode并配置CREATE POLICY - 如果数据库不支持 RLS,必须在业务代码里确保每个查询都带
AND tenant_id = ?或AND org_id = ?这类强绑定条件 - 测试时故意把
limit设成 1,然后循环改offset或主键条件,看是否能跨权读取——这是最常被忽略的渗透点
限制行数只是第一道缝,真正的防线在查询意图是否被严格约束。很多人调好 LIMIT 就以为万事大吉,却没发现 WHERE 条件是空的,或者用户 ID 是从 cookie 里随便读的。

















