用 PHP 构建自定义搜索引擎2

来源:互联网 发布:安卓怎样优化最流畅 编辑:程序博客网 时间:2024/05/20 18:19




Inventory 表是汽车零件的典范列表。零件 —— 例如螺钉或灯泡 —— 可能用于每辆汽车和多个部件中,但是零件只在 Inventory 表中显示一次。Inventory 表中的每行包含:

  ·使用了惟一的 32 位整数 serialno 标识行。
  ·字母数字零件号(此零件号惟一并且可以用作主键。但是,由于它可以包含字母数字字符,因此它不适于与 Sphinx 结合使用,Sphinx 要求索引的每条记录都有一个惟一的 32 位整型键)。
  ·文本描述。
  ·价格。

  Inventory 表的规范如清单 3 中所示:

  清单 3. Inventory 表

CREATE TABLE Inventory (
id int(10) unsigned NOT NULL auto_increment,
partno varchar(32) NOT NULL,
descrīption varchar(256) NOT NULL,
price float unsigned NOT NULL default '0',
PRIMARY KEY (id),
UNIQUE KEY partno USING BTREE (partno)
) ENGINE=InnoDB;

  零件的(部分)列表可能如下面所示:

INSERT INTO `Inventory`
(`id`, `partno`, `descrīption`, `price`)
VALUES
(1,'WIN408','Portal window',423),
(2,'ACC711','Jack kit',110),
(3,'ACC43','Rear-view mirror',55),
(4,'ACC5409','Cigarette lighter',20),
(5,'WIN958','Windshield, front',500),
(6,'765432','Bolt',0.1),
(7,'ENG001','Entire engine',10000),
(8,'ENG088','Cylinder head',55),
(9,'ENG976','Large cylinder head',65);

  Schematic 表

  Schematic 表将把零件与部件和车型版本绑定在一起。因此,将使用 Schematic 表来查找组装 1979 J Class 敞篷车引擎的所有零件。Schematic 表中的每行都有一个惟一 ID,一个引用 Inventory 表行的外键,一个标识部件的外键,以及用于引用 Model 表中特定型号和版本的另一个键。各行如清单 4 所示:

  清单 4. Schematic 表

CREATE TABLE Schematic (
id int(10) unsigned NOT NULL auto_increment,
partno_id int(10) unsigned NOT NULL,
assembly_id int(10) unsigned NOT NULL,
model_id int(10) unsigned NOT NULL,
PRIMARY KEY (id),
KEY partno_index USING BTREE (partno_id),
KEY assembly_index USING BTREE (assembly_id),
KEY model_index USING BTREE (model_id),
FOREIGN KEY (partno_id) REFERENCES Inventory(id),
FOREIGN KEY (assembly_id) REFERENCES Assembly(id),
FOREIGN KEY (model_id) REFERENCES Model(id)
) ENGINE=InnoDB;

  为了强化表的意图,下面是 Schematic 中的一张小型行列表:

INSERT INTO `Schematic`
(`id`, `partno_id`, `assembly_id`, `model_id`)
VALUES
(1,6,5,1),
(2,8,5,1),
(3,1,3,1),
(4,5,3,1),
(5,8,5,7),
(6,6,5,7),
(7,4,7,3),
(8,9,5,3);

  搜索表

  定义了这些表后,就可以轻松地响应很多搜索:

  ·显示特定型号的所有版本
  ·列出装配特殊型号和版本所需的所有部件
  ·显示构成特定型号和版本的特殊部件的所有零件

  但是很多搜索代价较大:

  ·查找所有模型和版本中出现零件号开头为 “WIN” 的所有零件
  ·查找描述中有 “lacquer” 或 “paint” 的那些零件
  ·查找描述中有 “black leather” 的所有零件
  ·查找描述中有 “paint” 的所有 2002 J 系列零件

  这些搜索中的每个搜索都要求使用长篇的 JOIN 子句或代价高昂的 LIKE 子句,尤其是在 Inventory 表和 Schematic 表十分大时更是如此。而且,复杂的文本搜索完全超出了 MySQL 的能力。要搜索大量文本数据,请考虑构建和使用 Sphinx 索引。

  集成 Sphinx 软件

  要应用 Sphinx 来解决问题,您必须定义一个或多个数据源以及一个或多个索引。

  source 将标识数据库来建立索引,提供验证信息,并且定义查询用以构造每行。数据源可以随意地标识一列或多列作为过滤器,Sphinx 将之称为组。您将使用组来过滤结果。例如,单词描述可能得到 900 个匹配。如果只对特定型号的汽车匹配感兴趣,则可以进一步使用型号组进行过滤。

  index 将要求获得数据源(即一组数据行)并定义应当如何为已从数据源中提取出来的数据编目。

  您将在 sphinx.conf 文件中定义数据源和索引。Body Parts 的数据源是 MySQL 数据库。清单 5 显示了名为 catalog 的数据源的部分定义 —— 指定连接的数据库以及如何建立连接(主机、套接字、用户和密码)的代码片段。

  清单 5. 用于访问 MySQL 数据库的设置

source catalog
{
type = mysql

sql_host = localhost
sql_user = reaper
sql_pass = s3cr3t
sql_db = body_parts
sql_sock = /var/run/mysqld/mysqld.sock
sql_port = 3306

  接下来,创建一个查询以生成要被索引的行。通常,将创建 SELECT 子句,可能需要把许多表 JOIN 在一起才能得到行。但这里存在一个问题:搜索型号和年份必须使用 Assembly 表,但是零件号和零件描述只能在 Inventory 表中找到。为此,Sphinx 必须能够把搜索结果与 32 位整型主键绑定在一起。

  要获得右侧表单中的数据,需要创建一个视图 —— MySQL V5 中的新结构,它将把来自其他表的列整合到单独的合成虚拟表中。使用视图,各类搜索所需的所有数据都在一个位置,但是活动数据实际上存在于其他表中。清单 6 显示了定义 Catalog 视图的 SQL。

  清单 6. Catalog 视图将把数据整合到虚拟表中

CREATE OR REPLACE VIEW Catalog AS
SELECT
 Inventory.id,
 Inventory.partno,
 Inventory.descrīption,
 Assembly.id AS assembly,
 Model.id AS model
FROM
 Assembly, Inventory, Model, Schematic
WHERE
 Schematic.partno_id=Inventory.id
AND Schematic.model_id=Model.id
AND Schematic.assembly_id=Assembly.id;

  如果用前面所示的表和数据创建名为 body_parts 的数据库,则 Catalog 视图应当类似以下内容:

mysql> use body_parts;
Database changed
mysql> select * from Catalog;
+----+---------+---------------------+----------+-------+
| id | partno | descrīption | assembly | model |
+----+---------+---------------------+----------+-------+
| 6 | 765432 | Bolt | 5 | 1 |
| 8 | ENG088 | Cylinder head | 5 | 1 |
| 1 | WIN408 | Portal window | 3 | 1 |
| 5 | WIN958 | Windshield, front | 3 | 1 |
| 4 | ACC5409 | Cigarette lighter | 7 | 3 |
| 9 | ENG976 | Large cylinder head | 5 | 3 |
| 8 | ENG088 | Cylinder head | 5 | 7 |
| 6 | 765432 | Bolt | 5 | 7 |
+----+---------+---------------------+----------+-------+
8 rows in set (0.00 sec)

  在视图中,字段 id 将指回 Inventory 表中的零件条目。partno 和 descrīption 列是要搜索的主要文本,而 assembly 和 model 列用作进一步过滤结果的组。视图就绪后,构造数据源查询就是小事一桩。清单 7 显示了 catalog 数据源定义的其余部分。

  清单 7. 查询创建待索引的行

# indexer query
# document_id MUST be the very first field
# document_id MUST be positive (non-zero, non-negative)
# document_id MUST fit into 32 bits
# document_id MUST be unique
sql_query = /
SELECT /
 id, partno, descrīption, /
 assembly, model /
FROM /
 Catalog;

 sql_group_column = assembly
 sql_group_column = model

 # document info query
 # ONLY used by search utility to display document information
 # MUST be able to fetch document info by its id, therefore
 # MUST contain '$id' macro
 #
 sql_query_info = SELECT * FROM Inventory WHERE id=$id
}

  sql_query 必须包括后续查找需要使用的主键,并且它必须包括需要索引和用作组的所有字段。两个 sql_group_column 条目将声明 Assembly 和 Model 可用于过滤结果。并且 search 实用程序将使用 sql_query_info 来查找匹配记录。在查询中,$id 被替换为 searchd 返回的每个主键。

  最后一个配置步骤是构建索引。清单 8 显示了数据源 catalog 的索引。

  清单 8. 描述 catalog 数据源的一个可能的索引

index catalog
{
 source = catalog
 path = /var/data/sphinx/catalog
 morphology = stem_en

 min_word_len = 3
 min_prefix_len = 0
 min_infix_len = 3
}

  第 1 行将指向 sphinx.conf 文件中的指定数据源。第 2 行将定义存储索引数据的位置;按照约定,Sphinx 索引将被存储到 /var/data/sphinx 中。第 3 行将允许索引使用英文词法。并且第 5 行至第 7 行将告诉索引器只索引含有三个字符或更多字符的那些单词,并且为每个这样的字符的子字符串创建中缀索引(为了便于引用,清单 9 显示了 Body Parts 的完整示例 sphinx.conf 文件)。

  清单 9. Body Parts 的示例 sphinx.conf

source catalog
{
 type = mysql

 sql_host = localhost
 sql_user = reaper
 sql_pass = s3cr3t
 sql_db = body_parts
 sql_sock = /var/run/mysqld/mysqld.sock
 sql_port = 3306

 # indexer query
 # document_id MUST be the very first field
 # document_id MUST be positive (non-zero, non-negative)
 # document_id MUST fit into 32 bits
 # document_id MUST be unique

 sql_query = /
SELECT /
 id, partno, descrīption, /
 assembly, model /
FROM /
 Catalog;

 sql_group_column = assembly
 sql_group_column = model

 # document info query
 # ONLY used by search utility to display document information
 # MUST be able to fetch document info by its id, therefore
 # MUST contain '$id' macro
 #

 sql_query_info = SELECT * FROM Inventory WHERE id=$id
}

index catalog
{
 source = catalog
 path = /var/data/sphinx/catalog
 morphology = stem_en

 min_word_len = 3
 min_prefix_len = 0
 min_infix_len = 3
}

searchd
{
 port = 3312
 log = /var/log/searchd/searchd.log
 query_log = /var/log/searchd/query.log
 pid_file = /var/log/searchd/searchd.pid
}

  底部的 searchd 部分将配置 searchd 守护程序本身。该部分中的条目不言自明。query.log 尤为有用:它将在运行时显示每次搜索并显示结果,例如搜索的文档数和匹配总数。