小数据量优先用 strings.contains 内存遍历,中等规模用 sqlite fts5 或 postgresql 全文索引,es 仅适用于大规模场景且需注意 v8 api、中文分词、refresh 策略及 body 关闭。

小数据量别碰ES,先用 strings.Contains + 内存遍历
90% 的内部工具、静态博客、配置搜索根本不需要倒排索引或分词器。把文章加载进 []Article,循环里调 strings.Contains 就能跑得飞快。
- 忽略大小写?别反复
strings.ToLower,直接用strings.EqualFold(line, keyword)更准(尤其对非 ASCII 字符) - 要同时搜
Title和Content?拼成一个字符串再查,比两次Contains少一次循环开销:text := a.Title + " " + a.Content - 关键词带空格想“AND”匹配?用
strings.Fields(keyword)拆词,再逐个strings.Contains判断,比正则简单又可控 - 别在循环里做
strings.ToLower(a.Title)—— 如果Title是固定字段,提前转好存进结构体,避免每次搜索重复分配
中等规模(几千条以上)直接上数据库全文索引
SQLite 的 FTS5、PostgreSQL 的 to_tsvector、MySQL 的 MATCH() AGAINST() 都比手写索引靠谱得多,而且不用运维、不额外占内存、查询响应稳定。
- PostgreSQL 中建索引要显式加扩展:
CREATE EXTENSION IF NOT EXISTS zhparser;,否则中文分词会退化成单字切分 - SQLite FTS5 不支持 LIKE,必须用
WHERE content MATCH 'keyword',写错成LIKE会静默返回空结果 - 用 GORM 查询时,
Raw()是绕不过去的——GORM 的Where不理解MATCH语法,硬套会报near "MATCH": syntax error - 别把全文字段和普通字段混在同一个
SELECT *里查;大文本字段(如Content)会拖慢整行传输,只查需要的字段更稳
真要上 Elasticsearch,别用 olivere/elastic,死坑已埋好
olivere/elastic/v7 已归档,不修 bug、不发安全补丁;而官方 go-elasticsearch/v8 强制 TLS、强制认证、API 结构全变,新手照着老教程抄必炸。
- 构造查询不能链式调用:
client.Search().Query().Match(...)在 v8 里不存在,必须用map[string]interface{}组 body,再json.Marshal,漏一个引号或嵌套层级就400 Bad Request - 中文搜不到?大概率是索引用了
ik_max_word,但查询没加"analyzer": "ik_max_word",默认走standard分词器,切出来的词项对不上 -
Refresh别设"true"—— 写完立刻可查听着爽,但高并发下吞吐暴跌,线上应设"wait_for"或干脆不设,靠 ES 默认 1s 刷新间隔 - 记得
defer res.Body.Close(),v8 所有Do(ctx)返回的res都要关 body,漏掉会导致文件描述符泄漏,跑几天就too many open files
文件内容搜索:别读整个文件,用 bufio.Scanner + filepath.WalkDir
搜日志、搜代码、搜配置,核心就三点:不爆内存、不错过权限错误、不被超长行卡死。
-
os.ReadFile是雷区,几十 MB 的日志文件一读就 RSS 翻倍;bufio.Scanner是流式处理,内存恒定 - 遇到
scanner: token too long?不是文件有问题,是默认缓冲区太小,加一句scanner.Buffer(make([]byte, 1 即可 -
filepath.WalkDir比旧版Walk健壮得多,遇到.git或node_modules直接return filepath.SkipDir,别让它往下钻 - 正则匹配优先用
regexp.MustCompile预编译,别在循环里Compile;要忽略大小写,写(?i)error,别用strings.ToLower再匹配
analyzer 参数,结果就是“明明写了却搜不到”。还有就是 Refresh 和 Body.Close() 这种细节,不出问题时看不见,一出就是线上事故。golang免费学习笔记(深入):立即使用
在学习笔记中,你将探索golang的核心概念和高级技巧!











