SQL Server 2012+ 唯一原生分页语法是 OFFSET/FETCH,必须配合确定性 ORDER BY(如 ORDER BY id DESC, created_at DESC),偏移量为 (@PageNumber-1)*@PageSize,不支持 GROUP BY 直接分页,需用 CTE 或子查询封装聚合逻辑。

SQL Server 的 OFFSET/LIMIT 语法不原生支持
SQL Server 在 2012+ 版本才通过 OFFSET ... FETCH NEXT 支持分页,而 GORM 默认拼的是 MySQL/PostgreSQL 风格的 LIMIT ... OFFSET。直接在 SQL Server 上用 db.Offset().Limit() 会报错:Incorrect syntax near 'OFFSET' —— 因为老版本 SQL Server(如 2008)根本不认这个写法,即使新版,GORM v2 也**不会自动改写 SQL 语法**。
实操建议:
- GORM 连接 SQL Server 时,必须显式启用
sqlserverdialect,并确认驱动版本支持OFFSET/FETCH(如github.com/denisenkom/go-mssqldbv1.4+) - 避免手动拼
"OFFSET ? ROWS FETCH NEXT ? ROWS ONLY"—— GORM 的Offset/Limit方法在正确配置下会自动转译,但前提是没混用其他不兼容链式调用(比如Joins后再Offset可能失效) - 若用 GORM v2 +
go-mssqldb,需确保连接字符串含database=xxx且未开启disableOrderByNulls等干扰排序的选项
ORDER BY 是 SQL Server 分页的硬性前提
SQL Server 要求 OFFSET/FETCH 必须配合 ORDER BY,否则报错:The ORDER BY clause is mandatory for the OFFSET and FETCH clauses。这比 MySQL/PostgreSQL 更严格 —— 即使你只想要随机前 10 条,也不能写 ORDER BY (SELECT NULL) 来绕过,GORM 不会帮你补。
实操建议:
-
db.Order("id ASC").Limit(10).Offset(20)是安全写法;漏掉.Order()会导致查询 panic - 别依赖
db.Order("created_at DESC")单字段排序:SQL Server 在时间精度为秒级、多条记录同秒时,OFFSET行为不可预测,必须加二级排序,例如.Order("created_at DESC, id DESC") - 如果业务真需要无序取样(如后台抽样),改用
db.Limit(10).Find(&list)+ 应用层 shuffle,别碰OFFSET
Count 查询在 SQL Server 上容易慢且不准
SQL Server 对带 JOIN 或 WHERE 子句的 COUNT(*) 执行计划敏感,尤其当关联表没建好索引时,db.Model(&User{}).Joins("JOIN profiles...").Where(...).Count(&total) 可能比主查询还慢 3 倍。更糟的是,GORM 的 Count() 在 SQL Server 下仍会复用前面链上的 Limit/Offset,导致返回值恒为 0 或上限值。
实操建议:
- 总数查询必须拆开:先构建条件查询对象(如
query := db.Where("status = ?", "active")),再分别调query.Count(&total)和query.Order(...).Limit().Offset().Find(&list) - 对复杂关联,优先手写子查询:
db.Raw("SELECT COUNT(*) FROM (SELECT u.id FROM users u LEFT JOIN profiles p ON u.id = p.user_id WHERE u.status = ?) AS t", "active").Scan(&total) - SQL Server 的
COUNT(1)和COUNT(*)性能无差别,不用刻意换;但COUNT(id)会忽略 NULL,慎用
大数据量下 OFFSET 性能断崖式下跌
SQL Server 的 OFFSET 100000 ROWS 并非跳过索引位置,而是强制扫描并丢弃前 N 行——哪怕 id 有聚集索引。实测 50 万行表,OFFSET 200000 响应从 12ms 拉到 1800ms。这不是 GORM 的锅,是 SQL Server 引擎限制。
实操建议:
- 超过 10 万行数据,必须放弃
OFFSET,改用游标分页:前端传上一页最后一条的id和created_at,后端写WHERE id < ? ORDER BY id DESC - 游标字段必须有索引:单列
id聚集索引足够,但若按时间分页,得建INDEX IX_created_at_id ON users(created_at DESC, id DESC) - 别指望
SET ROWCOUNT替代OFFSET—— 它已被标记为“即将废弃”,且不支持FETCH语义
OFFSET/FETCH、强制加确定性 ORDER BY、总数和列表查彻底隔离。最容易被忽略的是——即使你代码里写了 Order,只要关联查询中某个 JOIN 表没索引,SQL Server 就可能退化成全表扫描,让分页延迟翻倍。


















