基于.NET的分词软件设计与实现V6.0--使用数据库篇
忙了一阵子,今天用空下来的一点时间来总结一下之前未完成的分词系列吧。。
上篇提到了使用HashSet<T>作为词典存储数据结构的方法,这也是在不使用数据库的情况下,自己在能力范围之内找到的最佳的解决方案。
但是,如果使用数据库呢,好吧,下面就让我们来看在使用数据库的情况下,本分词软件的表现。
一、建立数据库
在之前的版本中,分词的词典都以文本的形式直接保存在txt文件中,这里自然要将其全部转存到数据库的表中,介于词典采用的是每行存取一个词的方法,我采用的方法是循环读取文本文档的每一行,随后使用insert语句将其录入数据库的表中。
随后我们不作任何优化措施,直接开始简单的测试,首先开启SQL中显示统计信息和分析、编译、执行各语句耗时的功能:
SET STATISTICS IO ONSET 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%填充因子可以提升读取的性能,但会减缓写活动的功能,引发频繁的页拆分,因为数据库引擎为了在数据页中得到空间必须持续地交换行的位置。
填充因子太低会给插入带来便利,但读取较慢,因为如果填充因子太小,就需要使用更多的页来保存数据,而更多页就意味着每个查询要读取的物理数据页读操作的数量也变得更多,此时,读和写操作的机能都会降低。
最佳实践:在没有修改活动的表中使用100%的填充因子,中低活动的使用70-90%,高活动的使用50%甚至更低的填充因子。
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秒左右,较之前没有使用索引的惨不忍睹有了不少好转。
四、优化第三步——使用存储过程
首先,列举一下公认的存储过程的几个优点:
•高效:
–存储过程只在建立时编译,以后每次执行就不需要重新编译,而一般SQL语句每执行一次都需要先分析,然后再执行,所以使用存储过程可提高SQL语句的执行效率。
–存储过程代码直接存储于数据库中,不会产生大量T-sql语句的代码流量,显著降低了网络流量。
•安全:存储过程执行时,使用的是参数化的SQL语句,防止了常见的如SQL注入攻击等。
•可复用,可维护性高:更新存储过程通常比更改、测试以及重新部署程序集需要较少的时间和精力。
由于分词过程中需要大量重复的执行SQL语句,那到底使用存储过程对于执行效率有没有提高呢,那我们一试:
USE [Splitter]GO/****** 对象: StoredProcedure [dbo].[IsExist] 脚本日期: 05/07/2011 01:34:56 ******/SET ANSI_NULLS ONGOSET QUOTED_IDENTIFIER ONGOCREATE PROCEDURE [dbo].[IsExist]( @item varchar(50), @result int output)ASBEGIN SET NOCOUNT ON; SELECT @result = count(*) from Vocabulary WHERE item = @itemEND程序中详细调用步骤:
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(&
补充:Web开发 , ASP.Net ,