分页性能差的主因是order by字段无索引导致全表扫描;需建复合索引(如(status, created_at))、确保where条件类型匹配、遵守最左前缀原则、游标分页时索引字段必须高选择性且类型严格匹配,并用explain验证索引实际使用情况。

ORDER BY 字段没索引,分页直接变全表扫描
这是最常见也最容易被忽略的硬伤。GORM 或原生 db.Query 执行 LIMIT/OFFSET 分页时,如果 ORDER BY 的字段(比如 created_at)没建索引,MySQL/PostgreSQL 就必须先排序全部数据再截取,EXPLAIN 里会明确出现 Using filesort,响应时间随数据量线性增长。
实操建议:
- 用
EXPLAIN SELECT * FROM orders ORDER BY created_at DESC LIMIT 20 OFFSET 1000验证,看key列是否为空、Extra是否含Using filesort - 建索引别只写
created_at单列——如果查询还带WHERE status = ?,优先建复合索引(status, created_at),兼顾过滤和排序 - Go 中用 GORM 迁移时:
db.Migrator().CreateIndex(&Order{}, "idx_status_created_at"),字段顺序按查询实际使用习惯排
WHERE 条件字段类型不匹配,索引悄悄失效
Go 应用传参时,看似写了 WHERE user_id = ?,但如果数据库字段是 INT,而你传的是字符串 "123",MySQL 会隐式转成 CAST(user_id AS CHAR),导致索引无法命中。现象是分页首屏快、翻几页就卡住,EXPLAIN 显示 type: ALL。
实操建议:
- 查表结构确认字段类型:
DESCRIBE orders,比对 Go 代码中传参类型(int64vsstring) - 用预编译语句强制类型对齐:
stmt, _ := db.Prepare("SELECT * FROM orders WHERE user_id = ?"),然后stmt.Query(123)(传整数,不是"123") - GORM 中避免用
Where("user_id = ?", "123"),改用Where("user_id = ?", uint(123))或直接绑定 struct 字段
复合索引没守最左前缀,WHERE + ORDER BY 白搭
建了 (status, created_at) 索引,但查询写成 WHERE created_at > ? ORDER BY created_at,MySQL 无法跳过 status 直接用第二列——索引完全失效。更隐蔽的是,GORM 的 Scopes 或 @Query 拼 SQL 时,容易漏掉前置条件。
实操建议:
- 复合索引字段顺序必须和查询条件严格对齐:要支持
WHERE status = ? AND created_at > ? ORDER BY created_at,索引就得是(status, created_at) - 如果业务既要按
status查,又要单独按created_at查,宁可多建一个单列索引created_at,别指望复合索引“兼职” - 用
EXPLAIN检查key_len:值越小说明用到的索引前缀越短,key_len为 0 就等于没用上
游标分页仍踩索引坑:last_id 值未走索引
放弃 OFFSET 改用游标分页(WHERE id > ? ORDER BY id LIMIT 20)后性能飙升,但若 id 字段没主键或没索引,或者前端传错字段(比如传了 created_at 字符串却没加索引),照样慢。尤其当 id 是 UUID 类型且没建索引时,等同于全表扫。
实操建议:
- 游标字段必须是高选择性、有索引、且类型严格匹配的列;首选自增
id或带索引的created_at,避开status这类低选择性字段 - GORM 写法必须显式指定索引字段:
db.Where("id > ?", lastID).Order("id ASC").Limit(20).Find(&users),不能漏Order - 首次请求不要用
WHERE id > 0,而应直接ORDER BY id ASC LIMIT 20,避免因id不连续导致空结果
真正卡住人的,往往不是不会建索引,而是建了索引却没验证它是否真被用上——EXPLAIN 必须在每个分页接口上线前跑一遍,且用真实数据量压测。索引不是贴膏药,是精密手术。
golang免费学习笔记(深入):立即使用
在学习笔记中,你将探索golang的核心概念和高级技巧!











