PHP中的Full-Text Search:对比MySQL内置、Elasticsearch与Algolia的集成方案

PHP中的Full-Text Search:对比MySQL内置、Elasticsearch与Algolia的集成方案

各位朋友,大家好!今天我们来聊聊PHP应用中实现全文搜索的几种主要方案,并深入对比它们的优缺点和适用场景。全文搜索在现代Web应用中至关重要,它能帮助用户快速找到所需信息,提升用户体验。我们将重点讨论MySQL内置的全文索引、Elasticsearch和Algolia这三种方案,并结合代码示例,希望能帮助大家在实际项目中选择最合适的解决方案。

一、全文搜索的基本概念

在深入讨论具体方案之前,我们先简单回顾一下全文搜索的核心概念。与传统的基于LIKE语句的模糊匹配不同,全文搜索会预先对文本进行分词(Tokenization)和索引(Indexing),以便快速检索包含特定关键词的文档。

  • 分词(Tokenization): 将文本分割成独立的词语(Tokens)。分词的质量直接影响搜索的准确性。不同的语言和应用场景需要不同的分词策略。

  • 索引(Indexing): 将分词后的词语与文档建立关联,形成倒排索引(Inverted Index)。倒排索引是一种数据结构,它以词语为索引,指向包含该词语的文档列表。

  • 停用词(Stop Words): 一些常见的、意义不大的词语(如“的”、“是”、“在”)会被排除在索引之外,以减少索引大小和提高搜索效率。

  • 词干提取(Stemming): 将词语还原为词干(例如,将“running”还原为“run”),以提高搜索的覆盖面。

  • 相关度排序(Relevance Ranking): 根据文档与搜索关键词的相关程度对搜索结果进行排序。

二、MySQL内置全文搜索

MySQL从5.6版本开始支持InnoDB存储引擎的全文索引。虽然功能相对简单,但在某些场景下,MySQL内置的全文搜索已经足够满足需求。

1. 创建全文索引

首先,我们需要在需要进行全文搜索的列上创建全文索引。例如,我们有一个名为articles的表,包含idtitlecontent三个字段,我们希望对titlecontent字段进行全文搜索:

CREATE TABLE articles (
    id INT AUTO_INCREMENT PRIMARY KEY,
    title VARCHAR(255) NOT NULL,
    content TEXT NOT NULL,
    FULLTEXT(title, content)  -- 创建全文索引
);

2. 使用MATCH...AGAINST进行搜索

创建索引后,我们可以使用MATCH...AGAINST语句进行全文搜索。

SELECT id, title, content, MATCH(title, content) AGAINST('搜索关键词' IN NATURAL LANGUAGE MODE) AS relevance
FROM articles
WHERE MATCH(title, content) AGAINST('搜索关键词' IN NATURAL LANGUAGE MODE)
ORDER BY relevance DESC;
  • MATCH(title, content)指定要搜索的列。
  • AGAINST('搜索关键词' IN NATURAL LANGUAGE MODE)指定搜索关键词和搜索模式。IN NATURAL LANGUAGE MODE是最常用的模式,它会根据MySQL的内置分词器进行分词。
  • relevance是相关度评分,可以用来对搜索结果进行排序。

3. 其他搜索模式

除了IN NATURAL LANGUAGE MODE,MySQL还支持其他两种搜索模式:

  • IN BOOLEAN MODE:允许使用布尔运算符(如+-*)来精确控制搜索行为。例如:AGAINST('+关键词1 -关键词2' IN BOOLEAN MODE)表示搜索包含“关键词1”但不包含“关键词2”的文档。
  • WITH QUERY EXPANSION:MySQL会先进行一次自然语言搜索,然后根据搜索结果自动扩展搜索关键词,以提高搜索的覆盖面。

4. PHP集成示例

下面是一个简单的PHP集成示例:

<?php

$host = 'localhost';
$username = 'your_username';
$password = 'your_password';
$database = 'your_database';

$conn = new mysqli($host, $username, $password, $database);

if ($conn->connect_error) {
    die("Connection failed: " . $conn->connect_error);
}

$searchTerm = $_GET['q'] ?? ''; // 获取搜索关键词

$sql = "SELECT id, title, content, MATCH(title, content) AGAINST(? IN NATURAL LANGUAGE MODE) AS relevance
        FROM articles
        WHERE MATCH(title, content) AGAINST(? IN NATURAL LANGUAGE MODE)
        ORDER BY relevance DESC";

$stmt = $conn->prepare($sql);
$stmt->bind_param("ss", $searchTerm, $searchTerm);
$stmt->execute();
$result = $stmt->get_result();

if ($result->num_rows > 0) {
    while($row = $result->fetch_assoc()) {
        echo "ID: " . $row["id"]. " - Title: " . $row["title"]. " - Relevance: " . $row["relevance"]. "<br>";
    }
} else {
    echo "No results found.";
}

$stmt->close();
$conn->close();

?>

5. 优点与缺点

特性 优点 缺点
集成复杂度 低,直接使用MySQL内置功能,无需额外安装和配置 分词器和搜索算法相对简单,无法满足复杂的搜索需求
性能 对于小数据量和简单搜索,性能尚可 对于大数据量和复杂搜索,性能较差
灵活性 有限,只能使用MySQL提供的搜索模式和分词器 无法自定义分词器和搜索算法,无法进行拼写纠错、同义词扩展等高级功能
维护成本 低,与MySQL数据库一起维护 当搜索需求超出MySQL内置功能范围时,维护成本较高
适用场景 数据量较小,搜索需求简单,对性能要求不高的应用。例如,简单的博客或论坛。 数据量较大,搜索需求复杂,对性能要求高的应用。例如,电商网站、新闻门户网站。

三、Elasticsearch集成

Elasticsearch是一个基于Lucene的分布式搜索和分析引擎,具有强大的全文搜索能力、可扩展性和灵活性。

1. 安装和配置Elasticsearch

首先,需要安装和配置Elasticsearch。可以从Elasticsearch官网下载安装包,并根据官方文档进行配置。 配置包括设置集群名称、节点名称、网络端口等。

2. 安装Elasticsearch PHP客户端

使用Composer安装Elasticsearch PHP客户端:

composer require elasticsearch/elasticsearch

3. 创建索引和映射

在Elasticsearch中,数据存储在索引(Index)中,每个索引可以包含多个文档(Document)。在创建索引之前,需要定义映射(Mapping),指定每个字段的数据类型和索引方式。

<?php

require 'vendor/autoload.php';

use ElasticsearchClientBuilder;

$client = ClientBuilder::create()->setHosts(['localhost:9200'])->build();

$indexParams = [
    'index' => 'articles',
    'body' => [
        'mappings' => [
            'properties' => [
                'title' => [
                    'type' => 'text',
                    'analyzer' => 'ik_max_word', // 使用IK分词器
                    'search_analyzer' => 'ik_smart'
                ],
                'content' => [
                    'type' => 'text',
                    'analyzer' => 'ik_max_word', // 使用IK分词器
                    'search_analyzer' => 'ik_smart'
                ]
            ]
        ]
    ]
];

//检查索引是否存在,如果存在先删除再创建
if ($client->indices()->exists(['index' => 'articles'])) {
   $deleteParams = ['index' => 'articles'];
   $client->indices()->delete($deleteParams);
}

$response = $client->indices()->create($indexParams);

print_r($response);

?>
  • index指定索引名称。
  • mappings定义字段映射。
  • type指定字段的数据类型(例如,text表示文本类型)。
  • analyzer指定索引时使用的分词器。这里使用了ik_max_word,这是一款流行的中文分词器,需要先安装IK分词器插件。
  • search_analyzer 指定搜索时使用的分词器。

4. 索引数据

将数据从MySQL导入到Elasticsearch。可以使用PHP脚本从MySQL读取数据,然后调用Elasticsearch PHP客户端的index方法将数据索引到Elasticsearch。

<?php

require 'vendor/autoload.php';

use ElasticsearchClientBuilder;

$client = ClientBuilder::create()->setHosts(['localhost:9200'])->build();

// 从MySQL读取数据
$host = 'localhost';
$username = 'your_username';
$password = 'your_password';
$database = 'your_database';

$conn = new mysqli($host, $username, $password, $database);

if ($conn->connect_error) {
    die("Connection failed: " . $conn->connect_error);
}

$sql = "SELECT id, title, content FROM articles";
$result = $conn->query($sql);

if ($result->num_rows > 0) {
    while($row = $result->fetch_assoc()) {
        $params = [
            'index' => 'articles',
            'id' => $row['id'],
            'body' => [
                'title' => $row['title'],
                'content' => $row['content']
            ]
        ];

        $response = $client->index($params);
        print_r($response);
    }
} else {
    echo "No articles found in MySQL.";
}

$conn->close();

?>

5. 执行搜索

使用Elasticsearch PHP客户端的search方法执行搜索。

<?php

require 'vendor/autoload.php';

use ElasticsearchClientBuilder;

$client = ClientBuilder::create()->setHosts(['localhost:9200'])->build();

$searchTerm = $_GET['q'] ?? ''; // 获取搜索关键词

$params = [
    'index' => 'articles',
    'body' => [
        'query' => [
            'multi_match' => [
                'query' => $searchTerm,
                'fields' => ['title', 'content']
            ]
        ]
    ]
];

$response = $client->search($params);

//print_r($response);

$hits = $response['hits']['hits'];

if (count($hits) > 0) {
    foreach ($hits as $hit) {
        echo "ID: " . $hit['_id'] . " - Title: " . $hit['_source']['title'] . " - Score: " . $hit['_score'] . "<br>";
    }
} else {
    echo "No results found.";
}

?>
  • index指定要搜索的索引。
  • query定义搜索查询。这里使用了multi_match查询,可以在多个字段中搜索关键词。
  • fields指定要搜索的字段。
  • _score是相关度评分。

6. 优点与缺点

特性 优点 缺点
集成复杂度 中等,需要安装和配置Elasticsearch,并使用Elasticsearch PHP客户端 需要学习Elasticsearch的API和概念,配置和维护相对复杂
性能 高,基于Lucene的分布式搜索引擎,具有强大的搜索性能和可扩展性 需要一定的硬件资源,对于小数据量,性能优势不明显
灵活性 高,支持自定义分词器、搜索算法和数据模型,可以满足各种复杂的搜索需求 学习曲线较陡峭,需要深入理解Elasticsearch的各种特性才能充分发挥其潜力
维护成本 中等,需要定期维护Elasticsearch集群,包括监控、备份和升级 需要一定的运维经验,当数据量和搜索需求增长时,需要进行集群扩展和优化
适用场景 数据量大,搜索需求复杂,对性能要求高的应用。例如,电商网站、新闻门户网站、企业搜索。 数据量较小,搜索需求简单,对性能要求不高的应用。

四、Algolia集成

Algolia是一个托管的搜索服务,提供了简单易用的API和强大的搜索功能。无需自己搭建和维护搜索服务器,可以快速实现高质量的全文搜索。

1. 创建Algolia账号和索引

首先,需要在Algolia官网注册账号,并创建一个索引。索引可以理解为Elasticsearch中的Index。

2. 安装Algolia PHP客户端

使用Composer安装Algolia PHP客户端:

composer require algolia/algoliasearch-client-php

3. 索引数据

将数据从MySQL导入到Algolia。可以使用PHP脚本从MySQL读取数据,然后调用Algolia PHP客户端的saveObjects方法将数据索引到Algolia。

<?php

require 'vendor/autoload.php';

use AlgoliaAlgoliaSearchSearchClient;

$client = SearchClient::create('Your Application ID', 'Your Admin API Key');
$index = $client->initIndex('articles');

// 从MySQL读取数据
$host = 'localhost';
$username = 'your_username';
$password = 'your_password';
$database = 'your_database';

$conn = new mysqli($host, $username, $password, $database);

if ($conn->connect_error) {
    die("Connection failed: " . $conn->connect_error);
}

$sql = "SELECT id, title, content FROM articles";
$result = $conn->query($sql);

$objects = [];
if ($result->num_rows > 0) {
    while($row = $result->fetch_assoc()) {
        $objects[] = [
            'objectID' => $row['id'], // Algolia要求必须有objectID
            'title' => $row['title'],
            'content' => $row['content']
        ];
    }
} else {
    echo "No articles found in MySQL.";
}

$conn->close();

$index->saveObjects($objects);

?>
  • Your Application IDYour Admin API Key可以在Algolia控制台中找到。
  • articles是索引名称。
  • objectID是每个文档的唯一标识符,Algolia要求每个文档必须包含objectID字段。

4. 执行搜索

使用Algolia PHP客户端的search方法执行搜索。

<?php

require 'vendor/autoload.php';

use AlgoliaAlgoliaSearchSearchClient;

$client = SearchClient::create('Your Application ID', 'Your Search-Only API Key');
$index = $client->initIndex('articles');

$searchTerm = $_GET['q'] ?? ''; // 获取搜索关键词

$results = $index->search($searchTerm);

//print_r($results);

$hits = $results['hits'];

if (count($hits) > 0) {
    foreach ($hits as $hit) {
        echo "ID: " . $hit['objectID'] . " - Title: " . $hit['title'] . " - Score: " . $hit['_rankingInfo']['nbTypos'] . "<br>";  // score的意义不同
    }
} else {
    echo "No results found.";
}

?>
  • Your Search-Only API Key可以在Algolia控制台中找到。
  • _rankingInfo 包含Algolia的排序信息。

5. 优点与缺点

特性 优点 缺点
集成复杂度 低,使用Algolia提供的API,无需自己搭建和维护搜索服务器 需要依赖第三方服务,数据存储在Algolia服务器上,可能存在数据安全和隐私问题
性能 高,Algolia专门为搜索优化,具有极快的搜索速度和良好的用户体验 性能受到网络延迟的影响,可能存在一定的延迟
灵活性 中等,Algolia提供了丰富的搜索配置选项,可以满足大部分搜索需求 无法完全自定义分词器和搜索算法,灵活性相对较低
维护成本 低,Algolia负责服务器的维护和升级,无需自己维护 需要支付Algolia的服务费用,费用根据搜索次数和数据量而定
适用场景 对搜索速度和用户体验要求高,但不希望自己维护搜索服务器的应用。例如,电商网站、移动应用。 对数据安全和隐私要求极高,或需要完全自定义搜索算法的应用。

五、方案对比总结

为了更清晰地对比这三种方案,我们将其特性总结如下表:

特性 MySQL内置全文搜索 Elasticsearch Algolia
集成复杂度 中等
性能
灵活性 中等
维护成本 中等
部署方式 自建 自建 托管
适用场景 小型应用 中大型应用 对速度敏感应用
成本 硬件成本 硬件和运维成本 服务费用

六、如何选择合适的方案

选择哪种方案取决于项目的具体需求和预算。

  • 如果数据量较小,搜索需求简单,对性能要求不高,且希望减少维护成本,可以选择MySQL内置的全文搜索。

  • 如果数据量较大,搜索需求复杂,对性能要求高,且愿意投入一定的硬件和运维成本,可以选择Elasticsearch。

  • 如果对搜索速度和用户体验要求高,但不希望自己维护搜索服务器,且愿意支付一定的服务费用,可以选择Algolia。

七、额外考量:中文分词

对于中文全文搜索,分词的质量至关重要。MySQL内置的分词器对中文的支持有限,Elasticsearch和Algolia都支持自定义分词器。常用的中文分词器包括:

  • IK分词器: 一个流行的开源中文分词器,支持多种分词模式。
  • 结巴分词: 另一个流行的开源中文分词器,具有简单易用、性能较好的特点。

在Elasticsearch中,可以通过安装IK分词器插件或结巴分词器插件来使用这些分词器。在Algolia中,可以选择Algolia提供的中文分词器,也可以自定义分词规则。

八、 总结与选择建议

今天我们讨论了PHP应用中实现全文搜索的三种主要方案:MySQL内置全文搜索、Elasticsearch和Algolia。每种方案都有其优点和缺点,适用于不同的场景。选择合适的方案需要综合考虑数据量、搜索需求、性能要求、预算和维护成本等因素。希望今天的分享能帮助大家在实际项目中做出明智的选择,构建高质量的全文搜索应用。

发表回复

您的邮箱地址不会被公开。 必填项已用 * 标注