必须用Elasticsearch替代MySQL原生LIKE查询以解决万级数据卡顿、分词不准等问题;需安装7.17.20版本,配置PHP客户端、创建ik分词索引、同步商品数据并实现带高亮的multi_match搜索。

在 ThinkPHP 5.1 项目中,当 MySQL 的 LIKE 查询面对万级数据开始卡顿、分词不准、无法支持同义词或拼音搜索时,必须用 Elasticsearch 替代原生模糊查询实现真正可用的搜索功能。
安装并启动 Elasticsearch 服务
下载 Elasticsearch 7.17.20(兼容 PHP 7.2+ 且与 tp5.1 配合稳定),解压后进入 bin 目录 → 双击 elasticsearch.bat(Windows)或执行 ./elasticsearch(Linux/macOS)。
访问 http://127.0.0.1:9200 确认返回 JSON 响应,status 字段值为 200 且 name 字段非空,说明服务已就绪。
注意:Elasticsearch 默认绑定本地 127.0.0.1,如需远程访问,必须修改 config/elasticsearch.yml 中 network.host: 127.0.0.1 为 network.host: 0.0.0.0,并重启服务——但【生产环境严禁开放 0.0.0.0】,仅开发调试阶段临时启用。
立即学习“PHP免费学习笔记(深入)”;
安装官方 PHP 客户端并配置连接
执行 composer require elasticsearch/elasticsearch:7.17.20,确保版本与服务端一致,否则会触发 NoNodeAvailableException 异常。
在 thinkphp/application/common.php 或单独新建 app/library/ElasticSearch.php 中定义连接实例:
use Elasticsearch\ClientBuilder;
return ClientBuilder::create()->setHosts(['http://127.0.0.1:9200'])->build();
这一步操作起来很简单,直接把文件拖进去就行。但【务必关闭 Elasticsearch 的 xpack.security.enabled = true,否则客户端默认无认证会报 401】——若已启用安全模块,需调用 setBasicAuthentication('user', 'pass')。
创建索引并映射商品文档结构
第一步:准备 mapping 数组,指定 title 字段为 ik_max_word 分词器,price 设为 keyword 类型避免被拆分:
第二步:调用 client->indices()->create() 方法传入 index 名称和 body 参数,body 包含 settings 和 mappings;其中 analysis → analyzer → ik → type 必须设为 "custom",否则 ik 分词器加载失败。
第三步:执行后检查响应中的 acknowledged 字段是否为 true,若为 false,大概率是索引名已存在且类型冲突——此时需先 client->indices()->delete(['index' => 'goods']) 再重试。
将 MySQL 商品数据批量导入 ES
方法一:使用 tp5.1 的 Db::name('goods')->select() 拉取全量数据 → foreach 循环构造 document 数组 → 调用 client->bulk() 提交,每 500 条一组提交,避免超时。
方法二:直接导出 SQL 为 CSV,用 logstash 插件同步,适合百万级以上数据,但需额外部署 logstash 服务并编写 conf 文件。
注意:导入前必须确保文档 _id 与 MySQL 主键一致,否则后续更新会重复写入——设置 $params['body'][] = ['index' => ['_id' => $row['id']]]。
实现带高亮的关键词搜索
构造 search 数组,query 下用 multi_match 覆盖 title、subtitle、tags 字段,operator 设为 'and' 避免垃圾结果;highlight 段声明 pre_tags 和 post_tags 为 标签,fields 列出要高亮的字段名。
执行 client->search() 后,遍历 hits → hits 数组,从 source 提取原始数据,从 highlight 提取带 em 标签的标题片段,直接 assign 给模板变量即可渲染。
这一步不需要改前端代码,只需把原来 $list = Db::name('goods')->where('title', 'like', "%{$kw}%")->select() 替换为 ES 返回结果,保持控制器返回格式一致。



















