字符串创建索引方式:
1、直接创建完整索引,比较占用空间。
2、创建前缀索引,节省空间,但会增加查询扫描次数,并且不能使用覆盖索引。
3、倒序存储,在创建前缀索引,用于绕过字符串本身前缀的却分度不够的问题。
4、创建hash字段索引,查询性能稳定,有额外的存储和计算消耗。
倒序存储和hash字段索引都不支持范围查询。倒序存储的字段上创建的所有是按照倒序字符串的方式排序的。hash字段的方式也只能支持等值查询。
mysql>alter table SUser add index index1(email):包含了每个记录的整个字符串
或
mysql>alter table SUser add index index2(email(6)):-对于每个记录只取前6个字节
全字段索引 *** 作流程
使用的是 index1(即 email 整个字符串的索引结构),执行顺序是这样的:
1、从 index1 索引树找到满足索引值是’ zhangssxyz@xxx.com ’的这条记录,取得 ID2 的值;
2、到主键上查到主键值是 ID2 的行,判断 email 的值是正确的,将这行记录加入结果集;
3、取 index1 索引树上刚刚查到的位置的下一条记录,发现已经不满足 email=' zhangssxyz@xxx.com ’的条件了,循环结束。
前缀字段索引 *** 作流程
如果使用的是 index2(即 email(6) 索引结构),执行顺序是这样的:
1、从 index2 索引树找到满足索引值是’zhangs’的记录,找到的第一个是 ID1;
2、到主键上查到主键值是 ID1 的行,判断出 email 的值不是’ zhangssxyz@xxx.com ’,这行记录丢弃;
3、取 index2 上刚刚查到的位置的下一条记录,发现仍然是’zhangs’,取出 ID2,再到 ID 索引上取整行然后判断,这次值对了,将这行记录加入结果集;
4、重复上一步,直到在 idxe2 上取到的值不是’zhangs’时,循环结束。
倒序查询和hash字段的区别
它们的区别,主要体现在以下三个方面:
1、从占用的额外空间来看,倒序存储方式在主键索引上,不会消耗额外的存储空间,而 hash 字段方法需要增加一个字段。当然,倒序存储方式使用 4 个字节的前缀长度应该是不够的,如果再长一点,这个消耗跟额外这个 hash 字段也差不多抵消了。
2、在 CPU 消耗方面,倒序方式每次写和读的时候,都需要额外调用一次 reverse 函数,而 hash 字段的方式需要额外调用一次 crc32() 函数。如果只从这两个函数的计算复杂度来看的话,reverse 函数额外消耗的 CPU 资源会更小些。
3、从查询效率上看,使用 hash 字段方式的查询性能相对更稳定一些。因为 crc32 算出来的值虽然有冲突的概率,但是概率非常小,可以认为每次查询的平均扫描行数接近 1。而倒序存储方式毕竟还是用的前缀索引的方式,也就是说还是会增加扫描行数。
创建索引的过程如下:
索引结构如下:
IndexWriter结构:
IndexWriter通过指定存放的目录(Directory)以及文档分析器(Analyzer)来构建,direcotry代表索引存储在哪里;analyzer表示如何来分析文档的内容;similarity用来规格化文档,给文档算分;IndexWriter类里还有一些SegmentInfos对象用于存储索引片段信息,以及发生故障回滚等。
添加文档使用addDocument()方法,删除文档使用deleteDocuments(Term)或者deleteDocuments(Query)方法,而且一篇文档可以使用updateDocument()方法来更新(仅仅是先执行delete在执行add *** 作而已)。当完成了添加、删除、更新文档,应该需要调用close方法。
这些修改会缓存在内存中(buffered in memory),并且定期地(periodically)刷新到(flush)Directory中(在上述方法的调用期间)。一次flush *** 作会在如下时候触发(triggered):当从上一次flush *** 作后有足够多缓存的delete *** 作(参见setMaxBufferedDeleteTerms(int)),或者足够多已添加的文档(参见setMaxBufferedDocs(int)),无论哪个更快些(whichever is sooner)。对被添加的文档来说,一次flush会在如下任何一种情况下触发,文档的RAM缓存使用率(setRAMBufferSizeMB)或者已添加的文档数目,缺省的RAM最高使用率是16M,为得到索引的最高效率,你需要使用更大的RAM缓存大小。需要注意的是,flush处理仅仅是将IndexWriter中内部缓存的状态(internal buffered state)移动进索引里去,但是这些改变不会让IndexReader见到,直到commit()和close()中的任何一个方法被调用时。一次flush可能触发一个或更多的片断合并(segmentmerges),这时会启动一个后台的线程来处理,所以不会中断addDocument的调用,请参考MergeScheduler。
一个IndexReader或者IndexSearcher只会看到索引在它打开的当时的状态。任何在索引被打开之后提交到索引中的commit信息,在它被重新打开之前都不会见到。
DocumentsWriter结构:
DocumentsWriter 是由IndexWriter 调用来负责处理多个文档的类,它通过与Directory 类及Analyzer 类、Scorer 类等将文档内容提取出来,并分解成一组term列表再生成一个单一的segment 所需要的数据文件,如term频率、term 位置、term 向量等索引文件,以便SegmentMerger 将它合并到统一的segment 中去。
该类可接收多个添加的文档,并且直接写成一个单独的segment 文件。这比为每一个文档创建一个segment(使用DocumentWriter)以及对那些segments 执行合作处理更有效率。
每一个添加的文档都被传递给DocConsumer类,它处理该文档并且与索引链表中(indexing chain)其它的consumers相互发生作用(interacts with)。确定的consumers,就像StoredFieldWriter和TermVectorsTermsWriter,提取一个文档的摘要(digest),并且马上把字节写入“文档存储”文件(比如它们不为每一个文档消耗(consume)内存RAM,除了当它们正在处理文档的时候)。
其它的consumers,比如FreqProxTermsWriter和NormsWriter,会缓存字节在内存中,只有当一个新的segment制造出的时候才会flush到磁盘中。
一旦使用完我们分配的RAM缓存,或者已添加的文档数目足够多的时候(这时候是根据添加的文档数目而不是RAM的使用率来确定是否flush),我们将创建一个真实的segment,并将它写入Directory中去。
索引创建的调用过程:
一个Directory对象是一系列统一的文件列表(a flat list of files)。文件可以在它们被创建的时候一次写入,一旦文件被创建,它再次打开后只能用于读取(read)或者删除(delete) *** 作。并且同时在读取和写入的时候允许随机访问。
FSDirectory类直接实现Directory抽象类为一个包含文件的目录。目录锁的实现使用缺省的SimpleFSLockFactory,但是可以通过两种方式修改,即给getLockFactory()传入一个LockFactory实例,或者通过调用setLockFactory()方法明确制定LockFactory类。
目录将被缓存(cache)起来,对一个指定的符合规定的路径(canonical path)来说,同样的FSDirectory实例通常通过getDirectory()方法返回。这使得同步机制(synchronization)能对目录起作用。
RAMDirectory类是一个驻留内存的(memory-resident)Directory抽象类的实现。目录锁的实现使用缺省的SingleInstanceLockFactory,但是可以通过setLockFactory()方法修改。
IndexInput类是一个为了从一个目录(Directory)中读取文件的抽象基类,是一个随机访问(random-access)的输入流(input stream),用于所有Lucene读取Index的 *** 作。BufferedIndexInput是一个实现了带缓冲的IndexInput的基础实现。
IndexOutput类是一个为了写入文件到一个目录(Directory)中的抽象基类,是一个随机访问(random-access)的输出流(output stream),用于所有Lucene写入Index的 *** 作。BufferedIndexOutput是一个实现了带缓冲的IndexOutput的基础实现。RAMOuputStream是一个内存驻留(memory-resident)的IndexOutput的实现类。
域索引选项通过倒排索引来控制文本是否可被搜索。
当lucene建立起倒排索引后,默认情况下它会保存所有必要的信息以实施Vector Space Model。该Model需要计算文档中出现的Term数,以及它们出现的文职(这是必要的,比如通过词组搜索时用到)。但有时候这些域只是在布尔搜索时用到,他们并不为相关评分做贡献,一个常见的例子是,域只是被用作过滤,如权限过滤和日期过滤。在这种情况下,可以通过调用Field.setOmitTermFreqAndPositions(true)方法让lucene跳过对改选项的出现频率和出现位置的索引。该方法可以节省一些索引在磁盘上的储存空间,还可以加速搜索和过滤过程,但会悄悄阻止需要位置信息的搜索,如阻止PhraseQuery和SpanQuery类的运行。
域存储选项是用来确定是否需要存储域的真实值,以便后续搜索时能回复这个值。
lucene支持想一个域中写入多个不同的值。
这种处理方式是完全可以接受并鼓励使用的,因为这是逻辑上具有多个域值的域的自然表示方式。在lucene内部,只要文档中出现同名的多域值,倒排索引和项向量都会在逻辑上将这些语汇单元附加进去,具体顺序由添加该域的顺序决定。
文档和域的加权 *** 作可以在索引期间完成。而搜索期间的加权 *** 作会更加动态化,因为每次搜索都可以根据不同的加权因子独立选择加权或不加权,但这个策略也可能多消耗一些CPU效率。搜索期间的动态加权可以更灵活控制。
默认情况下,所有文档的加权因子都是1.0,通过改变文档的加权因子,就可以影响文档在索引中的重要程度。调整加权 *** 作的API为:setBoost(float);
同文档加权一样,可以对进行加权 *** 作。文档加权时,lucene内部会采用同一加权因子来对该文档中的域进行加权。域加权API:Field.setBoost(fliat)。
Analyzer类构建用于分析文本的TokenStream对象,因此(thus)它表示(represent)用于从文本中分解(extract)出组成索引的terms的一个规则器(policy)。典型的(typical)实现首先创建一个Tokenizer,它将那些从Reader对象中读取字符流(stream of characters)打碎为(break into)原始的Tokens(raw Tokens)。然后一个或更多的TokenFilters可以应用在这个Tokenizer的输出上。警告:你必须在你的子类(subclass)中覆写(override)定义在这个类中的其中一个方法,否则的话Analyzer将会进入一个无限循环(infinite loop)中。
StandardAnalyzer:
StandardAnalyzer类是使用一个English的stop words列表来进行tokenize分解出文本中word,使用StandardTokenizer类分解词,再加上StandardFilter以及LowerCaseFilter以及StopFilter这些过滤器进行处理的这样一个Analyzer类的实现。
欢迎分享,转载请注明来源:内存溢出
评论列表(0条)