面對千萬級數據量,MySQL 即使加索引仍難以滿足複雜檢索與多維聚合需求。本文通過實測數據對比 MySQL(有/無索引)、Solr(/select vs /export)、Solr Facet 聚合在範圍查詢、ID 列表獲取、統計分析場景的性能差異,並給出架構選型建議。
2014-12-06 00:00:00 ~ 2014-12-10 21:31:55tf_hotspotdata_copy_test,含 collectTime、deviceID、id 等字段| 方案 | 耗時 | 說明 |
|---|---|---|
| MySQL 無索引 | 30s | 全表掃描 |
| MySQL 有索引 (collectTime) | 2s | 索引範圍掃描 + 回表 |
| Solr /select (QTime) | 72ms | 僅索引查詢耗時 |
| SolrJ /select 獲取結果集 | 12s | 含磁盤讀取 stored 字段 + HTTP 傳輸開銷 |
結論:Solr 索引查詢極快,但大結果集網絡/磁盤 IO 成為瓶頸。
| 方案 | 耗時 | 說明 |
|---|---|---|
| MySQL 無索引 | 10s | 全表掃描 |
| MySQL 有索引 | 0.337s | 覆蓋索引,無回表 |
| SolrJ /select 僅返回 id | 7s | 仍需解析文檔、HTTP 傳輸 |
Solr 提供 /export RequestHandler,採用流式排序技術,毫秒級開始輸出,適合大結果集導出。
docValues 建立索引int, float, long, double, string<field name="id" type="long" indexed="true" stored="true" docValues="true" />
<field name="CollectTime" type="tdate" indexed="true" stored="true" docValues="true" />
<field name="DeviceID" type="string" indexed="true" stored="true" docValues="true" />
SolrQuery params = new SolrQuery();
params.set("q", timeQueryString);
params.set("fq", queryString);
params.set("sort", "id asc"); // 必須指定排序
params.set("qt", "/export"); // 關鍵:使用 export handler
params.setFields("id"); // 僅導出 id
params.set("rows", Integer.MAX_VALUE);
QueryResponse response = server.query(params);
SolrJ BinaryResponseParser 硬編碼期望 application/octet-stream,但 Solr 返回 application/json。自行實現 HTTP GET + JSON 解析:
String url = "https://host:8985/solr/core/export?q=...&sort=id+asc&fl=id&wt=json";
long s = System.currentTimeMillis();
SolrHttpJsonClient client = new SolrHttpJsonClient();
SolrQueryResult result = client.getQueryResultByGet(url);
System.out.println("Size: " + result.getResponse().getNumFound()); // 220296
System.out.println("Time: " + (System.currentTimeMillis() - s)); // ~2000ms
| 方案 | 耗時 | 說明 |
|---|---|---|
| MySQL 無索引 GROUP BY | 33s | 臨時表 + filesort |
| MySQL 有索引 | 14s | 索引掃描聚合 |
| Solr Facet 查詢 | 540ms | 倒排索引天然適合聚合 |
SolrQuery query = new SolrQuery();
query.set("q", "*:*");
query.setFacet(true);
query.addFacetField("DeviceID");
QueryResponse response = server.query(query);
FacetField facet = response.getFacetField("DeviceID");
for (Count count : facet.getValues()) {
System.out.println(count.getName() + ": " + count.getCount());
}
SolrQuery query = new SolrQuery();
query.set("q", "DeviceID:1013");
query.setFacet(true);
Date start = toSolrDate("2014-12-06 00:00:00");
Date end = toSolrDate("2014-12-16 21:31:55");
query.addDateRangeFacet("CollectTime", start, end, "+1DAY");
QueryResponse response = server.query(query);
// 返回每天文檔數,耗時 662ms
| 查詢場景 | MySQL 無索引 | MySQL 有索引 | Solr /select | Solr /export | Solr Facet |
|---|---|---|---|---|---|
| 範圍查詢全字段 | 30s | 2s | 12s | - | - |
| 範圍查詢僅 ID | 10s | 0.34s | 7s | 2s | - |
| 分組統計 | 33s | 14s | - | - | 0.54s |
| 時間下鑽 | - | - | - | - | 0.66s |
/select(深分頁優化:游標/ID 游標)/export(需 docValues,字段類型受限)Facet/DateRangeFacet,性能碾壓 MySQL| 維度 | Sphinx/Coreseek | Solr |
|---|---|---|
| 索引速度 | 10-15 MB/s | ~10 MB/s |
| 查詢響應 | <0.1s (2-4GB) | 150ms (8GB索引) |
| 中文分詞 | mmseg3 (詞庫靜態) | IK/Ansj/mmseg4j (動態擴展詞庫) |
| 實時索引 | Coreseek 3.x 不支持,4.x 測試版 | 原生支持 NRT (Near Real Time) |
| 增量索引合併 | 主/增量索引分離,合併耗時不可控 | 單索引文件,增量自動合併 |
| 多核 | 不支持 | 支持 (SolrCloud) |
| 生態/管理 | 輕量、運維簡單 | 功能豐富、Web UI、SolrCloud 分布式 |
結論:中文場景、實時性要求高、需分布式擴展選 Solr;極簡部署、純檢索服務可考慮 Sphinx。