dexleapFTC,一个纯粹由存储过程组成的全文搜索引擎
为所有 MS SQL Server 版本实现一个全文搜索引擎,全部使用存储过程。
引言
要在 MS SQL Server 应用程序中执行全文搜索,通常有两种选择
Select Something From SomeTable Where SomeText Like %SomePhrases%
.- SQL Server 内置的全文搜索引擎。
或者
第一种选择只能处理 8000 个字符长度的字段(4000 个 Unicode 字符)。当然,您可以将长文本拆分成多个 8K 字段,但问题在于 %SomePhrases% 模式。此查询总是会导致“蛮力”表扫描,当文本量增大时,这会非常慢。
第二种选择是更好的选择。但是,它仍然有两个问题
- SQL Server 桌面版 (MSDE) 没有这个内置引擎。
- 即使您的 SQL Server 版本有这个引擎,您也几乎无法控制它。
FTC 方法的目标是使 SQL Server 上的全文索引和搜索过程易于理解、易于实现,并让程序员完全控制该过程。
FTC 方法 - 如何索引
该方法由存储过程组成,并在所有 SQL Server 版本(包括 MSDE 和 Express 版本)上运行。
我们来看一个表
ContentID | CreateDate | ContentText |
4891 | 3/30/2006 | The quick brown fox jumps over the lazy dog. |
FTC 将内容文本逐个单词拆分,并将它们插入索引表中。无论文本有多大,单个单词总是由一个整数表示。这个整数是该单词的索引 ID。像“the”这样的停用词会被忽略。FTC 维护一个额外的表来处理这些停用词。
WordIndex | IndexWord |
1 | quick |
2 | brown |
3 | fox |
4 | jumps |
5 | over |
6 | lazy |
7 | dog |
tbl_IndexMap 链接 tbl_WordIndex 和 tbl_Content。该表的所有三个字段都是整数。这消除了最终表的大小并加快了搜索速度。为避免任何“蛮力表扫描(即,针对 tbl_Content 的“%%”模式查询),FTC 会记录每个单词在文本中的起始位置。这就像一个指针,因此 FTC 始终可以知道单词在原始文本中的确切位置。
WordIndex | WordStartPosition | ContentID |
1 | 5 | quick |
2 | 11 | brown |
3 | 17 | fox |
4 | 21 | jumps |
5 | 27 | over |
6 | 36 | lazy |
7 | 41 | dog |
FTC 方法 - 如何分词
将短语中的单词拆分称为“词干提取”。大多数全文搜索引擎使用“词干提取符号”来提取基于拉丁语的语言,如英语、法语、西班牙语、意大利语和德语。词干提取符号包括空格、逗号、句号等。对于中文、日文和韩文(CJK)等远东语言,每个字符本身就是一个词干提取符号。
FTC 使用 UNICODE 范围进行词干提取。对于任何给定的字符串,FTC 都会逐个字符进行处理。如果字符在 0x0041 到 0x005a 之间,FTC 就知道它是一个有意义的拉丁字符。FTC 不会检查下一个字符,直到遇到任何非拉丁 UNICODE。当遇到非拉丁代码时,FTC 将从之前的字符构建一个单词。注意:范围 0x0041-0x005a 仅为示例。请访问 www.unicode.org 了解更多关于 UNICODE 的详细信息。
FTC 方法 - 如何搜索
假设您想在内容表中搜索精确短语“brown fox jumps”。
第一步:为此任务创建一个**临时**短语表
WordIndex | PhraseWord | WordRelativePosition |
棕色 | 1 | |
Fox | 7 | |
jumps | 11 |
基本上,短语被拆分成单词,这些单词被插入到临时短语表中。“WordRelativePosition
”是单词在短语中的位置。
第二步:为每个单词填充索引 ID(来自 tbl_WordIndex)
WordIndex | PhraseWord | WordRelativePosition |
2 | 棕色 | 1 |
3 | Fox | 7 |
4 | jumps | 11 |
第三步:搜索...
目标是查找“tbl_IndexMap”中是否存在具有与短语表相同的单词索引和相同的相对位置的项。在临时短语表中,“brown”、“fox”、“jumps”分别具有位置“1”、“7”、“11”。在 tbl_IndexMap 中,“brown”、“fox”、“jumps”分别具有位置“11”、“17”、“21”。它们显然不同。但是,它们是相对相同的,它们都具有从单词“brown”开始的相同距离。因此,FTC 程序可以确定短语“brown fox jumps”在“content# 4891”中有精确的搜索结果。
表格
演示实现了这些表
- tdatArchiveEntry -- 此表保存所有文本条目。
- tdatWordIndex -- 此表处理唯一单词索引。每个单词都由一个整数表示。该整数是单词索引 ID。
- tdatWordIndex2Text -- 此表映射“tdatWordIndex”和“tdatArchiveEntry”。
- tdatWordIndexControl -- 这为长时间索引进程提供了立即停止的机会。
- trefIndexStatus -- 这是一个引用表。它包含一个条目的四个可能状态:“不索引”、“准备索引”、“正在索引”、“已索引”。
- trefWordNoise -- 这也是一个引用表。所有停用词都在这里,例如“the”、“a”、“this”、“that”等。
存储过程
演示实现了这些存储过程
up_EntryAdd
up_EntryAddStringChunk
up_EntryContent
up_EntryList
up_EntryReadyToIndex
up_EntryRemove
up_MaxPageNum
up_WordIndex
up_WordIndexControl
up_WordIndexProgress
up_WordIndexStartOver
up_WordSearch
命名非常直观。您可以根据名称判断其功能。最重要的两个 SP 是 up_WordIndex
和 up_WordSearch
。
up_wordIndex
索引所有状态为“准备索引”的条目。索引后,这些条目的状态将变为“已索引”。此过程可能是一个长时间运行的任务。不需要输入参数。要停止长时间运行的任务,您可以将 tdatWordIndexControl
的 ynDoIndex
字段设置为“no”。这将立即停止正在运行的过程。
前端应用程序调用 up_wordSearch
来执行**精确**或**全部**搜索。“精确搜索”是指用户希望在文本中查找精确短语“jumps over the lazy dog”;“全部搜索”是指用户希望查找文本中同时包含“jumps,dog,fox”。
重要提示: 是的,您可以并发运行 up_WordIndex
,当然,您可以随时按需运行它。但是,除非您正在开发一个单用户系统,否则这**是个坏主意**。
索引过程是一项繁重的长时间运行过程。
想象一个处理并发用户输入的 Web 应用程序。如果系统在用户提交其输入时启动索引过程,您猜会发生什么?
如果只有一个用户提交,您很幸运;10 个用户同时提交输入,系统会令人恼火地变慢一点;50 个用户同时提交,您的系统可能会崩溃。
一些**好的实践**应该是
- 将索引过程放入调度程序并在批处理中运行。
- 设计您的系统,使索引过程在系统空闲时运行。
前端技巧
演示的前端是一个带有 Web 浏览器控件的 C# WinForms 应用程序。
ActiveX 控件始终指向一个名为“ax.htm”的本地 HTML 文件。每当需要刷新时,FTC 程序将对“ax.htm”进行一些文件编辑,并触发 NavigateComplete2
事件来更新页面。
当 ax.htm 中的超链接(如“view”、“delete”、“page number”)被点击时,FTC 前端窗体会响应。这些超链接背后的技巧很简单,就是“about:WhatEverCommand”。
有许多其他文档教授如何使用 ATL 和 COM/ActiveX 处理程序来完成相同的任务,但没有一个像“about:”协议那样简单。
非官方性能基准测试
dexleapFTC 的性能取决于许多因素,如硬件配置、并发请求、索引质量和带宽等。
我设置了一个在线实时演示来测试它作为 Web 门户全文搜索引擎。在在线实时演示中,索引 450,000 个英文单词大约需要 400 秒。
我也在本地进行了测试。在 800MHz/512M PC 上,对 200,000 个英文单词的“精确短语”搜索不到 0.2 秒;在同一台 PC 上,对 700,000 个英文单词的“精确短语”搜索不到 0.5 秒(平均而言,一个英文单词由 4.5 个字符组成)。
源代码中的命名约定(枯燥的细节)
命名约定非常重要。它有助于创建自解释的源代码,便于项目评审和程序维护。
FTC 项目使用 C#(.NET Framework 1.1/VS.NET 2003)和 MSDE2000 完成。以下是在 FTC 项目中使用的命名约定
DataTable
的名称以 tdat 开头,例如:tdatArchiveEntry
。- 引用表(reference table)的名称以 tref 开头,例如:
trefIndexStatus
。 - 存储过程的名称格式为 up_SubjectVerb,例如:
up_EntryAddStringChunk
。 - 类成员变量的名称以小写字母开头,例如:
sCurrentPath
。 - 函数的名称以大写字母开头,例如:
RefreshIndexProgress()
,Dal_sEntryText(int _ixArchiveEntry)
。 - 函数局部变量的名称以_下划线开头,例如:
_daWordSearch
。
关于变量的一些额外规则
- 大多数变量的名称遵循模式:类型 + 主题 + [动词](大小写是其中的一部分)。
- FTC 项目中不使用布尔变量。使用类似
_ynDoIndex
的变量来表示。 "yes" 表示执行索引,"no" 表示不执行索引。 - "ix" 代表 "index",它是数组/列表/表/循环中的位置或主键。
- "n" 代表 "status" 或 "state",它是 "n" 个选项中的一个状态。
- "cnt" 是 "count"。该变量将计算数组/列表/表中的元素数量。
- "s" 是 UNICODE 字符串;"as" 是 ASCII 字符串。
- "conn" 是 "connection";"cmd" 是 "command"。
如何使用演示项目
userguide.chm 和 programmerguide.chm 文件包含在演示项目文件夹中。
为了方便您测试演示,我已经在数据库中输入了 6 部莎士比亚戏剧。非常感谢 Project Gutenberg,他们提供了没有版权问题的免费文本。
最后
© 2005-2006 DEXLEAP.COM 版权所有。
本文的唯一授权版本可在以下网站获取: www.codeproject.com,www.dexleap.com。