gorm中加gorm:"index"标签只是起点,索引是否生效取决于字段选择、顺序、基数及查询写法;常见原因包括数据库未实际建索引、字段类型不匹配(如text无b-tree支持)、查询条件导致索引失效(如函数包裹或like前置通配符),复合索引需严格按最左匹配原则对齐查询条件,且必须用explain验证。

直接说结论:GORM里加gorm:"index"标签只是起点,不是终点;索引是否生效、查得快不快,取决于字段选择、顺序、基数和查询写法,而不是有没有打标签。
为什么gorm:"index"加了但查询还是慢?
常见错误现象是:模型字段打了gorm:"index",迁移也成功了,但SELECT * FROM users WHERE name = ?依然走全表扫描。原因通常有三个:
- 数据库实际没建出索引——GORM默认只在
AutoMigrate时创建索引,如果你用的是手动SQL建表或跳过了迁移,标签完全无效 - 字段类型不匹配——比如Go里定义
Name string,但数据库列是TEXT(尤其PostgreSQL),B-tree索引对TEXT默认不生效,需显式指定type:btree或改用VARCHAR - 查询条件触发索引失效——例如
WHERE LOWER(name) = ?或WHERE name LIKE '%张%',函数包裹或前置通配符会让索引无法使用
复合索引怎么写才真正被WHERE a = ? AND b = ?用上?
关键不是“多个字段加index”,而是字段顺序必须和查询条件严格对齐。GORM中定义如下:
type Order struct {
ID uint `gorm:"primaryKey"`
Status string `gorm:"index:idx_status_user"`
UserID uint `gorm:"index:idx_status_user"`
CreatedAt time.Time
}
这个idx_status_user索引只对WHERE status = ? AND user_id = ?高效;如果查询只写WHERE user_id = ?,索引大概率被忽略(除非数据库优化器强行选上)。更安全的做法是:
- 把过滤性最强的字段放最左——比如
status只有3个值(待处理/已完成/已取消),而user_id有百万级唯一值,那user_id应该放第一位 - 避免冗余索引——已有
(user_id, status),就别再单独建user_id单列索引 - 用
EXPLAIN验证:在MySQL里跑EXPLAIN SELECT * FROM orders WHERE status = 'done' AND user_id = 123,看key列是否显示idx_status_user
全文检索别硬扛LIKE,PostgreSQL用to_tsvector要配对
Go里写WHERE name ILIKE '%搜索词%'在数据量超几千行后就会卡顿,且中文完全不可靠。PostgreSQL原生全文检索需要前后端配合:
- 建表时确保字段支持中文分词:
CREATE EXTENSION IF NOT EXISTS zhparser;,再建配置CREATE TEXT SEARCH CONFIGURATION chinese (PARSER = zhparser); - GORM模型里不能只靠tag,得在SQL里显式调用:
to_tsvector('chinese', title || ' ' || content)和to_tsquery('chinese', ?) - Go代码里扫描
tsvector字段会报错sql: Scan error on column index 0: unsupported driver -> Scan pair: []uint8 -> *string,必须用pgtype.TSVector或先CONVERT TO TEXT再解析 - 别漏掉
@@操作符——这是触发全文索引的关键,写成WHERE to_tsvector(...) @@ to_tsquery(...),少一个符号就退化成普通字符串匹配
go-zero或gorp项目里,索引和缓存怎么不打架?
go-zero用goctl生成代码时,默认给单字段索引字段加缓存(如FindByMobile),但如果你同时在mobile上建了复合索引(mobile, status),缓存层可能取不到新字段,导致数据不一致。gorp同理——AddIndex只管建索引,不管缓存逻辑。实际要盯住三点:
- 缓存key设计是否覆盖索引字段组合——比如复合索引查
mobile + status,缓存key就得是cache:order:mobile:13800138000:status:done,不能只用mobile - 写操作后缓存失效是否同步——GORM的
Save不会自动删缓存,得自己加redis.Del或用go-zero的@CacheExpire注解 - 索引字段变更时,缓存策略是否同步更新——比如把
status从VARCHAR(20)改成ENUM,缓存反序列化可能失败
最常被忽略的一点:索引本身不解决N+1查询。哪怕每个SELECT都命中索引,如果代码里循环查100次,总耗时照样爆炸——这时候该用Preload或批量IN,而不是再加十个索引。
golang免费学习笔记(深入):立即使用
在学习笔记中,你将探索golang的核心概念和高级技巧!











