mysql 单表数据2000多万,没有分区、没有表、没有分库,表存储的是企业信息,例如:企业名称、经营范围、注册地址等信息。现在需要通过关键词模糊匹配企业名称,搜索出匹配的数据。因为模糊匹配,所以直接在企业名称字段上建索引是没有效果的。如果直接用sql语句写,例如:select * from tab1 where name like ‘%搜搜一下%’,那简直是噩梦般,绝对是超时,并且mysql会卡死。
研究了一下各种索引组件,最后决定采用lucene.net,版本:3.0.3.0
如何安装lucene.net,其实很简单,就是直接通过vs2015的项目-》管理NuGet程序包..,然后搜索:lucene.net,最后点击下载安装就可以了,如下图:
安装完Lucene.net后,就需要建索引,2000多万条数据,如果是单线程创建索引,那大概需要48小时左右。 如果是多线程,那就看线程的数量,还有服务器的性能了。2000多万条数据创建完索引后,占了20G的空间左右。
创建索引的代码如下:
1、先打开索引目录
//创建索引目录
if (!System.IO.Directory.Exists(indexDir))
{
System.IO.Directory.CreateDirectory(indexDir);
}
FSDirectory directory = FSDirectory.Open(new DirectoryInfo(indexDir),
new NativeFSLockFactory());
//IndexReader:对索引库进行读取的类
bool isExist = IndexReader.IndexExists(directory);
//是否存在索引库文件夹以及索引库特征文件
if (isExist)
{
//如果索引目录被锁定(比如索引过程中程序异常退出或另一进程在操作索引库),则解锁
//Q:存在问题 如果一个用户正在对索引库写操作 此时是上锁的 而另一个用户过来操作时 将锁解开了 于是产生冲突 --解决方法后续
if (IndexWriter.IsLocked(directory))
{
IndexWriter.Unlock(directory);
}
}
//IndexWriter第三个参数:true指重新创建索引,false指从当前索引追加....此处为新建索引所以为true
writer = new IndexWriter(directory, new PanGuAnalyzer(), true,
Lucene.Net.Index.IndexWriter.MaxFieldLength.UNLIMITED);
2、写入索引
Document doc = new Document();
doc.Add(new Field("id", id, Field.Store.YES, Field.Index.NOT_ANALYZED));//进行索引,但是不进行分词,如果身份证号、姓名、ID等,适用于精确搜索
doc.Add(new Field("name", qymc, Field.Store.YES, Field.Index.ANALYZED));//存储且索引,适用模糊搜索
doc.Add(new Field("qymc2", qymc, Field.Store.YES, Field.Index.NOT_ANALYZED));//存储且索引,但是不进行分词,适用于精确搜索
doc.Add(new Field("fddbr", fddbr, Field.Store.YES, Field.Index.NO));//不进行索引
writer.AddDocument(doc);
特殊说明一下:
//Index.ANALYZED:进行分词和索引,适用于标题、内容等
//Index.NOT_ANALYZED:进行索引,但是不进行分词,如果身份证号、姓名、ID等,适用于精确搜索
//Index.ANALYZED_NOT_NORMS:进行分词但是不存储norms信息,这个norms中包括了创建索引的时间和权值等信息
//Index.NOT_ANALYZED_NOT_NORMS:即不进行分词也不存储norms信息
//Index.NO:不进行索引
创建完索引后,接下来就是查询索引,查询索引时,不能频繁打开索引,我是将IndexSearcher写成单例模式,只在最开始时打开一次。
查询索引的代码如下:
IndexSearcher search = luceneSingle.IndexSearcher;
//Occur.SHOULD表示条件关系为“or”,
//Occur.MUST表示“and”,
//Occur.MUST_NOT表示“not”
BooleanQuery bq = new BooleanQuery();
//省份
Query pnameQuery = null;
if (!string.IsNullOrEmpty(name))
{
//pname不进行分词,直接模糊匹配
pnameQuery = new PrefixQuery(new Term("name", name));
bq.Add(pnameQuery, Occur.MUST);
}
TopScoreDocCollector collector = TopScoreDocCollector.Create(PageIndex * PageSize, true);
search.Search(bq, null, collector);
//起始数
int start = PageSize * (PageIndex - 1);
//结束数
int limit = PageSize;
//分页查询
var hits = collector.TopDocs(start, limit).ScoreDocs;
//总条数
int numTotalHits = collector.TotalHits;
Maigou.Common.Log.OperateLog.WriteLog("numTotalHits:" + numTotalHits, "lucene_");
//总页数
var pageCount = 0;
//总条数
var totalSize = numTotalHits;
if (totalSize != 0)
{
if ((totalSize % 10) != 0)
pageCount = (totalSize / 10) + 1;
else
pageCount = (totalSize / 10);
}
for (int i = 0; i < hits.Length; i++)
{
var hit = hits[i];
Document doc = search.Doc(hit.Doc);
#region 数据
var id = doc.Get("id").ToString();
var model = new qyModel()
{
//id = Zhangwoo.Core.DESEncrypt.Encrypt(id, "87654321"),
id= id,
qymc = doc.Get("qymc").ToString(),
fddbr = fddbr,
tel = telStr,
addr = doc.Get("addr").ToString(),
email = doc.Get("email").ToString(),
zczb = doc.Get("zczb").ToString(),
jyfw = doc.Get("jyfw").ToString(),
pageCount = pageCount
};
list.Add(model);
#endregion
}
如果要体验查询速度,可以在:搜搜一下,免费企业信息查询(www.sousouyixia.com)试一下查询速度。
这里分词是采用盘古分词的,听说盘古分词已经停止更新了。 我实际使用,也发现盘古分词不是很全,有些关键词没办法搜搜到。 如何有其他更好的分词算法,也可以帮推荐一下。