GORM复合索引使用指南 #
基础语法 #
单字段索引 #
type User struct {
Name string `gorm:"index"`
Email string `gorm:"uniqueIndex"`
}
复合索引 #
type BillRecord struct {
UserName string `gorm:"index:idx_user_time,priority:1"`
PayTime string `gorm:"index:idx_user_time,priority:2"`
Amount string `gorm:"index:idx_user_time,priority:3"`
}
生成的索引:CREATE INDEX idx_user_time ON bill_record(user_name, pay_time, amount)
复合索引核心规则 #
1. 字段顺序很关键 #
选择性原理 #
选择性 = 不同值的数量 / 总记录数
示例 (10万条记录):
UserName: 1000个用户 → 选择性 = 0.01 (高)PayTime: 365个日期 → 选择性 = 0.00365 (中)Status: 3个状态 → 选择性 = 0.00003 (低)
// ✅ 正确:高选择性字段在前
UserName string `gorm:"index:idx_user_time,priority:1"` // 用户ID,高选择性
PayTime string `gorm:"index:idx_user_time,priority:2"` // 时间,中等选择性
Status string `gorm:"index:idx_user_time,priority:3"` // 状态,低选择性
// ❌ 错误:低选择性字段在前
Status string `gorm:"index:idx_bad,priority:1"` // 只有几个值
PayTime string `gorm:"index:idx_bad,priority:2"`
UserName string `gorm:"index:idx_bad,priority:3"`
性能对比分析 #
查询:WHERE user_name = 'alice' AND pay_time = '2024-01-01'
错误索引 (status, pay_time, user_name):
- 扫描status字段 → 找到33,333条记录 (1/3数据)
- 在33,333条中筛选pay_time → 剩余100条
- 在100条中找user_name → 最终1条
正确索引 (user_name, pay_time, status):
- 直接定位user_name → 找到100条记录
- 在100条中筛选pay_time → 最终1条
结果:正确索引比错误索引快 330倍!
核心原则:让数据库先用"筛选能力最强"的字段过滤,后续处理的数据量就很小。
2. 最左匹配原则 #
对于索引 (user_name, pay_time, amount):
-- ✅ 能使用索引
WHERE user_name = 'alice'
WHERE user_name = 'alice' AND pay_time > '2024-01-01'
WHERE user_name = 'alice' AND pay_time > '2024-01-01' AND amount > 100
-- ❌ 无法使用索引
WHERE pay_time > '2024-01-01'
WHERE amount > 100
WHERE pay_time > '2024-01-01' AND amount > 100
复合索引与单字段查询 #
常见误区:有复合索引就不支持单字段查询?
答案:支持,但仅限最左字段!
type BillRecord struct {
UserName string `gorm:"index:idx_user_time,priority:1"`
PayTime string `gorm:"index:idx_user_time,priority:2"`
Amount string `gorm:"index:idx_user_time,priority:3"`
}
// 生成索引:(user_name, pay_time, amount)
-- ✅ 单字段查询:可以使用复合索引
SELECT * FROM bill_record WHERE user_name = 'john';
-- ❌ 单字段查询:无法使用复合索引
SELECT * FROM bill_record WHERE pay_time = '2024-01-01';
SELECT * FROM bill_record WHERE amount = '100';
实用索引策略 #
策略1:复合索引 + 单字段索引组合
type Order struct {
UserID uint `gorm:"index:idx_user_time,priority:1;index:idx_user"`
CreatedAt time.Time `gorm:"index:idx_user_time,priority:2;index:idx_time"`
Status string `gorm:"index:idx_status"`
}
支持查询:
-- 使用复合索引
WHERE user_id = 1 AND created_at > '2024-01-01'
-- 使用单字段索引
WHERE user_id = 1 -- idx_user
WHERE created_at > now() -- idx_time
WHERE status = 'pending' -- idx_status
策略2:根据查询频率设计
type UserLog struct {
// 90%查询都带user_id,放最左边
UserID uint `gorm:"index:idx_user_time_action,priority:1"`
CreatedAt time.Time `gorm:"index:idx_user_time_action,priority:2"`
Action string `gorm:"index:idx_user_time_action,priority:3;index:idx_action"`
}
实战场景 #
场景1:用户+时间范围查询 #
type BillRecord struct {
UserName string `gorm:"index:idx_user_time,priority:1"`
PayTime string `gorm:"index:idx_user_time,priority:2"`
Amount string
}
优化查询:
SELECT * FROM bill_record
WHERE user_name IN ('alice', 'bob')
AND pay_time BETWEEN '2024-01-01' AND '2024-12-31'
场景2:多维度分析索引 #
type BillRecord struct {
UserName string `gorm:"index:idx_user_category,priority:1"`
CategoryName string `gorm:"index:idx_user_category,priority:2"`
PayTime string `gorm:"index:idx_time_category,priority:1"`
CategoryName string `gorm:"index:idx_time_category,priority:2"`
}
支持两种查询模式:
- 用户+分类:
WHERE user_name = ? AND category_name = ? - 时间+分类:
WHERE pay_time BETWEEN ? AND ? AND category_name = ?
场景3:覆盖索引优化 #
type BillRecord struct {
UserName string `gorm:"index:idx_cover,priority:1"`
PayTime string `gorm:"index:idx_cover,priority:2"`
Amount string `gorm:"index:idx_cover,priority:3"` // 包含查询字段
Status string `gorm:"index:idx_cover,priority:4"` // 包含查询字段
}
无需回表查询:
SELECT amount, status FROM bill_record
WHERE user_name = 'alice' AND pay_time > '2024-01-01'
-- 所有字段都在索引中,无需访问数据页
何时使用复合索引 #
✅ 适用场景 #
多字段WHERE条件
WHERE user_id = ? AND status = ? AND created_at > ?范围查询+精确匹配
WHERE category = 'food' AND pay_time BETWEEN ? AND ?GROUP BY + ORDER BY优化
SELECT user_name, COUNT(*) FROM bills GROUP BY user_name ORDER BY user_name频繁的联合查询
WHERE (user_name = ? AND status = ?) OR (user_name = ? AND category = ?)
❌ 避免场景 #
低选择性字段组合
// 性别+状态,区分度太低 Gender string `gorm:"index:idx_bad,priority:1"` // 只有男/女 Status string `gorm:"index:idx_bad,priority:2"` // 只有active/inactive过多字段的复合索引
// 超过4个字段,维护成本高 Field1 string `gorm:"index:idx_too_many,priority:1"` Field2 string `gorm:"index:idx_too_many,priority:2"` Field3 string `gorm:"index:idx_too_many,priority:3"` Field4 string `gorm:"index:idx_too_many,priority:4"` Field5 string `gorm:"index:idx_too_many,priority:5"` // 过多频繁更新的字段
// 更新频繁的字段不适合做索引 UpdateCount int `gorm:"index:idx_bad,priority:1"` // 每次都更新
性能验证 #
查看执行计划 #
-- SQLite
EXPLAIN QUERY PLAN SELECT * FROM bill_record
WHERE user_name = 'alice' AND pay_time > '2024-01-01';
-- MySQL
EXPLAIN SELECT * FROM bill_record
WHERE user_name = 'alice' AND pay_time > '2024-01-01';
预期结果 #
-- ✅ 使用了索引
SEARCH TABLE bill_record USING INDEX idx_user_time (user_name=? AND pay_time>?)
-- ❌ 全表扫描
SCAN TABLE bill_record
实际案例对比 #
优化前后性能差异 #
// 原始查询:10万条数据,耗时2000ms
SELECT * FROM bill_record WHERE user_name IN (?,?,?) AND pay_time BETWEEN ? AND ?
// 添加复合索引后:相同查询,耗时20ms
// 性能提升100倍
最佳实践 #
- 分析查询模式:先看WHERE条件,再设计索引
- 高选择性字段优先:区分度高的字段放前面
- 控制索引数量:单表索引不超过5个
- 定期监控:检查索引使用率,删除无用索引
- 测试验证:用EXPLAIN检查执行计划
复合索引是数据库优化的核心工具,合理使用可以带来10-100倍的性能提升。