如何操作ThinkPHP全文索引及搜索引擎集成?
- 内容介绍
- 文章标签
- 相关推荐
本文共计1251个文字,预计阅读时间需要6分钟。
ThinkPHP 本身不提供开箱即用的全文索引抽象层。所谓全文索引,指的是一种数据库索引,能够对文本内容进行快速搜索。下面是几种常见的全文索引实现方式:
MySQL FULLTEXT 索引必须手动建,TP 模型不感知
ThinkPHP 的 where()、field() 或模型定义完全不会触发或管理 MySQL 的 FULLTEXT 索引。它只生成标准 SQL,是否走全文检索,全看你的 SQL 里有没有 MATCH() AGAINST()。
- 必须在数据库中显式创建索引:
ALTER TABLE `article` ADD FULLTEXT INDEX `ft_title_content` (`title`, `content`);
- 查询时不能用
where('title', 'like', '%关键词%'),得手写原生 SQL 或用whereRaw():$model->whereRaw("MATCH(title, content) AGAINST(? IN NATURAL LANGUAGE MODE)", [$keyword])->select(); - 中文直接查会失败:MySQL 默认最小词长 4,且无中文分词能力;必须提前把标题/内容按词切分后存入单独的索引表(如
search_index),再对那个表建FULLTEXT—— 这才是 ThinkPHP 3.2/5.x 项目里常见的做法 -
AGAINST()支持三种模式:NATURAL LANGUAGE(默认,相关性排序)、BOOLEAN(支持+ - *等语法)、QUERY EXPANSION(少用),别混用
Elasticsearch 需要显式同步字段,mapping 决定能否搜
ThinkPHP 模型字段加个 searchable = true 属性?没这回事。ES 的可搜索性由索引的 mappings 定义决定,和 PHP 代码无关。
- 字段类型必须设为
text(不是keyword)才支持分词搜索;JSON 字段如extra_info默认不解析,得声明为nested或object并指定enabled: true - 数据写入 ES 不能靠模型
save()自动同步,必须在业务逻辑里显式调用客户端插入:$params = [ 'index' => 'article', 'id' => $id, 'body' => [ 'title' => $data['title'], 'content' => $data['content'], 'tags' => $data['tags'] ] ]; $client->index($params);
- ThinkPHP 6 可用
laravel/scout+scout-elasticsearch-driver封装一层,但要注意:-
Scout的toSearchableArray()方法必须重写,控制哪些字段进 ES - 同步时机需手动触发(
$model->searchable())或监听事件,不是实时的 -
config/scout.php中driver设为elastic,否则search()会静默退化为数据库查询
-
中文分词必须前置处理,MySQL 和 ES 都不自带
无论是 FULLTEXT 还是 ES,默认都不理解“人工智能”该拆成“人工”“智能”还是“人工智能”。你得自己做:
立即学习“PHP免费学习笔记(深入)”;
- MySQL 方案:插入前用
jieba-php或Fenci切词,把title转成空格分隔字符串存进search_index.title_words字段,再对这个字段建FULLTEXT - ES 方案:在
mappings里指定中文分词器,比如ik_max_word:"title": { "type": "text", "analyzer": "ik_max_word", "search_analyzer": "ik_smart" }
- 切词逻辑别写在控制器里反复加载词典:封装成 Service,初始化一次词典实例,复用对象;否则每次请求 new \Fenci\Segment() 会吃 CPU
- 注意停用词:ES 的
ik自带停用词表,MySQL 没有,得自己过滤掉“的”“了”“和”这类词,否则影响召回率
复合索引和全文索引别混用,explain 是唯一真相
有人以为给 status 和 title 建了个联合索引,再 MATCH(title) 就能加速——不可能。FULLTEXT 索引和 B+ 树索引是两套机制,互不兼容。
- 一个字段上不能同时存在普通索引和 FULLTEXT 索引(MySQL 允许,但无意义)
-
EXPLAIN SELECT ... MATCH() AGAINST()显示的type是fulltext,不是ref或range;如果看到ALL,说明根本没走全文索引,可能是字段没索引、模式写错、或用了LIKE - TP 的
useIndex()对MATCH()无效,它只影响普通 WHERE 条件;全文检索的索引选择由 MySQL 内部决定,无法提示
真正容易被忽略的点:全文检索不是“加个索引就变快”,而是“换了一套数据组织方式”。你得接受它需要额外存储、同步延迟、分词不准、调试靠 explain 和 _analyze API——这些没法藏在模型方法后面。
本文共计1251个文字,预计阅读时间需要6分钟。
ThinkPHP 本身不提供开箱即用的全文索引抽象层。所谓全文索引,指的是一种数据库索引,能够对文本内容进行快速搜索。下面是几种常见的全文索引实现方式:
MySQL FULLTEXT 索引必须手动建,TP 模型不感知
ThinkPHP 的 where()、field() 或模型定义完全不会触发或管理 MySQL 的 FULLTEXT 索引。它只生成标准 SQL,是否走全文检索,全看你的 SQL 里有没有 MATCH() AGAINST()。
- 必须在数据库中显式创建索引:
ALTER TABLE `article` ADD FULLTEXT INDEX `ft_title_content` (`title`, `content`);
- 查询时不能用
where('title', 'like', '%关键词%'),得手写原生 SQL 或用whereRaw():$model->whereRaw("MATCH(title, content) AGAINST(? IN NATURAL LANGUAGE MODE)", [$keyword])->select(); - 中文直接查会失败:MySQL 默认最小词长 4,且无中文分词能力;必须提前把标题/内容按词切分后存入单独的索引表(如
search_index),再对那个表建FULLTEXT—— 这才是 ThinkPHP 3.2/5.x 项目里常见的做法 -
AGAINST()支持三种模式:NATURAL LANGUAGE(默认,相关性排序)、BOOLEAN(支持+ - *等语法)、QUERY EXPANSION(少用),别混用
Elasticsearch 需要显式同步字段,mapping 决定能否搜
ThinkPHP 模型字段加个 searchable = true 属性?没这回事。ES 的可搜索性由索引的 mappings 定义决定,和 PHP 代码无关。
- 字段类型必须设为
text(不是keyword)才支持分词搜索;JSON 字段如extra_info默认不解析,得声明为nested或object并指定enabled: true - 数据写入 ES 不能靠模型
save()自动同步,必须在业务逻辑里显式调用客户端插入:$params = [ 'index' => 'article', 'id' => $id, 'body' => [ 'title' => $data['title'], 'content' => $data['content'], 'tags' => $data['tags'] ] ]; $client->index($params);
- ThinkPHP 6 可用
laravel/scout+scout-elasticsearch-driver封装一层,但要注意:-
Scout的toSearchableArray()方法必须重写,控制哪些字段进 ES - 同步时机需手动触发(
$model->searchable())或监听事件,不是实时的 -
config/scout.php中driver设为elastic,否则search()会静默退化为数据库查询
-
中文分词必须前置处理,MySQL 和 ES 都不自带
无论是 FULLTEXT 还是 ES,默认都不理解“人工智能”该拆成“人工”“智能”还是“人工智能”。你得自己做:
立即学习“PHP免费学习笔记(深入)”;
- MySQL 方案:插入前用
jieba-php或Fenci切词,把title转成空格分隔字符串存进search_index.title_words字段,再对这个字段建FULLTEXT - ES 方案:在
mappings里指定中文分词器,比如ik_max_word:"title": { "type": "text", "analyzer": "ik_max_word", "search_analyzer": "ik_smart" }
- 切词逻辑别写在控制器里反复加载词典:封装成 Service,初始化一次词典实例,复用对象;否则每次请求 new \Fenci\Segment() 会吃 CPU
- 注意停用词:ES 的
ik自带停用词表,MySQL 没有,得自己过滤掉“的”“了”“和”这类词,否则影响召回率
复合索引和全文索引别混用,explain 是唯一真相
有人以为给 status 和 title 建了个联合索引,再 MATCH(title) 就能加速——不可能。FULLTEXT 索引和 B+ 树索引是两套机制,互不兼容。
- 一个字段上不能同时存在普通索引和 FULLTEXT 索引(MySQL 允许,但无意义)
-
EXPLAIN SELECT ... MATCH() AGAINST()显示的type是fulltext,不是ref或range;如果看到ALL,说明根本没走全文索引,可能是字段没索引、模式写错、或用了LIKE - TP 的
useIndex()对MATCH()无效,它只影响普通 WHERE 条件;全文检索的索引选择由 MySQL 内部决定,无法提示
真正容易被忽略的点:全文检索不是“加个索引就变快”,而是“换了一套数据组织方式”。你得接受它需要额外存储、同步延迟、分词不准、调试靠 explain 和 _analyze API——这些没法藏在模型方法后面。

