本文目录导读:

- 最简单的 SQL LIKE 查询(适合数据量小)
- MySQL 全文索引(支持前缀)
- 使用 Trie 树(前缀树) - 内存方案
- 使用 Redis 有序集合(Sorted Set)
- 混合方案:MySQL + Redis 缓存
- 前端配合(AJAX 实时请求)
- 性能优化建议
在PHP项目中实现基于前缀匹配的搜索建议联想,通常有几种常见方案,下面按实现复杂度从低到高介绍。
最简单的 SQL LIKE 查询(适合数据量小)
// 用户输入前缀
$prefix = trim($_GET['q'] ?? '');
$prefix = mysqli_real_escape_string($conn, $prefix);
$sql = "SELECT DISTINCT keyword FROM suggestions
WHERE keyword LIKE '$prefix%'
ORDER BY LENGTH(keyword), keyword
LIMIT 10";
$result = mysqli_query($conn, $sql);
$suggestions = [];
while ($row = mysqli_fetch_assoc($result)) {
$suggestions[] = $row['keyword'];
}
echo json_encode($suggestions);
缺点:前缀通配符导致无法利用索引(除非有LIKE 'abc%'类型的索引),大数据量时性能差。
MySQL 全文索引(支持前缀)
-- 创建全文索引 ALTER TABLE suggestions ADD FULLTEXT INDEX ft_keyword (keyword);
$prefix = mysqli_real_escape_string($conn, $prefix);
// 使用布尔模式 + * 通配符实现前缀匹配
$sql = "SELECT DISTINCT keyword FROM suggestions
WHERE MATCH(keyword) AGAINST('$prefix*' IN BOOLEAN MODE)
LIMIT 10";
注意:MySQL 全文索引默认最小词长度是4(InnoDB),短词可能不生效。
使用 Trie 树(前缀树) - 内存方案
适合固定词库,启动时加载到内存:
class TrieNode {
public $children = [];
public $isEnd = false;
public $value = null;
}
class Trie {
private $root;
public function __construct() {
$this->root = new TrieNode();
}
public function insert($word) {
$node = $this->root;
for ($i = 0; $i < strlen($word); $i++) {
$char = $word[$i];
if (!isset($node->children[$char])) {
$node->children[$char] = new TrieNode();
}
$node = $node->children[$char];
}
$node->isEnd = true;
$node->value = $word;
}
public function searchPrefix($prefix) {
$node = $this->root;
for ($i = 0; $i < strlen($prefix); $i++) {
$char = $prefix[$i];
if (!isset($node->children[$char])) {
return [];
}
$node = $node->children[$char];
}
// 收集所有以该前缀开头的词
$results = [];
$this->collectWords($node, $results);
return $results;
}
private function collectWords($node, &$results) {
if ($node->isEnd) {
$results[] = $node->value;
}
foreach ($node->children as $char => $child) {
$this->collectWords($child, $results);
}
}
}
// 使用示例
$trie = new Trie();
// 从数据库加载所有关键词
$keywords = ['apple', 'application', 'appetite', 'banana', ...];
foreach ($keywords as $word) {
$trie->insert(strtolower($word));
}
$prefix = strtolower($_GET['q'] ?? '');
$suggestions = $trie->searchPrefix($prefix);
echo json_encode(array_slice($suggestions, 0, 10));
使用 Redis 有序集合(Sorted Set)
利用 Redis 的前缀搜索特性:
$redis = new Redis();
$redis->connect('127.0.0.1', 6379);
// 添加关键词(可以批量)
$keywords = ['apple', 'application', ...];
foreach ($keywords as $word) {
$redis->zAdd('autocomplete', 0, $word);
}
// 前缀搜索 - 使用 ZRANGEBYLEX
$prefix = strtolower($_GET['q'] ?? '');
$min = $prefix;
$max = $prefix . "\xff"; // 最大前缀值
$suggestions = $redis->zRangeByLex('autocomplete', "[$min", "[$max", 0, 10);
echo json_encode($suggestions);
优点:高性能,适合大量关键词。
混合方案:MySQL + Redis 缓存
将常用搜索词缓存到 Redis:
function getSuggestions($prefix) {
$redis = new Redis();
$redis->connect('127.0.0.1', 6379);
$cacheKey = "suggest:{$prefix}";
// 1. 先查 Redis 缓存
if ($redis->exists($cacheKey)) {
return json_decode($redis->get($cacheKey), true);
}
// 2. 缓存未命中,查 MySQL
$prefix = mysqli_real_escape_string($conn, $prefix);
$sql = "SELECT keyword FROM suggestions
WHERE keyword LIKE '$prefix%'
ORDER BY frequency DESC, LENGTH(keyword)
LIMIT 10";
$result = mysqli_query($conn, $sql);
$suggestions = [];
while ($row = mysqli_fetch_assoc($result)) {
$suggestions[] = $row['keyword'];
}
// 3. 写入缓存(有效期5分钟)
if (!empty($suggestions)) {
$redis->setex($cacheKey, 300, json_encode($suggestions));
}
return $suggestions;
}
前端配合(AJAX 实时请求)
// jQuery 示例
let debounceTimer;
$('#search-input').on('input', function() {
clearTimeout(debounceTimer);
const query = $(this).val().trim();
if (query.length < 2) {
$('#suggestions').hide();
return;
}
debounceTimer = setTimeout(() => {
$.ajax({
url: '/api/suggestions.php',
data: { q: query },
dataType: 'json',
success: function(data) {
let html = '';
data.forEach(item => {
// 高亮匹配部分
const highlighted = item.replace(
new RegExp('(' + query + ')', 'gi'),
'<strong>$1</strong>'
);
html += `<li>${highlighted}</li>`;
});
$('#suggestions').html(html).show();
}
});
}, 200); // 200ms 防抖
});
性能优化建议
| 方案 | 数据量 | 响应时间 | 实现复杂度 |
|---|---|---|---|
| SQL LIKE | < 1万 | 50-200ms | 低 |
| 全文索引 | 1-10万 | 10-50ms | 中 |
| Trie树 | 10-100万 | 1-10ms | 中高 |
| Redis Sorted Set | 100万+ | < 1ms | 中 |
| Elasticsearch | 千万级 | < 100ms | 高 |
推荐方案:
- 小型项目:SQL LIKE + 简单缓存
- 中型项目:Redis Sorted Set
- 大型项目:Elasticsearch 或专业的搜索服务
选择方案时要综合考虑数据量、并发量、维护成本和团队技术栈。