neo4j圖資料庫的cypher調優
neo4j圖資料庫介紹
neo4j是目前排名最高的圖資料庫,分為商業和社群版本,社群版只支援單機,而且查詢的執行時(runtime)不同(cypher runtime:interpreted(社群版),slotted(企業版)).
neo4j資料庫中只有3個概念: Node, Relationship, Properties. Node表示實體類別,使用Label區分,例如一個節點可以有Person/Father等多個標籤,Relationship即關係,僱傭關係,父子關係,投資關係,交易關係等. Node和Relationship都可以有Proerties,屬性自身不分是屬於節點還是屬於關係,例如Person可以有屬性name,關係也可以用屬性name.你可以在neo4j browser左側看到當前資料庫的所有Node Label,Relationship Type,Properties.
本地安裝和線上沙箱
neo4j背後的公司為了吸引使用者,提供了一些好玩的資料庫沙箱,這些沙箱資料庫已經提前放了一些主題資料,例如購物資料,國會關係資料.你可以通過註冊登入
https://
neo4j.com/sandbox-v2/
, 選擇一個數據沙箱例項進行學習試玩.當然你也可以下載社群版,命令列neo4j.bat console
啟動,開啟127.0.0.1:7474開始學習.
一個neo4j支援多個數據庫但是一次只能啟用一個數據庫,一個數據庫所有檔案都在$neo4j_home\data\databases目錄的獨立資料夾,在conf/neo4j.conf的dbms.active_database=graph.db
指定啟用那個資料庫.
cypher查詢語言
neo4j使用cypher語言作為查詢語言.這是一種模式匹配的宣告式語言.基本語法和SQL相似. cypher中常用的子句(clause)有: MATCH,RETURN,WITH,WHERE,UNWIND,LIMIT,UNION,SKIP,SET. RETURN,LIMIT WHERE和SQL中是一樣的,UNWIND這些需要用到再檢視文件,這裡介紹MATCH和WITH.MTACH
用於指定搜尋的模式.例如希望找到'Tom Hanks'在2018演過的所有電影:MATCH (p:ACTOR {name:'Tom Hanks' }) -[r: ACT_IN]->(m:MOVIE {time: '2018'})
,這是一個模式,可以直接REUREN返回p,r,m等變數.可以看到模式中的節點(Node Label)使用()
;關係型別(Relationship Type)使用[]
指定,如果不關心type,那麼[]可以省略.使用--; 屬性(Properties) 使用{pname: pvalue}指定.WITH
的作用和python的with非常相似(實際上cypher語言借鑑了python的list處理語法),用於修改一些變數,變數一般都是上一個子句的查詢結果,修改之後傳給下一個子句.例如下面的語句找到和Anders有關係的人的年齡最大的那個人,返回那個人的所有認識的人的名字.
MATCH (n { name: 'Anders' })--(m) WITH m ORDER BY m.age DESC LIMIT 1 MATCH (m)--(o) RETURN o.name
cypher手冊: https:// neo4j.com/docs/cypher-m anual/3.5/clauses/
cypher的操作符
如果需要進行cypher調優,有必要了解一下cypher的操作符. 一般程式語言的程式碼在被執行前都會被編譯得到抽象語法樹(AST). 例如Java程式碼,一個Java檔案會被抽象為一個package,class, method,variable declare等不同部分得到一個Class物件. cypher語句一樣會被編譯得到一棵語法樹(AST),每個樹節點是一個操作符. 從葉節點的操作符開始執行,得到的結果依次返回給父節點進一步處理.常見的操作符有:AllNodesScan(全域性掃描,只能作為葉節點),NodeByLabelScan,Apply等.例如MATCH (n) return n
會得到一個AllNodesScan
和ProduceResults
操作符構成的AST, 你可以通過PROFILE
檢視你語句編譯後得到的操作符構成的執行計劃.
# 執行語句得到下面的表格 PROFILE MATCH (p:Person { name: 'Tom Hanks' }) RETURN p # 省略了部分列 +-----------------+----------------+------+---------+-----------------+ | Operator| Estimated Rows | Rows | DB Hits | Page Cache Hits | +-----------------+----------------+------+---------+-----------------+ | +ProduceResults |1 |1 |0 |0 | | |+----------------+------+---------+-----------------+ | +NodeIndexSeek|1 |1 |2 |0 | +-----------------+----------------+------+---------+-----------------+
neo4j browser介紹
和大多數資料庫一樣,neo4j是server-client的資料庫,支援http和bolt 2種協議.neo4j自帶一個基於瀏覽器的客戶端,只需在瀏覽器輸入server_ip:7474即可使用. neo4j browser自帶一個教程和電影關係的資料庫初始化指令碼.方便你可以學習.下面介紹幾個常用的命令. >:helphelp命令顯示各種幫助提示. 常見的topic有 :help cypher :help commands :help keys :help param - :play 互動式學習命令. 例如,:play movie graph 進入基於電影資料庫的教程. - :param 命令,設定變數.:param usrname => "xiongdahu",注意,變數名和=>之間有空格.設定變數之後可以使用變數MATCH (n:Person) WHERE n.name = $usrname
- :params 顯示當前已經設定的所有變數.也可以使用:params {name: 'Stella', age: 24} 覆蓋目前的變數. 但是這個命令沒用型別安全.
cypher調優
cypher是一種宣告式的,模式匹配的查詢語言.模式在cypher語言中非常重要.如何合理地設計查詢中的模式是cypher效能可調優空間最大的地方.下面給出常見的優化建議. 需要說明的是,後面的這些建議其實大都可以在cypher手冊找到,如果感興趣,建議通讀這份長文件...
避免全域性scan
cypher 是一種模式匹配的語言,預設會進行全域性掃描,除非你告訴它不要.所以起始節點的label非常重要.起始的模式匹配基數大小也非常重要.
快取和硬碟IO
neo4j資料庫將資料檔案和Page Cache作了對映,如果在快取中沒有查詢到,neo4j會從硬碟載入資料檔案.第二次查詢就可以走快取.所以需要充分利用Page Cache.記住第一次查詢總是會比較慢,因為沒用快取.neo4j 有2級快取:string cache和AST cache + string cache 預設neo4j在cache中保留1000個查詢計劃,可在conf/neo4j.conf
中引數dbms.query_cache_size
修改這個設定.
需要注意的是cache是根據語句的string hash值判斷的,所以一樣的語句僅僅是大小寫不一樣或者空白符不一樣對快取來說也是2個語句.
PROFILE/EXPLAIN
語句只會cache其去掉PROFILE/EXPLAIN
之後的部分.例如:MATCH (n) return COUNT(n);
和PROFILE MATCH (n) return COUNT(n);
的cache是一致的.
-
AST cache 程式語言都有語法樹.如果在string cache中沒有找到快取.那麼會將查詢正規化,得到語法樹並將其快取.正規化的同時也會做一些優化,例如
match (n:Person {id:101}) return n;
在正規化之後得到match (n:Person) where n.id={param1} return n;{param1: 101}
,AST cache不區分大小寫,空格等,所以以下查詢是一致的:
match (n:Person) where n.id=101 return n; match (n:Person {id:101}) return n; MATCH ( n:Person { id : 101 } ) RETURN n;
execution plan
當cypher引擎收到查詢語句後如果沒用找到對應的快取,那麼Cypher query planner會將語句規範化,優化後編譯得到一個執行計劃(execution plan).這個執行計劃會快取一切且可以複用. 當查詢快取過多,或者資料庫的資料變化大時(設定引數是)這個執行計劃則失效被移除.在查詢中使用引數而不是字面量值,可以提高一個執行計劃的複用率. 更多資訊參考文件: https:// neo4j.com/docs/cypher-m anual/3.5/execution-plans/#execution-plan-introduction
檢視查詢計劃
如果想要檢視查詢語句的執行計劃,可以在查詢語句前加上EXPLANIN
ORPROFILE
關鍵字, 你可以在neo4j browser檢視query plan找到效能瓶頸.結果左側邊裡面第3個tab會給出詳細的效能警告(warn).EXPLAIN
只會給出語句的分析結果;而PROFILE
則會執行你的查詢語句把給出耗時最多的報告,以及每個操作符返回了多少行記錄. 注意,profiling會消耗很多資源,所以不要在生產環境中頻繁使用.調優的基礎是基於cypher的操作符,所以需要你對操作符有基本的瞭解.
索引
資料庫離不開索引.這裡有個小陷阱,最早譜系的節點是企業客戶(label: COR_CUSTOMER)+和幾十個零售客戶節點(label:RTL_CUSTOMER),我在查詢語句起始節點沒有指定label,沒用遇到效能問題,後來加入了3百萬的個人節點資料後,原來1s的查詢變成了1分半鐘. 所以在干擾的label比較少時,你不會察覺到效能問題.務必在起始節點指定label,即使目前只有一個label,最好也提前加上.
然而,cypher語句目前不允許在一個節點指定多個label,例如你希望起點label是COR_CUSTOMER|RTL_CUSTOMER
,這個是不允許的. 只能在where語句指定.
MATCH n WHERE n:COR_CUSTOMER OR n:RTL_CUSTOMER RETURN n
在3.0之前的neo4j中使用上面的語句,會導致一個AllNodesScan,在3.0之後,該語句則是將2個NodeByLabelScan匹配結果UNION
然後DISTINCT
的結果. 所以是搜尋2次再合併結果.你可以在上面的cypher語句前面新增EXPLAIN
檢視執行計劃,已確定你的語句是否會導致全域性掃描.SO上關於多個label匹配的討論
大結果集
如果你的查詢返回結果集太大,例如幾M大小,那麼你可能需要考慮你的設計了. 過大的結果集會導致查詢返回變慢,要注意,這些結果會佔用你的快取空間,而如果在網路情況不好時,情況家更加糟糕了.
目前譜系對這一塊並沒有優化,最大的譜系的返回介面可能達到1M多,加上ES的資料,前端接收資料會有4M多.
鎖
當你修改節點的資訊時,節點會被鎖定;如果修改關係,關係會被鎖定;如果增加/刪除關係,那麼2個節點和這個關係都會被鎖定.而如果此時有節點/關係的相關查詢請求,這些請求會等待.所以,如果你需要將50個節點加入一個組(group)--即新增50個關係,如果你呼叫50次方法,那麼這個group節點被lock的時間較長,此時可以通過UNWIND
和列表(list)引數處理這個問題.
MATCH (g:Group { uuid: $groupUuid }) UNWIND $personUuidList as personUuid MATCH (p:Person { uuid : personUuid }) MERGE (p)-[:IS_MEMBER]->(g)
常見查詢錯誤
- 變數名
- label忘記新增冒號,例如MATCH (Person) 和 MATCH (:Person) 是完全不一樣的,前者Person是變數,不走索引.
- 有過大的中間結果集,優化你的語句時思考:儘早distinct,儘早limit,使用collect減少結果的行數,在正確地方使用order by;
多個UNWIND語句導致笛卡爾積
多個UNWIND會導致一個笛卡爾積的結果,這個結果可能會很大.例如下面的結果會得到3*3=9行,所以儘量避免笛卡爾積.
with ['a','b','c'] as lts, [1,2,3] as nrs unwind lts as char unwind nrs as nr return char,nr
在MATCH中使用多個模式笛卡爾積
在MATCH中使用多個模式也會導致笛卡爾積,比較下面的2個結果相同的語句,第一個耗時80s,第二個只需8ms.
#1. 笛卡爾積 80000 ms w/ ~900 players, ~40 teams, ~1200 games MATCH (pl:Player),(t:Team),(g:Game) RETURN COUNT(DISTINCT pl), COUNT(DISTINCT t), COUNT(DISTINCT g) # 2. 8ms w/~900 players, ~40 teams, ~1200 games MATCH (pl:Player) WITH COUNT(pl) as players MATCH (t:Team) WITH COUNT(t) as teams, players MATCH (g:Game) RETURN COUNT(g) as games, teams, players
模式中的方向
下面的查詢中,如果給關係ACTED_IN新增上方向,可以提高查詢速度.
MATCH (p:Person)-[:ACTED_IN]-(m) WHERE p.name = "Tom Hanks" RETURN m