Solr与MySQL查询性能对比

本文涉及的产品
RDS MySQL Serverless 基础系列,0.5-2RCU 50GB
云数据库 RDS PostgreSQL,高可用系列 2核4GB
云数据库 RDS MySQL,高可用系列 2核4GB
简介:

测试环境

本文简单对比下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/

 


    本文转自阿凡卢博客园博客,原文链接:http://www.cnblogs.com/luxiaoxun/p/4696477.html,如需转载请自行联系原作者


相关实践学习
每个IT人都想学的“Web应用上云经典架构”实战
本实验从Web应用上云这个最基本的、最普遍的需求出发,帮助IT从业者们通过“阿里云Web应用上云解决方案”,了解一个企业级Web应用上云的常见架构,了解如何构建一个高可用、可扩展的企业级应用架构。
MySQL数据库入门学习
本课程通过最流行的开源数据库MySQL带你了解数据库的世界。 &nbsp; 相关的阿里云产品:云数据库RDS MySQL 版 阿里云关系型数据库RDS(Relational Database Service)是一种稳定可靠、可弹性伸缩的在线数据库服务,提供容灾、备份、恢复、迁移等方面的全套解决方案,彻底解决数据库运维的烦恼。 了解产品详情:&nbsp;https://www.aliyun.com/product/rds/mysql&nbsp;
相关文章
|
1月前
|
缓存 关系型数据库 BI
使用MYSQL Report分析数据库性能(下)
使用MYSQL Report分析数据库性能
84 3
|
1月前
|
SQL 缓存 监控
MySQL缓存机制:查询缓存与缓冲池优化
MySQL缓存机制是提升数据库性能的关键。本文深入解析了MySQL的缓存体系,包括已弃用的查询缓存和核心的InnoDB缓冲池,帮助理解缓存优化原理。通过合理配置,可显著提升数据库性能,甚至达到10倍以上的效果。
|
1月前
|
SQL 存储 关系型数据库
MySQL体系结构详解:一条SQL查询的旅程
本文深入解析MySQL内部架构,从SQL查询的执行流程到性能优化技巧,涵盖连接建立、查询处理、执行阶段及存储引擎工作机制,帮助开发者理解MySQL运行原理并提升数据库性能。
|
3月前
|
SQL 人工智能 关系型数据库
如何实现MySQL百万级数据的查询?
本文探讨了在MySQL中对百万级数据进行排序分页查询的优化策略。面对五百万条数据,传统的浅分页和深分页查询效率较低,尤其深分页因偏移量大导致性能显著下降。通过为排序字段添加索引、使用联合索引、手动回表等方法,有效提升了查询速度。最终建议根据业务需求选择合适方案:浅分页可加单列索引,深分页推荐联合索引或子查询优化,同时结合前端传递最后一条数据ID的方式实现高效翻页。
208 0
|
5月前
|
负载均衡 算法 关系型数据库
大数据大厂之MySQL数据库课程设计:揭秘MySQL集群架构负载均衡核心算法:从理论到Java代码实战,让你的数据库性能飙升!
本文聚焦 MySQL 集群架构中的负载均衡算法,阐述其重要性。详细介绍轮询、加权轮询、最少连接、加权最少连接、随机、源地址哈希等常用算法,分析各自优缺点及适用场景。并提供 Java 语言代码实现示例,助力直观理解。文章结构清晰,语言通俗易懂,对理解和应用负载均衡算法具有实用价值和参考价值。
大数据大厂之MySQL数据库课程设计:揭秘MySQL集群架构负载均衡核心算法:从理论到Java代码实战,让你的数据库性能飙升!
|
1月前
|
SQL 关系型数据库 MySQL
MySQL的查询操作语法要点
储存过程(Stored Procedures) 和 函数(Functions) : 储存过程和函数允许用户编写 SQL 脚本执行复杂任务.
171 14
|
1月前
|
SQL 关系型数据库 MySQL
MySQL的查询操作语法要点
以上概述了MySQL 中常见且重要 的几种 SQL 查询及其相关概念 这些知识点对任何希望有效利用 MySQL 进行数据库管理工作者都至关重要
81 15
|
1月前
|
SQL 监控 关系型数据库
SQL优化技巧:让MySQL查询快人一步
本文深入解析了MySQL查询优化的核心技巧,涵盖索引设计、查询重写、分页优化、批量操作、数据类型优化及性能监控等方面,帮助开发者显著提升数据库性能,解决慢查询问题,适用于高并发与大数据场景。
|
1月前
|
缓存 监控 关系型数据库
使用MYSQL Report分析数据库性能(上)
最终建议:当前系统是完美的读密集型负载模型,优化重点应放在减少行读取量和提高数据定位效率。通过索引优化、分区策略和内存缓存,预期可降低30%的CPU负载,同时保持100%的缓冲池命中率。建议每百万次查询后刷新统计信息以持续优化
149 6
|
1月前
|
缓存 监控 关系型数据库
使用MYSQL Report分析数据库性能(中)
使用MYSQL Report分析数据库性能
107 1

推荐镜像

更多