分页参数校验必须做,否则OFFSET可能为负或溢出;page至少为1、size限1–100;用参数化查询防注入;总数与分页数据查两次需考虑事务一致性;Pagination结构体应通过构造函数校验参数。

分页参数校验必须做,否则 OFFSET 可能为负或溢出
Go 里常见错误是直接用 page 和 size 算 OFFSET,却不检查边界。比如 page=0 或 size=-10 会传给数据库导致 SQL 报错或越权读取。
建议统一用非负整数约束:
-
page至少为1(前端传0时自动转成1) -
size限制在1–100区间(防恶意拉全表) - 计算
offset := (page - 1) * size,确保不会溢出int范围(尤其page极大时)
database/sql 拼接 LIMIT 和 OFFSET 要用问号占位符
别用字符串拼接构造 LIMIT ?, ?,否则容易被注入或类型不匹配。MySQL/PostgreSQL 都支持参数化 LIMIT 和 OFFSET,但注意驱动差异:
- MySQL 驱动(
github.com/go-sql-driver/mysql)支持Query中用?绑定int类型的LIMIT/OFFSET - PostgreSQL 驱动(
github.com/lib/pq)只支持$1,$2占位符,且LIMIT参数必须是int64 - SQLite 驱动(
github.com/mattn/go-sqlite3)也支持?,但要求参数为int64
rows, err := db.Query("SELECT id, name FROM users ORDER BY id LIMIT ? OFFSET ?", size, offset)
if err != nil {
return nil, err
}总数查询和分页数据查两次是常规做法,但要注意事务一致性
很多场景需要返回总条数(total)和当前页数据。最稳妥是执行两条 SQL:SELECT COUNT(*) + 主查询。但若业务对实时性敏感(如高并发写入),两次查询之间可能有数据变动。
立即学习“go语言免费学习笔记(深入)”;
本项目前后端分离,前端基于Vue+Vue-router+Vuex+Element-ui+Axios,参考小米商城实现。后端基于Node.js(Koa框架)+Mysql实现。前端包含了11个页面:首页、登录、注册、全部商品、商品详情页、关于我们、我的收藏、购物车、订单结算页面、我的订单以及错误处理页面。实现了商品的展示、商品分类查询、关键字搜索商品、商品详细信息展示、登录、注册、用户购物车、订单结算
应对方式:
- 读多写少场景:忽略微小不一致,直接查两次
- 强一致性要求:用子查询或 CTE 包裹(如 PostgreSQL 的
WITH t AS (...) SELECT *, COUNT(*) OVER() FROM t LIMIT ...),但性能略低 - 缓存总数:对不频繁变更的表,用 Redis 缓存
COUNT结果,配合写操作更新
封装分页结构体时,别把 Page 和 Size 暴露成可修改字段
定义类似 Pagination 结构体时,字段应设为只读或通过构造函数控制:
type Pagination struct {
Page int `json:"page"`
Size int `json:"size"`
Total int `json:"total"`
Data interface{} `json:"data"`
}
// ❌ 错误:外部可随意改 Page/Size,破坏校验逻辑
p := Pagination{Page: 0, Size: -5}
// ✅ 正确:用 NewPagination 强制校验
func NewPagination(page, size int) (*Pagination, error) {
if page < 1 {
page = 1
}
if size < 1 || size > 100 {
size = 20
}
return &Pagination{Page: page, Size: size}, nil
}
真正难处理的是带条件的动态分页(比如多个 WHERE 字段 + 排序字段可变),那得靠构建器模式或 SQL 模板,不是加个结构体就能解决的。









