PHP项目搜索建议如何基于前缀匹配实现联想

wen PHP项目 30

本文目录导读:

PHP项目搜索建议如何基于前缀匹配实现联想

  1. 最简单的 SQL LIKE 查询(适合数据量小)
  2. MySQL 全文索引(支持前缀)
  3. 使用 Trie 树(前缀树) - 内存方案
  4. 使用 Redis 有序集合(Sorted Set)
  5. 混合方案:MySQL + Redis 缓存
  6. 前端配合(AJAX 实时请求)
  7. 性能优化建议

在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 或专业的搜索服务

选择方案时要综合考虑数据量、并发量、维护成本和团队技术栈。

抱歉,评论功能暂时关闭!