hrm系统分页必须手写limit+offset,page≥1、page_size∈(0,100],order by须显式指定索引字段,总数查询需隔离db实例,超5万数据应切游标分页。

分页必须手写 Limit + Offset,别找 Paginate
GORM 没有 Paginate 方法,这是 HRM 系统上线后查不出第 3 页数据的最常见原因。所谓“自动分页”全是第三方封装,比如 github.com/joshbetz/pagination,但它不校验 page=0 或 page_size=0,在员工档案表(如 employees)上一用就 panic 或漏人。
HRM 场景下最稳的做法就是自己算:Offset((page - 1) * page_size),再接 Limit(page_size)。顺序建议固定为先 Offset 后 Limit,哪怕 GORM v2 允许互换,也避免在老版本 MySQL 驱动下出错。
- page 必须 ≥ 1:前端传
page=0或空值时,直接设为 1,别返回 400 —— HR 管理员点错页码很常见 - page_size 必须 > 0 且 ≤ 100:设上限防刷,比如
size := min(p.PageSize, 100),别让前端传个 5000 导致慢查询拖垮数据库 - 别信文档里“支持链式调用”的说法:GORM 的
Count()会继承前面的Limit和Offset,查总数时必须另起新会话
Order BY 不加,员工列表会丢人或重复
HRM 系统里员工档案常按 entry_date DESC 或 id ASC 排序,但如果你只写 Find(&employees) 没加 Order,数据库返回顺序完全不可控。尤其在每日批量入职/离职后,同一页可能漏掉刚录入的新人,或重复显示已转岗的老员工。
正确写法是显式指定带索引的排序字段:
- 优先用主键:
Order("id ASC")—— 所有表都有,且必走索引 - 业务排序用复合索引:
Order("entry_date DESC, id DESC")—— 防止多个员工同一天入职时顺序乱 - 绝对别用
Order("RAND()")做“随机查看”,HR 审核流程不允许
注意:MySQL 对 entry_date 单字段排序,若没建索引,OFFSET 10000 会直接卡住,不是 Go 层的问题,是 SQL 执行计划崩了。
总数查询必须隔离 db 实例,否则 total=10
你在写 db.Where("dept_id = ?", deptID).Limit(size).Offset(offset).Count(&total)?那 total 永远是 ≤ size 的数 —— 因为 Count() 复用了前面的 Limit 条件,实际执行的是 COUNT(*) LIMIT 10,不是全量统计。
HRM 系统要显示“共 237 人”,就得断开链式污染:
- 用新会话:
db.Session(&gorm.Session{NewDB: true}).Model(&Employee{}).Where("dept_id = ?", deptID).Count(&total) - 复杂 JOIN 场景(比如查员工+部门+职级)更推荐手写子查询:
db.Raw("SELECT COUNT(*) FROM (SELECT e.id FROM employees e LEFT JOIN depts d ON e.dept_id = d.id WHERE d.name LIKE ?) AS t", "%技术%").Scan(&total) - 如果只是给前端分页控件用,且不要求精确总页数,可跳过
Count,改查size + 1条,判断是否有下一页
超 5 万条档案时,立刻切游标分页
HRM 系统运行三年后,employees 表很容易突破 50 万行。这时 OFFSET 50000 在 MySQL 上不是“跳过”,而是真实扫描并丢弃前 5 万行 —— 查询从 20ms 涨到 2s,管理员点下一页要等半天。
游标分页不是“高级技巧”,是 HRM 这类长生命周期系统的刚需:
- 首次请求:
db.Order("id ASC").Limit(20).Find(&emps),取emps[len(emps)-1].ID作为cursor - 下一页请求:
db.Where("id > ?", cursor).Order("id ASC").Limit(20).Find(&emps) - 关键约束:排序字段必须有索引;不能在游标分页里用
Preload("contracts"),得先查 ID 列表,再用IN批量加载合同
最后提醒一句:游标分页没法跳转到“第 87 页”,但 HRM 真正需要的从来不是跳页,而是“继续看下一批”和“按条件筛选后重新开始”。把精力放在建好 idx_dept_entry 这类联合索引上,比折腾封装函数实在得多。











