ThinkPHP本身不提供开箱即用的中文全文检索能力;真正可用方案是Elasticsearch+IK分词器,须手动完成客户端集成、索引结构定义、数据同步和分词配置四件事。

ThinkPHP 本身不提供开箱即用的中文全文检索能力,LIKE 查询不是全文检索,MySQL FULLTEXT 对中文支持极弱(5.7+ 虽支持 ngram,但效果差、无法自定义词典),真正可用的方案只有外接分词+倒排索引系统——最常见的是 Elasticsearch + IK 分词器。这不是“配个参数就能跑”,而是必须手动完成客户端集成、索引结构定义、数据同步和分词配置四件事。
为什么 MySQL FULLTEXT 在 ThinkPHP 里不适合中文搜索
MySQL 的 FULLTEXT 索引默认按字符切分,中文没有空格分隔,它只能靠 ngram 插件做固定长度切片(如 n=2 就切“中”“文”“文检”“检索”),导致:
• 检索结果大量误匹配(搜“数据库”,可能命中“据库”“数数”)
• 无法识别词语边界,“人工智能”切不成“人工智能”,而可能是“人工”“智能”“能”
• 不支持同义词、停用词、自定义词典
• ThinkPHP 的 where('content', 'like', ...) 或 match against 写法看似简单,实际查不出有效结果
Elasticsearch + IK 分词器必须做的三件事
在 ThinkPHP(无论 5.x 还是 6.x)中接入 ES,光装插件、写几行 Client::search() 是没用的,以下三步缺一不可:
- ES 服务端必须安装对应版本的
elasticsearch-analysis-ik插件,且版本严格匹配(例如 ES 7.10 → 必须用 ik 7.10.2;ES 8.4 → 必须用 ik 8.4.3)。错一个版本,启动失败或分词直接退化为单字 - 创建索引时必须显式指定
analyzer和search_analyzer,例如:{ "settings": { "analysis": { "analyzer": { "ik_max_word": { "type": "custom", "tokenizer": "ik_max_word" }, "ik_smart": { "type": "custom", "tokenizer": "ik_smart" } } } }, "mappings": { "properties": { "title": { "type": "text", "analyzer": "ik_max_word", "search_analyzer": "ik_smart" }, "content": { "type": "text", "analyzer": "ik_max_word" } } } } - ThinkPHP 中写入数据前,不能直接
$es->index(...)原始内容,必须确保字段值已按业务逻辑清洗(如去 HTML 标签、截断超长文本),否则 IK 分词会因格式异常跳过或截断
ThinkPHP 同步数据到 ES 的关键陷阱
很多人在控制器里加一段 Client::index() 就以为同步完成了,结果搜索不到——问题出在时机和一致性上:
立即学习“PHP免费学习笔记(深入)”;
- 不能只在
add()里同步,edit()和delete()同样要触发 ES 的update或delete操作,否则 ES 数据与 MySQL 严重不一致 - 不要在事务提交前调用 ES 写入。ES 是异步响应,网络抖动或超时会导致写入失败,但 PHP 已返回成功。应在 MySQL 事务 commit 后,再执行 ES 操作,并捕获
Exception记录失败 ID,供后台补偿 - ThinkPHP 的模型事件(如
afterWrite)看似方便,但注意:它在事务内执行,且无法感知事务是否最终提交。更稳妥的方式是用队列(如 think-worker)延迟 100ms 后执行 ES 同步
IK 分词器不是“装上就灵”的黑盒——ik_max_word 和 ik_smart 的语义差异、主词典 main.dic 的编码格式(UTF-8 无 BOM)、远程词典热更新路径配置,任意一项出错都会让搜索变成“搜得到但不对”。最常被忽略的,是索引 mapping 定义后无法直接修改 text 字段的 analyzer,改了就得重建索引,而重建期间搜索不可用。



















