萬盛學電腦網

 萬盛學電腦網 >> 數據庫 >> mysql教程 >> Solr全文搜索與MySQL查詢性能比較

Solr全文搜索與MySQL查詢性能比較

SolrSolr是一個高性能,采用Java5開發,基於Lucene的全文搜索服務器。同時對其進行了擴展,提供了比Lucene更為豐富的查詢語言,同時實現了可配置、可擴展並對查詢性能進行了優化,並且提供了一個完善的功能管理界面,是一款非常優秀的全文搜索引擎。

測試數據量:10407608
Num Docs: 10407608

在項目中一個最常用的查詢,查詢某段時間內的數據,SQL查詢獲取數據,30s左右

SELECT * FROM `tf_hotspotdata_copy_test` WHERE collectTime BETWEEN '2014-12-06 00:00:00' AND '2014-12-10 21:31:55';

對collectTime建立索引後,同樣的查詢,2s,快了很多。

Solr索引



Solr查詢,同樣的條件,72ms

"status": 0,

    "QTime": 72,

    "params": {

      "indent": "true",

      "q": "CollectTime:[2014-12-06T00:00:00.000Z TO 2014-12-10T21:31:55.000Z]",

      "_": "1434617215202",

      "wt": "json"

    }

好吧,查詢性能提高的不是一點點,用Solrj代碼試試:


SolrQuery params = new SolrQuery();
params.set("q", timeQueryString);
params.set("fq", queryString);
params.set("start", 0); 
params.set("rows", Integer.MAX_VALUE);
params.setFields(retKeys);
QueryResponse response = server.query(params);



Solrj查詢並獲取結果集,結果集大小為220296,返回5個字段,時間為12s左右。

為什麼需要這麼長時間?上面的"QTime"只是根據索引查詢的時間,如果要從solr服務端獲取查詢到的結果集,solr需要讀取stored的字段(磁盤IO),再經過Http傳輸到本地(網絡IO),這兩者比較耗時,特別是磁盤IO。

時間對比:

查詢條件  時間
MySQL(無索引) 30s
MySQL(有索引) 2s
Solrj(select查詢) 12s

如何優化?看看只獲取ID需要的時間:

SQL查詢只返回id,沒有對collectTime建索引,10s左右

SELECT id FROM `tf_hotspotdata_copy_test` WHERE collectTime BETWEEN '2014-12-06 00:00:00' AND '2014-12-10 21:31:55';

SQL查詢只返回id,同樣的查詢條件,對collectTime建索引,0.337s,很快。

Solrj查詢只返回id,7s左右,快了一點。

    id Size: 220296

    Time: 7340

時間對比:

查詢條件(只獲取ID) 時間
MySQL(無索引) 10s
MySQL(有索引) 0.337s
Solrj(select查詢) 7s

繼續優化。。

關於Solrj獲取大量結果集速度慢的一些類似問題:

http://stackoverflow.com/questions/28181821/solr-performance#

http://grokbase.com/t/lucene/solr-user/11aysnde25/query-time-help

http://lucene.472066.n3.nabble.com/Solrj-performance-bottleneck-td2682797.html

這個問題沒有好的解決方式,基本的建議都是做分頁,但是我們需要拿到大量數據做一些比對分析,做分頁沒有意義。

偶然看到一個回答,solr默認的查詢使用的是"/select" request handler,可以用"/export" request handler來export結果集,看看solr對它的說明:

It's possible to export fully sorted result sets using a special rank query parser and response writer  specifically designed to work together to handle scenarios that involve sorting and exporting millions of records. This uses a stream sorting techniquethat begins to send records within milliseconds and continues to stream results until the entire result set has been sorted and exported.

Solr中已經定義了這個requestHandler:


      {!xport}    xsort    false        query




使用/export需要字段使用docValues建立索引:



使用docValues必須要有一個用來Sort的字段,且只支持下列類型:

Sort fields must be one of the following types: int,float,long,double,string

docValues支持的返回字段:

Export fields must either be one of the following types: int,float,long,double,string

使用Solrj來查詢並獲取數據:


        SolrQuery params = new SolrQuery();
        params.set("q", timeQueryString);
        params.set("fq", queryString);
        params.set("start", 0);
        params.set("rows", Integer.MAX_VALUE);
        params.set("sort", "id asc");
        params.setHighlight(false);
        params.set("qt", "/export");
        params.setFields(retKeys);
        QueryResponse response = server.query(params);




一個Bug:

org.apache.solr.client.solrj.impl.HttpSolrClient$RemoteSolrException: Error from server at http://192.8.125.30:8985/solr/hotspot: Expected mime type application/octet-stream but got application/json.

Solrj沒法正確解析出結果集,看了下源碼,原因是Solr server返回的ContentType和Solrj解析時檢查時不一致,Solrj的BinaryResponseParser這個CONTENT_TYPE是定死的:

public class BinaryResponseParser extends ResponseParser {
    public static final String BINARY_CONTENT_TYPE = "application/octet-stream";

一時半會也不知道怎麼解決這個Bug,還是自己寫個Http請求並獲取結果吧,用HttpClient寫了個簡單的客戶端請求並解析json獲取數據,測試速度:

    String url = "http://192.8.125.30:8985/solr/hotspot/export?q=CollectTime%3A[2014-12-06T00%3A00%3A00.000Z+TO+2014-12-10T21%3A31%3A55.000Z]&sort=id+asc&fl=id&wt=json&indent=true";
    long s = System.currentTimeMillis();
    SolrHttpJsonClient client = new SolrHttpJsonClient();
    SolrQueryResult result = client.getQueryResultByGet(url);
    System.out.println("Size: "+result.getResponse().getNumFound());
    long e = System.currentTimeMillis();
    System.out.println("Time: "+(e-s));




同樣的查詢條件獲取220296個結果集,時間為2s左右,這樣的查詢獲取數據的效率和MySQL建立索引後的效果差不多,暫時可以接受。

因為Export fields只支持int,float,long,double,string這幾個類型,如果你的查詢結果只包含這幾個類型的字段,那采用這種方式查詢並獲取數據,速度要快很多。

下面是Solr使用“/select”和“/export”的速度對比。

時間對比:

查詢條件   時間
MySQL(無索引) 30s
MySQL(有索引) 2s
Solrj(select查詢) 12s
Solrj(export查詢) 2s

項目中如果用分頁查詢,就用select方式,如果一次性要獲取大量查詢數據就用export方式,這裡沒有采用MySQL對查詢字段建索引,因為數據量每天還在增加,當達到億級的數據量的時候,索引也不能很好的解決問題,而且項目中還有其他的查詢需求。

我們來看另一個查詢需求,假設要統計每個設備(deviceID)上數據的分布情況:

用SQL,需要33s:

SELECT deviceID,Count(*) FROM `tf_hotspotdata_copy_test` GROUP BY deviceID;

同樣的查詢,在對CollectTime建立索引之後,只要14s了。

看看Solr的Facet查詢,只要540ms,快的不是一點點。

SolrQuery query = new SolrQuery();
query.set("q", "*:*");
query.setFacet(true);
query.addFacetField("DeviceID");
QueryResponse response = server.query(query);
FacetField idFacetField = response.getFacetField("DeviceID");
List idCounts = idFacetField.getValues();
for (Count count : idCounts) {
    System.out.println(count.getName()+": "+count.getCount());
}



時間對比:

查詢條件(統計)  時間
MySQL(無索引)33s
MySQL(有索引)14s
Solrj(Facet查詢)0.54s

如果我們要查詢某台設備在某個時間段上按“時”、“周”、“月”、“年”進行數據統計,Solr也是很方便的,比如以下按天統計設備號為1013上的數據:


    String startTime = "2014-12-06 00:00:00";
    String endTime = "2014-12-16 21:31:55";   
    SolrQuery query = new SolrQuery();
    query.set("q", "DeviceID:1013");
    query.setFacet(true);
    Date start = DateFormatHelper.ToSolrSearchDate(DateFormatHelper.StringToDate(startTime));
    Date end = DateFormatHelper.ToSolrSearchDate(DateFormatHelper.StringToDate(endTime));
    query.addDateRangeFacet("CollectTime", start, end, "+1DAY");
    QueryResponse response = server.query(query);
    List dateFacetFields = response.getFacetRanges();
    for (RangeFacet facetField : dateFacetFields{
        List dateCounts= facetField.getCounts();
        for (org.apache.solr.client.solrj.response.RangeFacet.Count count : dateCounts) {
            System.out.println(count.getValue()+": "+count.getCount());
        }
    }




2014-12-06T00:00:00Z: 58

2014-12-07T00:00:00Z: 0

2014-12-08T00:00:00Z: 0

2014-12-09T00:00:00Z: 0

2014-12-10T00:00:00Z: 3707

2014-12-11T00:00:00Z: 8384

2014-12-12T00:00:00Z: 7803

2014-12-13T00:00:00Z: 2469

2014-12-14T00:00:00Z: 142

2014-12-15T00:00:00Z: 34

2014-12-16T00:00:00Z: 0

Time: 662

 

水平拆分表:

由於本系統采集到的大量數據和“時間”有很大關系,一些業務需求根據“時間”來查詢也比較多,可以按“時間”字段進行拆分表,比如按每月一張表來拆分,但是這樣做應用層代碼就需要做更多的事情,一些跨表的查詢也需要更多的工作。綜合考慮了表拆分和使用Solr來做索引查詢的工作量後,還是采用了Solr。


總結:在MySQL的基礎上,配合Lucene、Solr、ElasticSearch等搜索引擎,可以提高類似全文檢索、分類統計等查詢性能。







Solr與Sphinx的比較

Solr版本:4.8.1
Coreseek版本:3.2.14 (基於Sphinx 0.9.9 release)

1.索引效率
     Sphinx:10-15MB/秒, 實測最高可對100GB的文本建立索引,單一索引可包含1億條記錄
    Solr:10MB/秒,億條數據,20G索引,新浪目前也采用lucene檢索
2.搜索性能
    Sphinx:高性能搜索,在2-4GB的文本數據上,平均每次檢索響應時間小於0.1秒;在1.2G文本,100萬條文檔上進行搜索,支持高達每秒150~250次查詢;
    Solr:高性能搜索,8G的索引文件,檢索響應時間為150ms。高峰支持500並發/秒。
3.擴展性
    Sphinx:高擴展性
    Solr:高擴展性
4.相關度算法
    Sphinx:基於短語相似度和統計(BM25)的復合Ranking方法,支持動態計算得分
    Solr:支持動態計算得分,也支持索引時計算得分
5.是否支持短語搜索
    支持
    支持
6.是否支持分布式搜索
    支持
    支持
7.是否支持Mysql引擎
    支持
    支持
8.是否支持多個字段全文檢索
    支持
    支持
9.數據交互
    http請求,Xml、Json
    http請求,Xml、Json
10.數據源
     Sphinx:mysql, pgsql, mssql, xmlpipe , xmlpipe2, odbc,python
    Solr:mysql,Xml,json,csv,odbc
11.多索引
    支持
    支持
12.多核(不同去多索引)
    Sphinx:不支持
    Solr:支持

13. 中文分詞的支持比較
          sphinx目前只支持mmseg3,sphinx for chinese兩種分詞,目前大家使用的比較多的是mmseg3。mmseg3的詞庫需要預先編譯,不利於詞庫的擴充。
          Solr目前支持的詞庫比較多,目前支持的有庖丁,IK,mmsegj4,通過比較分詞效果,IK在分詞的查全和查准的效果上更好一些。IK分詞支持設置停用詞和擴展詞庫,擴展詞庫每個詞一行的方式進行擴展。
14. 從Mysql數據庫索引數據的支持
        sphinx可以通過配置數據源的方式直接從mysql中索引數據,在數據量比較大的時候,可以設置主索引+增量索引的方式來同步數據。但是主索引需要是根據Mysql表的主鍵Id來設置索引范圍,比如Id小於9000,000,Id大於9000,000的通過增量索引來同步。這種只適應Id小於9000,000的數據更新頻繁的情況。
        如果主索引和增量索引是以記錄的最後更新時間來區分的話,由於主索引和增量索引使用的是2個不同的索引文件,這就會造成主索引和增量索引中的數據不一致,導致檢索時出現本不該出現的記錄。這種情況就需要把增量索引合並到主索引中,如果主索引的數據比較大,不確定合並的時間需要多少。
        solr索引mysql數據時,可以配置成主索引+增量索引,主索引和增量索引之間以更新的時間戳為分割線,solr會記錄每次更新的時間戳。由於solr的增量索引和增量索引使用的都是一個索引文件,所以在執行增量索引時會自動合並到最終的索引中。
15. 實時檢索的支持
        sphinx本身不支持中文分詞,Coreseek是現在用的最多的sphinx中文全文檢索,它提供了為Sphinx設計的中文分詞包LibMMSeg。Coreseek目前穩定版是3.2.14(基於Sphinx 0.9.9 release開發),該版本還不支持實時檢索。目前Coresekk4.1還是測試版,測試版本支持實時檢索,但是不太穩定。
        solr的索引支持實時新增、更新和刪除,可以根據記錄的最後更新時間實現增量更新,在增量更新數據不多的情況下,可以設置增量更新任務為10秒更新一次。從而達到數據的實時同步。

copyright © 萬盛學電腦網 all rights reserved