本文目录导读:

- MySQL LIKE 模糊搜索(小型项目)
- MySQL FULLTEXT 全文索引(中等规模)
- Elasticsearch 专业搜索引擎(大型项目)
- Sphinx 搜索引擎(中等规模高性能)
- 中文分词处理
- 性能优化建议
- 完整前端搜索交互
- 选择推荐
在PHP项目中实现搜索功能,根据项目规模和需求的不同,有多种实现方案,以下从简单到复杂,介绍几种主流方法。
MySQL LIKE 模糊搜索(小型项目)
最基础的方式,适合数据量小(万级以内)的场景。
// 搜索逻辑
function searchUsers($keyword) {
$pdo = new PDO('mysql:host=localhost;dbname=test', 'root', '');
$keyword = '%' . $keyword . '%';
// 使用预处理语句防止SQL注入
$stmt = $pdo->prepare("SELECT * FROM users
WHERE name LIKE :keyword
OR email LIKE :keyword
ORDER BY id DESC
LIMIT 20");
$stmt->execute([':keyword' => $keyword]);
return $stmt->fetchAll(PDO::FETCH_ASSOC);
}
// 调用示例
$results = searchUsers($_GET['q'] ?? '');
优点:实现简单,无需额外服务
缺点:性能差(全表扫描),不支持中文分词,不支持模糊排序
MySQL FULLTEXT 全文索引(中等规模)
适合10万级数据量,需要精确匹配关键字的场景。
-- 先创建全文索引 ALTER TABLE articles ADD FULLTEXT INDEX idx_content (title, content);
function searchArticles($keyword) {
$pdo = new PDO('mysql:host=localhost;dbname=test;charset=utf8mb4', 'root', '');
// BOOLEAN MODE 支持 +(必须包含) -(排除) *(通配符)
$sql = "SELECT *, MATCH(title, content) AGAINST(:keyword IN BOOLEAN MODE) AS relevance
FROM articles
WHERE MATCH(title, content) AGAINST(:keyword IN BOOLEAN MODE)
ORDER BY relevance DESC
LIMIT 20";
$stmt = $pdo->prepare($sql);
$stmt->execute([':keyword' => $keyword . '*']); // 通配符允许部分匹配
return $stmt->fetchAll(PDO::FETCH_ASSOC);
}
注意:
- 需要 MySQL 5.6+ 支持中文全文索引(ngram)
- 创建表时指定:
ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 - 配置最小词长:
ft_min_word_len=1(中文)
Elasticsearch 专业搜索引擎(大型项目)
适合百万级以上数据,需要复杂搜索、聚合分析、实时搜索的场景。
环境准备
# 安装 Elasticsearch(需要Java环境) wget https://artifacts.elastic.co/downloads/elasticsearch/elasticsearch-8.11.0-linux-x86_64.tar.gz tar -xzf elasticsearch-8.11.0-linux-x86_64.tar.gz cd elasticsearch-8.11.0/bin ./elasticsearch # 安装 PHP Elasticsearch 客户端 composer require elasticsearch/elasticsearch
使用示例
<?php
require_once __DIR__ . '/vendor/autoload.php';
use Elasticsearch\ClientBuilder;
class SearchService {
private $client;
public function __construct() {
$this->client = ClientBuilder::create()
->setHosts(['http://localhost:9200'])
->build();
}
// 索引文档(将数据存入ES)
public function indexArticle($id, $title, $content) {
$params = [
'index' => 'articles',
'id' => $id,
'body' => [
'title' => $title,
'content' => $content,
'created_at' => date('Y-m-d H:i:s')
]
];
return $this->client->index($params);
}
// 搜索文档
public function search($keyword, $page = 1, $size = 20) {
$params = [
'index' => 'articles',
'body' => [
'query' => [
'bool' => [
'should' => [
['match' => ['title' => ['query' => $keyword, 'boost' => 3]]],
['match' => ['content' => $keyword]]
]
]
],
'highlight' => [
'fields' => [
'title' => ['pre_tags' => ['<em>'], 'post_tags' => ['</em>']],
'content' => ['fragment_size' => 100, 'number_of_fragments' => 3]
]
],
'from' => ($page - 1) * $size,
'size' => $size,
'sort' => ['_score' => 'desc']
]
];
$response = $this->client->search($params);
// 格式化结果
$results = [];
foreach ($response['hits']['hits'] as $hit) {
$source = $hit['_source'];
$highlight = $hit['highlight'] ?? [];
$results[] = [
'id' => $hit['_id'],
'title' => $highlight['title'][0] ?? $source['title'],
'content' => implode('...', $highlight['content'] ?? [$source['content']]),
'score' => $hit['_score']
];
}
return [
'total' => $response['hits']['total']['value'],
'results' => $results,
'page' => $page,
'size' => $size
];
}
}
// 使用示例
$searchService = new SearchService();
// 索引数据
$searchService->indexArticle(1, 'PHP搜索功能实现', '本文介绍如何在PHP中实现高效搜索...');
// 搜索
$result = $searchService->search('PHP搜索');
echo "共找到 {$result['total']} 条结果:";
foreach ($result['results'] as $item) {
echo "标题:{$item['title']}\n";
echo "{$item['content']}\n\n";
}
Sphinx 搜索引擎(中等规模高性能)
适合需要高性能全文搜索但不想引入ES复杂性的场景。
// 安装 Sphinx PHP 扩展
// pecl install sphinx
$sphinx = new SphinxClient();
$sphinx->setServer('localhost', 9312);
$sphinx->setMatchMode(SPH_MATCH_EXTENDED2);
$sphinx->setLimits(0, 20);
$sphinx->setSortMode(SPH_SORT_RELEVANCE);
// 设置筛选条件
$sphinx->setFilter('category_id', [1, 2, 3]);
$sphinx->setFilterRange('price', 100, 500);
// 执行搜索
$result = $sphinx->query('PHP搜索', 'articles');
if ($result && $result['total'] > 0) {
// 从 MySQL 获取完整记录
$ids = array_keys($result['matches']);
$stmt = $pdo->prepare("SELECT * FROM articles WHERE id IN (" . implode(',', $ids) . ")");
$stmt->execute();
$articles = $stmt->fetchAll();
}
中文分词处理
对于中文搜索,分词至关重要:
使用 SCWS(简易中文分词系统)
// 安装 scws 扩展
// pecl install scws
$sh = scws_open();
scws_set_charset($sh, 'utf8');
scws_set_dict($sh, '/usr/local/scws/etc/dict.utf8.xdb');
scws_set_rule($sh, '/usr/local/scws/etc/rules.utf8.ini');
$text = '这是一个中文搜索测试';
scws_send_text($sh, $text);
$words = [];
while ($tmp = scws_get_result($sh)) {
foreach ($tmp as $word) {
$words[] = $word['word'];
}
}
scws_close($sh);
print_r($words);
// 输出: ['这是', '一个', '中文', '搜索', '测试']
集成到搜索中
function tokenizeSearch($keyword) {
$tokens = tokenize($keyword); // 自定义分词函数
// 构建布尔查询
$conditions = [];
$params = [];
foreach ($tokens as $i => $token) {
$conditions[] = "title LIKE :search{$i} OR content LIKE :search{$i}";
$params[":search{$i}"] = "%{$token}%";
}
$where = implode(' AND ', $conditions); // AND表示必须所有词都出现
$sql = "SELECT * FROM articles WHERE {$where} ORDER BY relevance DESC LIMIT 20";
$stmt = $pdo->prepare($sql);
$stmt->execute($params);
return $stmt->fetchAll();
}
性能优化建议
缓存策略
// 1. 查询结果缓存(Redis/Memcached)
$cacheKey = 'search:' . md5($keyword . $page);
$cached = $redis->get($cacheKey);
if ($cached) {
return json_decode($cached, true);
}
$results = performSearch($keyword);
$redis->setex($cacheKey, 300, json_encode($results)); // 缓存5分钟
// 2. 热门搜索缓存
$redis->zIncrBy('hot_queries', 1, $keyword);
// 3. 搜索建议缓存
$suggestions = $redis->get('suggestions');
if (!$suggestions) {
$suggestions = buildAutocompleteIndex();
$redis->setex('suggestions', 3600, $suggestions);
}
数据库优化
-- 1. 添加复合索引 ALTER TABLE articles ADD INDEX idx_search (title, created_at); -- 2. 使用覆盖索引 SELECT id, LEFT(title, 100) as title_fragment FROM articles WHERE title LIKE '%keyword%'; -- 3. 分表策略(按时间) CREATE TABLE articles_2024_01 ...; CREATE TABLE articles_2024_02 ...;
完整前端搜索交互
<!-- HTML 搜索框 -->
<div class="search-box">
<input type="text" id="searchInput" placeholder="搜索..." autocomplete="off">
<div id="searchResults" class="dropdown-menu"></div>
</div>
<script>
// 防抖搜索
const debounce = (fn, delay = 300) => {
let timer;
return (...args) => {
clearTimeout(timer);
timer = setTimeout(() => fn(...args), delay);
};
};
document.getElementById('searchInput').addEventListener('input', debounce(async (e) => {
const query = e.target.value.trim();
if (query.length < 2) {
document.getElementById('searchResults').innerHTML = '';
return;
}
try {
const response = await fetch(`/api/search?q=${encodeURIComponent(query)}`);
const data = await response.json();
renderResults(data);
} catch (error) {
console.error('搜索失败:', error);
}
}));
function renderResults(results) {
const container = document.getElementById('searchResults');
if (results.length === 0) {
container.innerHTML = '<div class="no-results">未找到相关结果</div>';
return;
}
container.innerHTML = results.map(item => `
<a href="/article/${item.id}" class="search-result-item">
<div class="result-title">${item.highlightTitle || item.title}</div>
<div class="result-snippet">${item.snippet}</div>
</a>
`).join('');
}
</script>
选择推荐
| 方案 | 适合场景 | 数据量 | 实现难度 |
|---|---|---|---|
| LIKE | 简单CMS、后台管理 | <1万 | |
| FULLTEXT | 博客、论坛 | <10万 | |
| Sphinx | 电商、垂直搜索 | <100万 | |
| Elasticsearch | 综合搜索、日志分析 | >100万 |
建议:
- 小项目从 MySQL FULLTEXT 起步
- 成长型项目直接上 Elasticsearch(后续不需要迁移)
- 始终做好 SQL 注入防护和 XSS 过滤