1. 程式人生 > >Solr與MySQL查詢效能對比

Solr與MySQL查詢效能對比

測試環境

本文簡單對比下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獲取大量結果集速度慢的一些類似問題:

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: 

<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等搜尋引擎,可以提高類似全文檢索、分類統計等查詢效能。

參考:

http://wiki.apache.org/solr/

https://lucidworks.com/blog/2013/04/02/fun-with-docvalues-in-solr-4-2/

相關推薦

SolrMySQL查詢效能對比

測試環境 本文簡單對比下Solr與MySQL的查詢效能速度。 測試資料量:10407608     Num Docs: 10407608 普通查詢 這裡對MySQL的查詢時間都包含了從MySQL Server獲取資料的時間。 在專案中一個最常用的查詢,查詢某段時間內的資料,SQL查詢獲取資料,30s

lucene3.5mysql 查詢效能對比

由於最近做畢設,需要做一個商品查詢模組,用到了lucene來做商品索引的查詢,原因為: 1.marks(商品標籤)欄位含有多個標籤,當針對某個標籤查詢時,或許只能用like 查詢,這樣的查詢慢! 2.沒用過lucene,想用來測試下效能 可能結果早就顯而易見,但是這次測

HAWQHive查詢效能對比測試

一、實驗目的        本實驗通過模擬一個典型的應用場景和實際資料量,測試並對比HAWQ內部表、外部表與Hive的查詢效能。二、硬體環境1. 四臺VMware虛機組成的Hadoop叢集。2. 每臺機器配置如下:(1)15K RPM SAS 100GB(2)Intel(R)

Python霧裡看花-listset十萬資料查詢效能對比

# -*- coding: utf-8 -*- import random import time num = 100000 listA = [random.randint(1, i) for i in range(1, num)] setB = set() while len(set

Mysql資料庫InnodbMyISAM的效能對比測試

由於近期有個專案對系統性能要求很高,技術選型上由於種種原因已經確定使用Mysql資料庫,接下來就是要確定到底使用哪種儲存引擎。我們的應用是典型的寫多讀少,寫入內容為也很短,對系統的穩定性要求很高。所以儲存引擎肯定就定在廣泛使用的Innodb和MyISAM之中了。   

solr MySQL(二)

apache lec 日誌 命名 set false data 訪問 tor 今天的學習依然讓人非常心累,遇到的bug怎麽都修改不過來,最後自己看哪幾個配置文件都暈了,幹脆全部推翻重來。 翻看了不少網上的教程,學習過程中一直翻車,最後終於弄好了,上來總結一下。 首頁Dash

hibernate抓取策略效能分析,子查詢/連線查詢效能對比

https://blog.csdn.net/qq_40762011/article/details/82993283 https://blog.csdn.net/lzm18064126848/article/details/50578285 https://blog.csdn.net/luckarecs/

高效能Mysql------------查詢效能優化

查詢優化,索引優化,庫表結構優化需要齊頭並進。 查詢效能低下最基本的原因是訪問的資料太多了。可以通過下面兩個步驟來分析: 1.是否檢索大量超過需要的資料 2.是否在分析大量超過需要的資料行 請求了不需要的資料 1)查詢不需要的記錄 最簡單的解決方法是在查詢後面加limit 2)多

轉載:Python Web 框架:Django、Flask Tornado 的效能對比

原文地址: https://www.jianshu.com/p/9960a9667a5c 寫在前面: 本文的資料涉及到我面試時遇到過的問題,大概一次 http 請求到收到響應需要多少時間。這個問題在實際工作中與框架有比較大的關係,因此特別就框架的效能做了一次分析。

mongodbmysql命令詳細對比

傳統的關係資料庫一般由資料庫(database)、表(table)、記錄(record)三個層次概念組成,MongoDB是由資料庫(database)、集合(collection)、文件物件(document)三個層次組成。MongoDB對於關係型資料庫裡的表,但是集合中沒有列、行和關係概念,這體現了模式自由

Java、PHP、Python、Erlang、Golang 千萬級記憶體資料插入、查詢效能對比

測試環境: centos 6.3 64bit php 7.2 java 1.86 python 3.4.8Erlang/OTP 19 [erts-8.1]golang 1.9.2 至強2.5G 4核 x 2 8 G記憶體 146g scsi x 2 raid 0+1 測試

Go 字串連線+=strings.Join效能對比

Go字串連線 對於字串的連線大致有兩種方式: 1、通過+號連線 func StrPlus1(a []string) string { var s, sep string for i := 0; i < len(a); i++ { s += sep + a[i]

C/C++ 折半查詢順序查詢對比分析】

線上性表的順序儲存結構中用到的查詢方式莫過於順序查詢和折半查詢; 儘管順序查詢的時間複雜度為O(n), 折半查詢的時間複雜度為O(log2n),相比之下折半查詢就顯得效率更高, 但是二者使用的場合不同,需要滿足的條件也不同,於是乎優劣之分便不再那麼重要。   首先博主對

Mobx Redux 的效能對比

在本文中你將看到我最終得出的結論是 Mobx 的效能優於 Redux。但很明顯這樣的結論是片面的,甚至是有失偏頗的,因為我只選取了一個的場景對兩者進行測試。可能真實的情況恰恰相反,Mobx 僅僅在我測試的這個場景中優於 Redux,但是在我所有沒有測試到的場景中都劣於 Redux,這都是有可能的。效能跑分這類

轉 Redis作為訊息佇列RabbitMQ的效能對比

週末測試了一下RabbitMQ的效能,RabbitMQ是使用Erlang編寫的一個開源的訊息佇列,本身支援很多的協議:AMQP,XMPP, SMTP, STOMP,也正是如此,使的它變的非常重量級,更適合於企業級的開發。個人認為,在網際網路開發中,使用訊息佇列,更多的因為在高併發環境下,由於來不及

[轉]Redis作為訊息佇列RabbitMQ的效能對比

週末測試了一下RabbitMQ的效能,RabbitMQ是使用Erlang編寫的一個開源的訊息佇列,本身支援很多的協議:AMQP,XMPP, SMTP, STOMP,也正是如此,使的它變的非常重量級,更適合於企業級的開發。個人認為,在網際網路開發中,使用訊息佇列,更多的因為在高併發環境下,由於來不及同步處理,請

MySQL查詢效能優化一則

公司有一套Web系統, 使用方反饋系統某些頁面訪問速度緩慢, 使用者體驗很差, 並且偶爾還會出現HTTP 502錯誤。 這是典型的伺服器端IO阻塞引發的問題,通過對訪問頁面的程式邏輯進行跟蹤,發現問題應該是出在某個SQL查詢上。 在頁面程式執行的某個步驟中,有這樣一段SQL select dis

並行流順序流效能對比試驗

通過試驗得到,並行流不一定要比順序流快,所以在選擇的時候要注意使用。 程式碼: ParallelStreamsHarness .java import java.util.concurrent.

PostgreSQL和MySQL效能對比實驗

測試資料庫schema: drop table if exists t1; drop table if exists t2; drop table if exists t3; create table t1(id int primary key, value1 int, v

Linux系統優化之設定swappiness值提高MySQL查詢效能

對MySQL來說,作業系統層面的優化也可以值得考慮一下:就是swappiness。 swappiness的大小主要對如何使用swap分割槽有著密切的聯絡。 來看一下: [[email protected] ~]# cat /proc/sys/vm/swappiness 60 這個