您現在的位置: 365建站網 > 365學習 > Solr與MySQL查詢性能對比測試

Solr與MySQL查詢性能對比測試

文章來源:365jz.com     點擊數:238    更新時間:2019-06-23 01:19   參與評論

測試環境

本文簡單對比下Solr與MySQL的查詢性能速度。

測試數據量:10407608     Num Docs: 10407608

普通查詢

這里對MySQL的查詢時間都包含了從MySQL Server獲取數據的時間。

在項目中一個最常用的查詢,查詢某段時間內的數據,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索引數據:

<!--Index Field for HotSpot--><field name="CollectTime" type="tdate" indexed="true" stored="true"/><field name="IMSI" type="string" indexed="true" stored="true"/><field name="IMEI" type="string" indexed="true" stored="true"/><field name="DeviceID" type="string" indexed="true" stored="true"/>

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獲取大量結果集速度慢的問題,沒有好的解決方式,基本的建議都是做分頁,但是我們需要拿到大量數據做一些比對分析,做分頁沒有意義。

偶然看到一個回答,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: 


<requestHandler name="/export" class="solr.SearchHandler">
  <lst name="invariants">
    <str name="rq">{!xport}</str>
    <str name="wt">xsort</str>
    <str name="distrib">false</str>
  </lst>
  <arr name="components">
    <str>query</str>
  </arr></requestHandler>


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

<field name="id" type="string" indexed="true" stored="true" required="true" multiValued="false" docValues="true"/><field name="CollectTime" type="tdate" indexed="true" stored="true" docValues="true"/><field name="IMSI" type="string" indexed="true" stored="true" docValues="true"/><field name="IMEI" type="string" indexed="true" stored="true" docValues="true"/><field name="DeviceID" type="string" indexed="true" stored="true" docValues="true"/>

使用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建立索引后的效果差不多,暫時可以接受。

為什么使用docValues的方式獲取數據速度快?

DocValues是一種按列組織的存儲格式,這種存儲方式降低了隨機讀的成本。

傳統的按行存儲是這樣的:

 

1和2代表的是docid。顏色代表的是不同的字段。

改成按列存儲是這樣的:

按列存儲的話會把一個文件分成多個文件,每個列一個。對于每個文件,都是按照docid排序的。這樣一來,只要知道docid,就可以計算出這個docid在這個文件里的偏移量。也就是對于每個docid需要一次隨機讀操作。

那么這種排列是如何讓隨機讀更快的呢?秘密在于Lucene底層讀取文件的方式是基于memory mapped byte buffer的,也就是mmap。這種文件訪問的方式是由操作系統去緩存這個文件到內存里。這樣在內存足夠的情況下,訪問文件就相當于訪問內存。那么隨機讀操作也就不再是磁盤操作了,而是對內存的隨機讀。

那么為什么按行存儲不能用mmap的方式呢?因為按行存儲的方式一個文件里包含了很多列的數據,這個文件尺寸往往很大,超過了操作系統的文件緩存的大小。而按列存儲的方式把不同列分成了很多文件,可以只緩存用到的那些列,而不讓很少使用的列數據浪費內存。

注意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<Count> 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<RangeFacet> dateFacetFields = response.getFacetRanges();    for (RangeFacet facetField : dateFacetFields{
        List<org.apache.solr.client.solrj.response.RangeFacet.Count> dateCounts= facetField.getCounts();        for (org.apache.solr.client.solrj.response.RangeFacet.Count count : dateCounts) {
            System.out.println(count.getValue()+": "+count.getCount());
        }
    }


這里為什么Solr/Lucene的Facet(聚合)查詢會這么快呢?

想想Solr/Lucene的索引數據的方式就清楚了:倒排索引。對于某個索引字段,該字段下有哪幾個值,對于每個值,對應的文檔集合是建立索引的時候就清楚的,做聚合操作的時候“統計”下就知道結果了。

如果通過docValues建立索引,對于這類Facet查詢會更快,因為這時候索引已經通過字段(列)分割好了,只需要去對應文件中查詢統計就行了,如上文所述,通過“內存映射”,將該索引文件映射到內存,只需要在內存里統計下結果就出來了,所以就非???。

水平拆分表:

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

 

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


如對本文有疑問,請提交到交流論壇,廣大熱心網友會為你解答??! 點擊進入論壇


發表評論 (238人查看,0條評論)
請自覺遵守互聯網相關的政策法規,嚴禁發布色情、暴力、反動的言論。
用戶名: 驗證碼: 點擊我更換圖片
最新評論
------分隔線----------------------------
自拍偷拍福力视频,偷拍国际精品,麻豆一区福利电影,国产网红视频午夜福利,se视频大全,久久国产AV影院 老司机说的p站怎么进| 男女祼交动态图h| 色欲人妻综合网| 试衣间和老师疯狂试爱| 在办公室被c到高潮| 办公室亲嘴加模下面的| 西西人体44rt高清大胆摄影| 翁熄乱叫| 无码真人肉片在线观看| 美女扒开大腿让我爽视频| 国产成 人 综合 亚洲| 日韩丰满孕妇孕交| 沟厕一个接一个美女嘘嘘| 伊人久久大香线蕉五月| 激情欧美成人小说在线视频| 空姐按着她的腰强行坐下去| 免费无遮挡无码视频在线观看| 久久久久亚洲av无码专区首页| 狠狠的日日在线2018| zozoaftvzozo大狗| 老司机午夜免费精品视频| 国啪产自制福利2020| 欧美乱妇高清无乱码免费| chinesevideos少妇性多毛| 他在我身体里待了一晚上| 隔壁的放荡邻居在线看| 可以直接看的免费毛片| 男人添女人p免费视频| 国产精品高清一区二区不卡| 美女又色又黄的视频| 娇妻被黑人大杂交19p| http://www.clickcpn.com