基于.NET的分词软件设计与实现V6.0--使用数据库篇(涉及索引、聚集因子和存储过程)
忙了一阵子,今天用空下来的一点时间来总结一下之前未完成的分词系列吧。。
上篇提到了使用HashSet<T>作为词典存储数据结构的方法,这也是在不使用数据库的情况下,自己在能力范围之内找到的最佳的解决方案。
但是,如果使用数据库呢,好吧,下面就让我们来看在使用数据库的情况下,本分词软件的表现。
一、建立数据库
在之前的版本中,分词的词典都以文本的形式直接保存在txt文件中,这里自然要将其全部转存到数据库的表中,介于词典采用的是每行存取一个词的方法,我采用的方法是循环读取文本文档的每一行,随后使用insert语句将其录入数据库的表中。
随后我们不作任何优化措施,直接开始简单的测试,首先开启SQL中显示统计信息和分析、编译、执行各语句耗时的功能:
SET STATISTICS IO ON
SET STATISTICS TIME ON
来看一下查询“他们”这个简单的词,select * from Vocabulary where item = '他们'
SQL中的执行结果:
注意下面几个数据:逻辑读取871次,CPU时间=62毫秒,占用时间=59毫秒。
随后,我们将程序中判断某个词是否存在的程序改为:
/// <summary>
/// Updated:判断是否在词典中出现
/// </summary>
/// <param name="str"></param>
/// <returns></returns>
bool isExist(string str)
{
DBHelper db = new DBHelper();
return Convert.ToInt32(db.ExecuteScalar("select count(*) from Vocabulary where item = '" + str + "'")) > 0;
}
起初我计划以1000字的文本测试,但最后发现这个想法很不现实,为什么?让我们看下100字文本的分词结果就知道了:
没错,100字的文本分词时间居然达到了20+秒,无法忍受的一个结果。
二、优化第一步——建立索引
索引的文章园子里面有很多,从原理到实例都有些经典的,这里自不必多说,这里主要看一下索引在本分词软件中的应用。
首先,我们频繁使用item字段,也就是保存词的字段进行查询,且其是表的主键,很适合建立聚集索引:
USE [Splitter]
GO
/****** 对象: Index [PK_Vocabulary] 脚本日期: 05/06/2011 23:56:26 ******/
CREATE CLUSTERED INDEX [PK_Vocabulary] ON [dbo].[Vocabulary]
(
[item] ASC
)
WITH
(
SORT_IN_TEMPDB = OFF,
DROP_EXISTING = OFF,
IGNORE_DUP_KEY = OFF,
ONLINE = OFF
)
ON [PRIMARY]
好了,建立完成后来看一下最终的结果:
是不是看着顺眼多了(附加一句:py和len两个字段是我为了方便某些特殊查询,比如看有多少个拼音简写是ab的词等,在 程序中木有用处)。
下面以相同的语句查询“他们”这个词,看下结果:
注意下面几个数据:逻辑读取2次,CPU时间=0毫秒,占用时间=11毫秒。耗时明显大幅度降低了。
三、优化第二步——使用填充因子
填充因子百分比指首次创建索引时索引页的叶级别充满程序,若没有显示设置,则默认为0。
当最初生成索引时,SQL Server 将索引 B 树结构放置在连续的物理页上,以便通过连续 I/O 扫描索引页获取最佳 I/O 性能。当由于发生页拆分,需要将新的页插入索引的逻辑 B 树结构时,SQL Server 必须分配新的 8 KB 索引页。这种插入发生在硬盘上的其它位置,从而打断了索引页的物理连续特性。使 I/O 操作从连续变为不连续,从而使得性能减低一半。可以通过重建索引页以恢复索引页的物理连续顺序来解决过多的页拆分。聚集索引的叶级也会遇到相同的问题,从而影响表的数据页。
100%填充因子可以提升读取的性能,但会减缓写活动的功能,引发频繁的页拆分,因为数据库引擎为了在数据页中得到空间必须持续地交换行的位置。
USE [Splitter]
GO
/****** 对象: Index [PK_Vocabulary] 脚本日期: 05/06/2011 23:56:26 ******/
CREATE CLUSTERED INDEX [PK_Vocabulary] ON [dbo].[Vocabulary]
(
[item] ASC
)
WITH
(
PAD_INDEX = ON,
FILLFACTOR = 100, --填充因子100%
SORT_IN_TEMPDB = OFF,
DROP_EXISTING = OFF,
IGNORE_DUP_KEY = OFF,
ONLINE = OFF
)
ON [PRIMARY]
上面的代码演示了创建聚集索引并指定100%的填充因子。
继续看一下数据库中查询“他们”的效果:
注意下面几个数据:逻辑读取3次,CPU时间=0毫秒,占用时间=1毫秒。
可以看到较建立索引并使用默认填充因子的表现又更进了一步。
下面可以看一下1000字文本的测试结果了:
可以看到,1000字的文本仅仅花了1.7秒左右,较之前没有使用索引的惨不忍睹有了不少好转。
四、优化第三步——使用存储过程
首先,列举一下公认的存储过程的几个优点:
•高效:
USE [Splitter]
GO
/****** 对象: StoredProcedure [dbo].[IsExist] 脚本日期: 05/07/2011 01:34:56 ******/
SET ANSI_NULLS ON
GO
SET QUOTED_IDENTIFIER ON
GO
CREATE PROCEDURE [dbo].[IsExist]
(
@item varchar(50),
@result int output
)
AS
BEGIN
SET NOCOUNT ON;
SELECT @result = count(*) from Vocabulary WHERE item = @item
END
程序中详细调用步骤:
bool isExist(string str)
{
SqlConnection con = new SqlConnection(ConfigurationManager.ConnectionStrings["ConnectionString"].ToString());
con.Open();
SqlCommand cmd = new SqlCommand("IsExist", con);
cmd.CommandType = CommandType.StoredProcedure;
cmd.Parameters.Add(new SqlParameter("@item", SqlDbType.VarChar, 50));
cmd.Parameters["@item"].Value = str;
cmd.Parameters.Add(new SqlParameter("@result", SqlDbType.Int));
cmd.Parameters["@result"].Direction = ParameterDirection.Output;
int result = cmd.ExecuteNonQuery();
con.Close();
return Convert.ToBoolean(result);
}
再次对1000字的文本进行测试,时间有小幅提升,大致在1.3秒左右,较之前有25%左右的性能提升。
五、总结
至此算是完成了本分词软件的所有研究,进行一个总结示例如下(其中V5.0和V6.0合并在上篇一起讲了):
不过这还木有完结,下篇将是最后一篇,讲述本分词软件移植BS时的最后展示效果。
出处:http://www.cnblogs.com/RockyMyx/
本文版权归作者和博客园共有,欢迎转载,但请在文章明显位置给出原文连接,否则保留追究法律责任的权利。