數據庫檢索效率時,通常首要優化途徑是從索引入手,而後根據需求再考慮更復雜的負載均衡、讀寫分離和分佈式水平/垂直分庫/表等手段;索引經過信息冗餘來提升檢索效率,其以空間換時間並會下降數據寫入的效率,所以對索引字段的選擇很是重要。java
Neo4j可對指定Label的Node Create Index,當新增/更新符合條件的Node屬性時,Index會自動更新。Neo4j Index默認採用Lucene實現(可定製,如Spatial Index自定義實現的RTree索引),但默認新建的索引只支持精確匹配(get),模糊查詢(query)的話須要以全文索引,控制Lucene後臺的分詞行爲。 node
Neo4j全文索引默認的分詞器是針對西方語種的,如默認的exact查詢採用的是lucene KeywordAnalyzer(關鍵詞分詞器),fulltext查詢採用的是 white-space tokenizer(空格分詞器),大小寫什麼的對中文沒啥意義;因此針對中文分詞須要掛一箇中文分詞器,如IK Analyzer,Ansj,至於相似梁廠長家的基於深度學習的分詞系統pullword,那就更厲害啦。 git
本文以經常使用的IK Analyzer分詞器爲例,介紹如何在Neo4j中對字段新建全文索引實現模糊查詢。github
IKAnalyzer是一個開源的,基於java語言開發的輕量級的中文分詞工具包。算法
採用了特有的「正向迭代最細粒度切分算法「,支持細粒度和最大詞長兩種切分模式;具備83萬字/秒(1600KB/S)的高速處理能力。spring
採用了多子處理器分析模式,支持:英文字母、數字、中文詞彙等分詞處理,兼容韓文、日文字符優化的詞典存儲,更小的內存佔用。支持用戶詞典擴展定義sql
針對Lucene全文檢索優化的查詢分析器IKQueryParser(做者吐血推薦);引入簡單搜索表達式,採用歧義分析算法優化查詢關鍵字的搜索排列組合,能極大的提升Lucene檢索的命中率。
IK Analyser目前尚未maven庫,還得本身手動下載install到本地庫,下次空了本身在github作一個maven私有庫,上傳這些maven central庫裏面沒有的工具包。數據庫
自定義詞典後綴名爲.dic的詞典文件,必須使用無BOM的UTF-8編碼保存的文件。app
詞典和IKAnalyzer.cfg.xml配置文件的路徑問題,IKAnalyzer.cfg.xml必須在src根目錄下。詞典能夠任意放,可是在IKAnalyzer.cfg.xml裏要配置對。以下這種配置,ext.dic和stopword.dic應當在同一目錄下。負載均衡
<?xml version="1.0" encoding="UTF-8"?> <!DOCTYPE properties SYSTEM "http://java.sun.com/dtd/properties.dtd"> <properties> <comment>IK Analyzer 擴展配置</comment> <!--用戶能夠在這裏配置本身的擴展字典 --> <entry key="ext_dict">/ext.dic;</entry> <!--用戶能夠在這裏配置本身的擴展中止詞字典--> <entry key="ext_stopwords">/stopword.dic</entry> </properties>
指定IKAnalyzer做爲luncene分詞的analyzer,並對全部Node的指定屬性新建全文索引
@Override public void createAddressNodeFullTextIndex () { try (Transaction tx = graphDBService.beginTx()) { IndexManager index = graphDBService.index(); Index<Node> addressNodeFullTextIndex = index.forNodes( "addressNodeFullTextIndex", MapUtil.stringMap(IndexManager.PROVIDER, "lucene", "analyzer", IKAnalyzer.class.getName())); ResourceIterator<Node> nodes = graphDBService.findNodes(DynamicLabel.label( "AddressNode")); while (nodes.hasNext()) { Node node = nodes.next(); //對text字段新建全文索引 Object text = node.getProperty( "text", null); addressNodeFullTextIndex.add(node, "text", text); } tx.success(); } }
對關鍵詞(如'有限公司'),多關鍵詞模糊查詢(如'蘇州 教育 公司')默認都能檢索,且檢索結果按關聯度已排好序。
package uadb.tr.neodao.test; import org.junit.Test; import org.junit.runner.RunWith; import org.neo4j.graphdb.GraphDatabaseService; import org.neo4j.graphdb.Node; import org.neo4j.graphdb.Transaction; import org.neo4j.graphdb.index.Index; import org.neo4j.graphdb.index.IndexHits; import org.neo4j.graphdb.index.IndexManager; import org.neo4j.helpers.collection.MapUtil; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.test.context.ContextConfiguration; import org.springframework.test.context.junit4.SpringJUnit4ClassRunner; import org.wltea.analyzer.lucene.IKAnalyzer; import com.lt.uadb.tr.entity.adtree.AddressNode; import com.lt.util.serialize.JsonUtil; /** * AddressNodeNeoDaoTest * * @author geosmart */ @RunWith(SpringJUnit4ClassRunner. class) @ContextConfiguration(locations = { "classpath:app.neo4j.cfg.xml" }) public class AddressNodeNeoDaoTest { @Autowired GraphDatabaseService graphDBService; @Test public void test_selectAddressNodeByFullTextIndex() { try (Transaction tx = graphDBService.beginTx()) { IndexManager index = graphDBService.index(); Index<Node> addressNodeFullTextIndex = index.forNodes("addressNodeFullTextIndex" , MapUtil. stringMap(IndexManager.PROVIDER, "lucene", "analyzer" , IKAnalyzer.class.getName())); IndexHits<Node> foundNodes = addressNodeFullTextIndex.query("text" , "蘇州 教育 公司" ); for (Node node : foundNodes) { AddressNode entity = JsonUtil.ConvertMap2POJO(node.getAllProperties(), AddressNode. class, false, true); System. out.println(entity.getAll地址實全稱()); } tx.success(); } } }
profile match (a:AddressNode{ruleabbr:'TOW',text:'惟亭鎮'})<-[r:BELONGTO]-(b:AddressNode{ruleabbr:'STR'}) where b.text=~ '金陵.*' return a,b
profile START b=node:addressNodeFullTextIndex("text:金陵*") match (a:AddressNode{ruleabbr:'TOW',text:'惟亭鎮'})<-[r:BELONGTO]-(b:AddressNode) where b.ruleabbr='STR' return a,b
對label爲AddressNode的節點,根據節點屬性ruleabbr的分類addressnode_fulltext_index(省->市->區縣->鄉鎮街道->街路巷/物業小區)/addressnode_exact_index(門牌號->樓幢號->單元號->層號->戶室號),對屬性text分別建不一樣類型的索引
profile START a=node:addressnode_fulltext_index("text:商業街"),b=node:addressnode_exact_index("text:二期19") match (a:AddressNode{ruleabbr:'STR'})-[r:BELONGTO]-(b:AddressNode{ruleabbr:'TAB'}) return a,b limit 10