本文目录导读:

- 数据量与场景分级处理
- MySQL LIKE 查询优化
- MySQL全文索引(Full-Text Search)
- 使用搜索引擎(Elasticsearch)
- 中文分词优化(针对中文项目)
- 缓存策略优化
- 查询限流与熔断
- 混合查询策略
- 性能监控与调优
- 综合推荐方案
在PHP项目中平衡模糊搜索的精度与查询速度,通常需要根据数据量、业务场景和可用资源进行综合设计,以下是一些实用的策略和技术组合:
数据量与场景分级处理
// 根据数据量选择不同方案
class FuzzySearchStrategy {
public function search($keyword, $dataSize) {
if ($dataSize < 1000) {
return $this->likeSearch($keyword); // 小数据量用LIKE
} elseif ($dataSize < 100000) {
return $this->fulltextSearch($keyword); // 中等数据量用全文索引
} else {
return $this->elasticsearchSearch($keyword); // 大数据量用搜索引擎
}
}
}
MySQL LIKE 查询优化
对于小型项目或临时需求,优化LIKE查询:
// 1. 使用前缀匹配(可以利用索引)
$keyword = 'search';
$sql = "SELECT * FROM table WHERE column LIKE '{$keyword}%'"; // 使用索引
// 2. 限制返回数量和范围
$sql = "SELECT * FROM table WHERE column LIKE '%{$keyword}%' LIMIT 20";
// 3. 使用覆盖索引
$sql = "SELECT id, title FROM table WHERE title LIKE '%{$keyword}%'";
MySQL全文索引(Full-Text Search)
-- 创建全文索引
ALTER TABLE articles ADD FULLTEXT INDEX ft_title_content (title, content);
-- 使用MATCH AGAINST(自然语言模式)
SELECT * FROM articles
WHERE MATCH(title, content) AGAINST('database optimization' IN NATURAL LANGUAGE MODE);
-- 布尔模式(支持精确匹配)
SELECT * FROM articles
WHERE MATCH(title, content) AGAINST('+database -mysql' IN BOOLEAN MODE);
PHP实现:
function fulltextSearch($keyword, $table, $columns) {
$keyword = $this->sanitize($keyword);
$columnsStr = implode(',', $columns);
// 自然语言模式(默认)
$sql = "SELECT *, MATCH($columnsStr) AGAINST(?) AS relevance
FROM $table
WHERE MATCH($columnsStr) AGAINST(?)
ORDER BY relevance DESC
LIMIT 20";
// 或使用布尔模式获得更高精度
$sql = "SELECT * FROM $table
WHERE MATCH($columnsStr) AGAINST(? IN BOOLEAN MODE)
LIMIT 20";
return $this->db->query($sql, [$keyword, $keyword]);
}
使用搜索引擎(Elasticsearch)
适用于大数据量和高精度需求:
<?php
require 'vendor/autoload.php';
use Elasticsearch\ClientBuilder;
class ElasticFuzzySearch {
private $client;
public function __construct() {
$this->client = ClientBuilder::create()
->setHosts(['localhost:9200'])
->build();
}
public function search($query, $index = 'products') {
$params = [
'index' => $index,
'body' => [
'size' => 20,
'query' => [
'bool' => [
'must' => [
'multi_match' => [
'query' => $query,
'fields' => ['title^3', 'description', 'tags'],
'type' => 'best_fields',
'fuzziness' => 'AUTO', // 自动模糊度
'prefix_length' => 2, // 前缀长度,提高性能
'max_expansions' => 50 // 最大扩展数
]
],
'filter' => [
'term' => ['status' => 'active'] // 过滤条件
]
]
],
'sort' => [
'_score' => ['order' => 'desc']
]
]
];
return $this->client->search($params);
}
}
Elasticsearch配置优化示例:
{
"settings": {
"analysis": {
"analyzer": {
"custom_analyzer": {
"tokenizer": "standard",
"filter": ["lowercase", "asciifolding", "edge_ngram"]
},
"search_analyzer": {
"tokenizer": "standard",
"filter": ["lowercase", "asciifolding"]
}
}
}
},
"mappings": {
"properties": {
"title": {
"type": "text",
"analyzer": "custom_analyzer",
"search_analyzer": "search_analyzer",
"fields": {
"keyword": {"type": "keyword"}
}
}
}
}
}
中文分词优化(针对中文项目)
使用MySQL+分词插件
-- 安装中文分词插件(如:mysqlcft)
ALTER TABLE articles ADD FULLTEXT INDEX ft_content(content) WITH PARSER ngram;
-- 使用ngram分词
SELECT * FROM articles
WHERE MATCH(content) AGAINST('数据库优化' IN NATURAL LANGUAGE MODE);
使用PHP分词库
// 使用scws分词(开源中文分词)
$so = scws_new();
$so->set_charset('utf8');
$so->send_text('数据库优化技巧');
$words = [];
while ($tmp = $so->get_result()) {
foreach ($tmp as $word) {
$words[] = $word['word'];
}
}
$searchQuery = implode(' ', $words);
缓存策略优化
class CachedFuzzySearch {
private $cache;
public function search($keyword, $isNew = false) {
$cacheKey = 'search_' . md5($keyword);
// 热点关键词缓存时间长
if ($this->isHotKeyword($keyword)) {
$ttl = 3600; // 1小时
} else {
$ttl = 600; // 10分钟
}
if (!$isNew && $cached = $this->cache->get($cacheKey)) {
return $cached;
}
$result = $this->performSearch($keyword);
$this->cache->set($cacheKey, $result, $ttl);
// 异步预热相关关键词缓存
$this->warmRelatedCache($keyword);
return $result;
}
private function warmRelatedCache($keyword) {
// 计算相关关键词并打散缓存
$related = [
$keyword . 's',
substr($keyword, 0, -1),
// 其他变体
];
foreach ($related as $relKeyword) {
$this->cache->add(
'search_' . md5($relKeyword),
$this->performSearch($relKeyword),
300 // 5分钟
);
}
}
}
查询限流与熔断
class RateLimiter {
private $redis;
public function check($userId) {
$key = "search_limit:{$userId}";
$current = $this->redis->incr($key);
if ($current === 1) {
$this->redis->expire($key, 60);
}
if ($current > 60) { // 每分钟最多60次
throw new \Exception('搜索频率超限,请稍后再试');
}
}
}
混合查询策略
function hybridSearch($keyword) {
$results = [];
// 1. 精确匹配优先(最高精度)
$exact = $this->exactSearch($keyword);
if (!empty($exact)) {
$results = array_merge($results, $exact);
}
// 2. 前缀匹配(较高精度/速度)
$prefix = $this->prefixSearch($keyword);
$results = array_merge($results, $prefix);
// 3. 模糊匹配(精度较低,但覆盖更广)
if (count($results) < 10) {
$fuzzy = $this->fuzzySearch($keyword);
$results = array_merge($results, $fuzzy);
}
// 4. 去重并限制结果数
$results = array_unique($results, SORT_REGULAR);
return array_slice($results, 0, 20);
}
性能监控与调优
class SearchProfiler {
public function search($keyword, $strategy) {
$start = microtime(true);
try {
$result = $this->executeStrategy($keyword, $strategy);
$duration = (microtime(true) - $start) * 1000; // 毫秒
$this->logQuery($keyword, $strategy, $duration, count($result));
// 动态调整策略
if ($duration > 200) {
$this->downgradeStrategy($strategy);
}
return $result;
} catch (\Exception $e) {
// 降级到基础搜索
return $this->fallbackSearch($keyword);
}
}
private function logQuery($keyword, $strategy, $duration, $count) {
// 记录到监控系统
$this->monitor->record([
'keyword' => $keyword,
'strategy' => $strategy,
'duration' => $duration,
'count' => $count,
'timestamp' => time()
]);
}
}
综合推荐方案
针对不同规模项目的推荐组合:
| 数据量 | 方案 | 精度 | 速度 |
|---|---|---|---|
| <10万 | MySQL LIKE + 缓存 | 中 | 高 |
| 10万-100万 | MySQL全文索引 + 分词 | 高 | 中高 |
| 100万-1000万 | Elasticsearch + 缓存 | 高 | 高 |
| >1000万 | Elasticsearch集群 + CDN | 极高 | 极高 |
最后建议:
- 不要过度优化:先使用简单方案,根据实际负载再升级
- 用户行为分析:记录热词和搜索模式,针对性优化
- 异步处理:搜索结果缓存预热使用队列异步处理
- AB测试:不同策略对用户实际体验的影响需要验证
选择方案时,建议从最简方案开始,根据监控数据和用户反馈逐步优化,对于大多数PHP项目,MySQL全文索引加上合理的缓存策略已经能覆盖95%的需求。