Elasticsearch从0到千万级数据查询实践(非转载)
1.es简介
1.1 起源
https://www.elastic.co/cn/what-is/elasticsearch,es的起源,是因为程序员Shay Banon在使用Apache Lucene发现不太好用,然后手动改造升级的过程中发展起来的。(程序员就是需要有这种动力~)实际上es也是一个java应用,跑在jvm里面的
1.2 与关系型数据库的区别
关系型数据库 | schema(库) | 表 | 每一行的数据 | 字段columns |
elasticsearch | index(索引) | document | 字段fields |
1.3 为什么这么快
索引方式的区别,es主要是利用倒排索引(inverted index),这个翻译可能会让初次接触的人产生误解,误以为是倒着排序?其实不是这样,一般关系型数据库索引是把某个字段建立起一张索引表,传入这个字段的某个值,再去索引中判断是否有这个值,从而找到这个值所在数据(id)的位置。而倒排索引则是把这个值所在的文档id记录下来,当输入这个值的时候,直接查询出这个值所匹配的文档id,再取出id。所以我们在建立es索引的时候,有分词的概念,相当于可以把filed字段内容拆分,然后索引记录下来。例如“我爱中国”,可以拆分成“我”,“爱”,“中国”,“我爱中国”这五个词,同时记录下来这几个关键词的对应文档数据id是1,当我们查询“我”,“中国”时,都能查出这条数据。而如果使用关系型数据库去查包含“中国”这个关键字的数据的时候,则需要like前后通配全表扫描,无法快速找到关键词所在的数据行。
1.4 下载安装
https://www.elastic.co/cn/start 在这个地址里面下载最新版本,目前是7.10.2(拖了一个月写完,我下载的时候是7.9.3- -!)
Windows版是一个压缩包文件,解压后(进入bin点开bat)即可使用。Linux版由于是直接在k8s里拉的镜像,这里就不做赘述。
启动完成之后访问:http://127.0.0.1:9200/,看见如下页面:You Know, for Search,就算启动成功啦。
1.5 安装可视化软件
像数据库一样,可视化界面有Navicat,SQLyog,MySql自带的Workbench。es也是需要一个可视化ui界面来方便我们操作的。这里选择的也是官方的的kibana:
https://www.elastic.co/cn/downloads/kibana :
请注意需要选择与es匹配的版本,如果版本不匹配,则会提示你:
或者是其他类似版本不匹配的错误。
安装完成后就可以打开kibana玩耍啦,由于我本地没有数据,拿的是7.6.2版本搭建的elk中kibana界面:
如果需要连接环境上的es,则可以在这里配置用户名和密码:
这个工具的搜索很方便,不需要指定查哪个字段的哪个值,直接在输入框搜索想要查询的字段即可。如果想看他对应的查询语句,点开F12打开控制台即可研究:
es的查询条件还是比较复杂的,但是在业务查询当中,一些比较简单的查询就可以满足大多数的通用分页查询了,除非是要开发报表查询,会复杂一些。
1.6 机器要求
本地跑demo的话还是很容易的,这两个应用默认占用内存都不大,有需求可以自行调小一点:
2.Java中使用Elasticsearch
2.1 使用spring-data提供的封装
2.1.1 maven依赖
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-elasticsearch</artifactId>
</dependency>
2.1.2 yml参数
2.1.3 代码中映射索引实体
其中“omsElasticsearchSettings”这一段的意思是像mybatis那样解析表达式,找到omsElasticsearchSettings这个bean的getSuffix方法获取前后缀。这样就可以实现动态的根据环境生成映射对应的索引
1 @Configuration
2 @AllArgsConstructor
3 public class ElasticsearchConfig {
5 private final Environment env;
7 @Bean
8 public ElasticsearchSettings omsElasticsearchSettings(){
9 return new ElasticsearchSettings().setSuffix(env.getActiveProfiles()[0]);
10 }
12 }
13
16 @Data
17 @Accessors(chain = true)
18 public class ElasticsearchSettings {
20 public String suffix;
22 }
2.1.4 索引mapping生成
在启动项目的时候,SpringData会检测配置中的es里是否存在对应索引,如不存在,则会根据@Document实体中配置的@Field字段来生成mapping文件:
生成的Mapping Demo如下:
PUT om_package_dev/?pretty
{
"settings": {
"number_of_shards" :1,
"number_of_replicas" : 1
},
"mappings": {
"properties": {
"_class": {
"type": "text",
"fields": {
"keyword": {
"type": "keyword",
"ignore_above": 256
}
}
},
"actualFreightCost": {
"type": "double"
},
"actualPackageCost": {
"type": "double"
}
}
}
}
2.1.5 增删改
建立一个@Repository像mybatis一样来做增删改查的映射封装:
底层是SpringData提供封装的统一方法:
保存数据的时候直接调用即可:
一般来说订单这些重要数据不会删除,要删除也是逻辑删除,所以删除接口基本不调用。直接更新逻辑删除值就好。更新也是调用这个:save/saveAll
2.1.6 查
查是Es的重头戏,我们打开org.elasticsearch.index.query.AbstractQueryBuilder查看实现类可以发现,继承这个抽象类的各种查询类有四五十个之多,不得不让人感叹es的查询强大,(与反人类,学习成本太高了)。
好消息是,如果业务场景不复杂,仅仅是想在分页查询上提高速度,那么只需要掌握一下几个类的用法即可:
我们封装了两个查询枚举,一个用来定义该实体是es查询条件实体@interface QueryEntity:
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
public @interface QueryEntity { String[] dbOrders() default {}; String[] esOrders() default {}; String dbLogicField() default ""; String esLogicField() default "";
}
另外一个是用来定义字段,即使用es的哪个条件去查询@interface QueryField:
@Target({ElementType.FIELD})
@Retention(RetentionPolicy.RUNTIME)
public @interface QueryField { String esField() default ""; String dbField() default ""; boolean like() default false; boolean range() default false; boolean require() default false; boolean match() default false; boolean commaSupported() default false; boolean isBigDecimal() default false; Class<?> searchTypeEnum() default void.class; Class<?> sortTypeEnum() default void.class;
}
对应到实体上的用法demo就是:
这样可以支持区间查询,字段类型,对应es字段,从设计上规避了根据每个字段,调用每个拼接语句的上百个if/else噩梦。通过一个通用的查询工具类,来封装拼接这些查询条件QueryUtils:
@Slf4j
public class QueryUtils { private static ConcurrentHashMap<Class<?>, HashMap<String, Field>> classFieldMap = new ConcurrentHashMap<>(); /**
* 构建查询
*
* @param obj
* @return 若为 null 说明该查询必定不会返回结果,无需查询 ES
*/
public static BoolQueryBuilder boolQuery(Object obj) {
if (obj == null) {
return null;
}
BoolQueryBuilder root = QueryBuilders.boolQuery();
if (!classFieldMap.containsKey(obj.getClass())) {
HashMap<String, Field> filedNameMap = new HashMap<>(obj.getClass().getDeclaredFields().length);
for (Field field : obj.getClass().getDeclaredFields()) {
filedNameMap.put(field.getName(), field);
}
classFieldMap.put(obj.getClass(), filedNameMap);
}
HashMap<String, Field> filedNameMap = classFieldMap.get(obj.getClass());
QueryEntity entitySetting = obj.getClass().getAnnotation(QueryEntity.class);
for (Field field : filedNameMap.values()) {
QueryField fieldSetting;
if ((fieldSetting = field.getAnnotation(QueryField.class)) == null) {
continue;
}
Object value = ReflectionUtil.getValue(field, obj);
if (isNullOrEmpty(value)) {
if (!fieldSetting.require()) {
continue;
}
return null;
}
String fieldName = getEsQueryFieldName(field, fieldSetting); if (fieldSetting.range()) {
BoolQueryBuilder bool = QueryBuilders.boolQuery();
String[] arr = (String[]) value;
RangeQueryBuilder range = QueryBuilders.rangeQuery(fieldName);
if (arr.length != 2 || (StringUtils.isEmpty(arr[0]) && StringUtils.isEmpty(arr[1]))) {
continue;
}
if (!StringUtils.isEmpty(arr[0]) && StringUtils.isEmpty(arr[1])) {
bool.must(range.from(
fieldSetting.isBigDecimal() ? new BigDecimal(arr[0]) : DateUtil.parseAndGetTimestamp(arr[0])));
} else if (StringUtils.isEmpty(arr[0]) && !StringUtils.isEmpty(arr[1])) {
bool.must(range.to(fieldSetting.isBigDecimal() ? new BigDecimal(arr[1]) : DateUtil.parseAndGetTimestamp(arr[1])));
} else {
bool.must(range.from(fieldSetting.isBigDecimal() ? new BigDecimal(arr[0]) : DateUtil.parseAndGetTimestamp(arr[0])).
to(fieldSetting.isBigDecimal() ? new BigDecimal(arr[1]) : DateUtil.parseAndGetTimestamp(arr[1])));
}
root.must(bool);
} else if (field.getType() == List.class) {
assert value instanceof List<?>;
List<?> list = (List<?>) value;
if (CollectionUtils.isEmpty(list)) {
if (fieldSetting.require()) {
return null;
}
continue;
}
if (list.get(0) instanceof StoreListBO) {
BoolQueryBuilder bool1 = QueryBuilders.boolQuery();
for (Object store : list) {
StoreListBO bo = (StoreListBO) store;
BoolQueryBuilder bool2 = QueryBuilders.boolQuery();
if (!bo.getFlagAll()) {
bool2.must(QueryBuilders.termQuery("platformCode", bo.getPlatformCode()));
bool2.must(QueryBuilders.termsQuery("storeCode", bo.getStoreCodeList()));
}
bool1.should(bool2);
}
root.must(bool1);
} else {
root.must(QueryBuilders.termsQuery(fieldName, (List<?>) value));
}
} else if (fieldSetting.like()) {
root.must(QueryBuilders.wildcardQuery(fieldName, String.format("*%s*", value)));
} else if (fieldSetting.commaSupported()) {
root.must(QueryBuilders.termsQuery(fieldName, StringUtility.splitCommaString((String) value)));
} else if (fieldSetting.match()) {
if (fieldSetting.commaSupported()) {
root.must(QueryBuilders.multiMatchQuery(fieldName, StringUtility.splitCommaString((String) value)));
} else {
root.must(QueryBuilders.matchQuery(fieldName, value));
}
} else if (fieldSetting.searchTypeEnum().isEnum()) {
try {
Object[] objects = fieldSetting.searchTypeEnum().getEnumConstants();
if (objects[0] instanceof IEsSearchTypeEnum) {
IEsSearchTypeEnum searchTypeEnum = (IEsSearchTypeEnum) objects[0];
fieldName = searchTypeEnum.getFiledName((Integer) value);
Field filed = filedNameMap.get(IEsSearchTypeEnum.searchContent);
filed.setAccessible(true);
String searchContent = (String) ReflectUtil.getField(filed, obj);
if (!StringUtils.isEmpty(fieldName) && !StringUtils.isEmpty(searchContent)) {
root.must(QueryBuilders.termsQuery(fieldName, searchContent.split(",")));
}
}
} catch (Exception e) {
e.printStackTrace();
log.error("拼接搜索类型有误:", e.getMessage());
}
} else if (fieldSetting.sortTypeEnum().isEnum()) {
continue;
} else {
root.must(QueryBuilders.termQuery(fieldName, value));
}
}
if (entitySetting != null) {
if (!StringUtils.isEmpty(entitySetting.esLogicField())) {
root.must(QueryBuilders.termQuery(entitySetting.esLogicField(), LogicValueConstants.NORMAL));
}
}
root.must(QueryBuilders.termQuery("tenantId", AuthUtil.getTenantId()));
log.info("query : {}", Strings.toString(root));
return root;
} private static boolean isNullOrEmpty(Object value) {
return Objects.isNull(value) || isEmptyString(value) || isEmptyCollection(value);
} private static String getEsQueryFieldName(Field field, QueryField fieldSetting) {
return StringUtils.isEmpty(fieldSetting.esField()) ?
field.getName() : fieldSetting.esField();
} private static boolean isEmptyCollection(Object value) {
return (value instanceof Collection) && CollectionUtils.isEmpty((Collection<?>) value);
} private static boolean isEmptyString(Object value) {
return (value instanceof String) && StringUtils.isEmpty(value);
} public static void handlePageable(Object obj, NativeSearchQueryBuilder builder) {
if (obj instanceof PageDTO) {
PageDTO pageDTO = (PageDTO) obj;
builder.withPageable(PageRequest.of(pageDTO.currForEsPaging(), pageDTO.size()));
}
} public static void dealSort(Object obj, NativeSearchQueryBuilder builder) {
// 默认按最后更新时间倒序
String fieldName = null;
Boolean isAsc = false;
Boolean asc2Desc;
try {
HashMap<String, Field> fieldNameMap = classFieldMap.get(obj.getClass());
Field sortTypeField = fieldNameMap.get(IEsSortTypeEnum.SORT_TYPE);
if (sortTypeField != null) {
QueryField fieldSetting = sortTypeField.getAnnotation(QueryField.class);
if (fieldSetting != null && fieldSetting.sortTypeEnum().isEnum()) {
Object[] objects = fieldSetting.sortTypeEnum().getEnumConstants();
if (objects[0] instanceof IEsSortTypeEnum) {
IEsSortTypeEnum sortTypeEnum = (IEsSortTypeEnum) objects[0];
fieldName = sortTypeEnum.getFiledName((Integer) sortTypeField.get(obj));
asc2Desc = sortTypeEnum.getAsc2Desc((Integer) sortTypeField.get(obj));
Field filed = ReflectUtil.getField(obj.getClass(), IEsSortTypeEnum.SORT_ASC);
filed.setAccessible(true);
isAsc = (Boolean) ReflectUtil.getField(filed, obj);
if (isAsc != null) {
isAsc = asc2Desc ? !isAsc : isAsc;
}
}
}
}
} catch (Exception e) {
e.printStackTrace();
log.error("拼接排序类型有误:", e);
}
builder.withSort(SortBuilders.fieldSort(fieldName == null ? IEsSortTypeEnum.DEFAULT_SORT_FILED : fieldName).order(isAsc == null ? SortOrder.DESC :
isAsc ? SortOrder.ASC : SortOrder.DESC));
log.info("es 排序参数" + Strings.toString(builder.build().getElasticsearchSorts().get(0)));
}
支持排序拼接、count统计类型拼接、时间区间拼接,金额拼接、list集合查询拼接,输入多个单号的时候,通过分隔符分隔拼接
实体搜索类型
(PS:1.8新增了枚举类可以实现接口,这样枚举用起来也很舒服了)
这两个枚举类的作用主要是适配按照不同的搜索条件以及排序条件排序
3.千万级数据测试
3.1数据准备
标题写的那么夸张,千万级数据,哈哈,其实就是老套路搞了个存储过程往数据库塞一千万数据,然后同步到es测试啦
DROP PROCEDURE IF EXISTS test;
DELIMITER $$
CREATE PROCEDURE test()
BEGIN
DECLARE v_i INT UNSIGNED DEFAULT 10000001;
WHILE v_i < 10000894 DO
INSERT INTO ‘test’ VALUES('v_i')
SET v_i = v_i+1;
END WHILE;
END $$
DELIMITER ; CALL test();
言归正传,测试的目的有两个,一个是后台任务的同步代码情况,验证数据库与es的数据一致性,同时估算性能,做到上线时迁移数据心中有数。二是模拟es在大数据量的情况下会不会有什么影响。
最终es结果如下:
- 后台任务分页查数据库,每页五千条,加上其他关联表查询,5000条差不多1~2秒。机器性能i7 9700 32g,10000000 / 5000 * 2s / 3600s = 大概一个小时左右同步完成。一千万数据大概占用1.3gb空间,要根据mapping字段多少来看。仅供参考
- 分页这边需要调整参数:
要不然es默认只能查出最大10000条。同时也需要调整es的参数:
PUT om_package_dev/_settings
{
"index" : {"max_result_window" : 10000000}
}
其实这个地方可以从业务角度思考一下,es默认10000也不是没有道理。对于大数据量,精准点击第666666页的人都是像我这样吃饱了没事干的。点到那页去干嘛?点之前你也不知道那页有啥呀。。。并且es分页效率也很低,选最后一页很慢。大数据量如果需要查询,一般根据条件精准查询。目前这点数据量查询还是非常快的。
4.小结
4.1 数据一致性
目前我们的方案主要是靠代码层面实现。当数据有变动时,发送一条消息给mq,由mq异步去同步es。同时,有一个后台任务一直在跑三分钟(根据数据量决定)以内的数据,以防mq失效有一个兜底任务。当然还有其他方案,比如通过MySQL的binlog写到es里面去,这种方案对性能要求高,同时需要引入第三方组件。最终我们选择了代码层面自己比较可控的一种方案。
4.2 elasticsearch-sql
从开始看见拼es查询条件,就在想如果能直接把sql转化成es就好了。后来搜了一下,果然有这种好东西,是中国自然语言处理开源组织提供的插件。但是已经写完了通用查询,就没有去研究这个插件怎么用。有兴趣的小伙伴可以试试。https://github.com/NLPchina/elasticsearch-sql 另外:kibana的工具控制台也可以直接发送sql请求
POST _sql/translate
{
"query": """
SELECT doc.message FROM "filebeat-7.6.2-2021.01.30"
""",
"fetch_size": 100
}
4.3 Connection Rest By Peer
测试发现,有些时候:早上刚来、中午刚起床、晚上准备下班。也就是很久没人点了,第一次点击的时候会报这个错误。(我们的测试真敬业- -)
出现Connection Rest By Peer的问题,一般是一端关闭了连接,而另一端还以为对方在呢,然后傻乎乎的发请求过去,发现对方已经不跟它玩了。查看了es所在机器的k8s keepalive设置:
可以看见默认的keepalive连接超时时间是7200s,也就是两小时。吻合了测试发现的报错时间点,也符合日志中记录的时间。而es客户端这边如果不指定keepalive的话,默认取的是ConnectionKeepAliveStrategy里面的-1。所以java客户端这边-1不会断开连接,而Linux那边两小时就会断开,从而造成了Connection Rest By Peer。研究了一下SpringData配置es参数的地方,发现Spring配置除了获取配置中的url和密码之外,没有可以配置keepalive的地方。只有重写RestClientBuilder的构建逻辑,实际上SpringData底层也是用的es提供的客户端,只不过在上层再封装了一下:
package com.zhkj.oms.config; import org.apache.http.HeaderElement;
import org.apache.http.HeaderElementIterator;
import org.apache.http.HttpHost;
import org.apache.http.HttpResponse;
import org.apache.http.auth.AuthScope;
import org.apache.http.auth.UsernamePasswordCredentials;
import org.apache.http.client.CredentialsProvider;
import org.apache.http.conn.ConnectionKeepAliveStrategy;
import org.apache.http.impl.client.BasicCredentialsProvider;
import org.apache.http.message.BasicHeaderElementIterator;
import org.apache.http.protocol.HTTP;
import org.apache.http.protocol.HttpContext;
import org.apache.http.util.Args;
import org.elasticsearch.client.RestClient;
import org.elasticsearch.client.RestClientBuilder;
import org.springframework.beans.factory.ObjectProvider;
import org.springframework.boot.autoconfigure.elasticsearch.ElasticsearchRestClientProperties;
import org.springframework.boot.autoconfigure.elasticsearch.RestClientBuilderCustomizer;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.util.StringUtils; import java.net.URI;
import java.net.URISyntaxException; /**
* @author Xxx
* @since 2021/1/19/0019 11:04
*
* 0.运维那边没有改keepalive,linux默认7200s 我们这边默认-1无限制
* 1.SpringData里面没有设置keepalive的地方,只有重写RestClientBuilder的构建
* 2.再重新实现HttpAsyncClientBuilder里面的ConnectionKeepAliveStrategy获取keepalive的方法
*/
@Configuration
public class EsRestClientBuilderConfig {
@Bean
RestClientBuilder elasticsearchRestClientBuilder(ElasticsearchRestClientProperties properties,
ObjectProvider<RestClientBuilderCustomizer> builderCustomizers) {
HttpHost[] hosts = properties.getUris().stream().map(this::createHttpHost).toArray(HttpHost[]::new);
RestClientBuilder builder = RestClient.builder(hosts);
final CredentialsProvider credentialsProvider = new BasicCredentialsProvider();
credentialsProvider.setCredentials(AuthScope.ANY,
new UsernamePasswordCredentials(properties.getUsername(), properties.getPassword()));
builder.setHttpClientConfigCallback((httpClientBuilder) -> {
builderCustomizers.orderedStream().forEach((customizer) -> customizer.customize(httpClientBuilder));
httpClientBuilder.setDefaultCredentialsProvider(credentialsProvider);
httpClientBuilder.setKeepAliveStrategy(new ConnectionKeepAliveStrategy() {
@Override
public long getKeepAliveDuration(HttpResponse response, HttpContext context) {
Args.notNull(response, "HTTP response");
final HeaderElementIterator it = new BasicHeaderElementIterator(
response.headerIterator(HTTP.CONN_KEEP_ALIVE));
while (it.hasNext()) {
final HeaderElement he = it.nextElement();
final String param = he.getName();
final String value = he.getValue();
if (value != null && param.equalsIgnoreCase("timeout")) {
try {
return Long.parseLong(value) * 1000;
} catch (final NumberFormatException ignore) {
}
}
}
// 三分钟
return 1 * 60 * 1 * 1000;
}});
return httpClientBuilder;
});
builder.setRequestConfigCallback((requestConfigBuilder) -> {
builderCustomizers.orderedStream().forEach((customizer) -> customizer.customize(requestConfigBuilder));
return requestConfigBuilder;
});
builderCustomizers.orderedStream().forEach((customizer) -> customizer.customize(builder));
return builder;
} private HttpHost createHttpHost(String uri) {
try {
return createHttpHost(URI.create(uri));
}
catch (IllegalArgumentException ex) {
return HttpHost.create(uri);
}
}
private HttpHost createHttpHost(URI uri) {
if (!StringUtils.hasLength(uri.getUserInfo())) {
return HttpHost.create(uri.toString());
}
try {
return HttpHost.create(new URI(uri.getScheme(), null, uri.getHost(), uri.getPort(), uri.getPath(),
uri.getQuery(), uri.getFragment()).toString());
}
catch (URISyntaxException ex) {
throw new IllegalStateException(ex);
}
}
}
Create By Xxx 2021年1月30日16:10:56 转载请注明出处,3q!
Elasticsearch从0到千万级数据查询实践(非转载)的更多相关文章
- 如何优化Mysql千万级快速分页,limit优化快速分页,MySQL处理千万级数据查询的优化方案
如何优化Mysql千万级快速分页,limit优化快速分页,MySQL处理千万级数据查询的优化方案
- Elasticsearch+Mongo亿级别数据导入及查询实践
数据方案: 在Elasticsearch中通过code及time字段查询对应doc的mongo_id字段获得mongodb中的主键_id 通过获得id再进入mongodb进行查询 1,数据情况: ...
- mysql 千万级数据查询效率实践,分析 mysql查询优化实践--本文只做了一部分,仅供参考
数据量, 1300万的表加上112万的表 注意: 本文只做了部分优化,并不全面,仅供参考, 欢迎指点. 请移步tim查看,因为写的时候在tim写的,粘贴过来截图有问题,就直接上链接了. https ...
- mysql千万级数据量查询出所有重复的记录
查询重复的字段需要创建索引,多个条件则创建组合索引,各个条件的索引都存在则不必须创建组合索引 有些情况直接使用GROUP BY HAVING则能直接解决:但是有些情况下查询缓慢,则需要使用下面其他的方 ...
- Elasticsearch 5.0 中term 查询和match 查询的认识
Elasticsearch 5.0 关于term query和match query的认识 一.基本情况 前言:term query和match query牵扯的东西比较多,例如分词器.mapping ...
- 构建ASP.NET MVC4+EF5+EasyUI+Unity2.x注入的后台管理系统(37)-文章发布系统④-百万级数据和千万级数据简单测试
原文:构建ASP.NET MVC4+EF5+EasyUI+Unity2.x注入的后台管理系统(37)-文章发布系统④-百万级数据和千万级数据简单测试 系列目录 我想测试EF在一百万条数据下的显示时间! ...
- MySQL千万级数据分区存储及查询优化
作为传统的关系型数据库,MySQL因其体积小.速度快.总体拥有成本低受到中小企业的热捧,但是对于大数据量(百万级以上)的操作显得有些力不从心,这里我结合之前开发的一个web系统来介绍一下MySQL数据 ...
- Mysql千万级数据删除实操-企业案例
某天,在生产环节中,发现一个定时任务表,由于每次服务区查询这个表就会造成慢查询,给mysql服务器带来不少压力,经过分析,该表中绝对部分数据是垃圾数据 需要删除,约1050万行,由于缺乏处理大数据的额 ...
- (转载)MYSQL千万级数据量的优化方法积累
转载自:http://blog.sina.com.cn/s/blog_85ead02a0101csci.html MYSQL千万级数据量的优化方法积累 1.分库分表 很明显,一个主表(也就是很重要的表 ...
随机推荐
- [leetcode]53Maximum Subarray动态规划经典题目:最大子串问题
/** * Find the contiguous subarray within an array (containing at least one number) * which has the ...
- [LeetCode]501. Find Mode in Binary Search Tree二叉搜索树寻找众数
这次是二叉搜索树的遍历 感觉只要和二叉搜索树的题目,都要用到一个重要性质: 中序遍历二叉搜索树的结果是一个递增序列: 而且要注意,在递归遍历树的时候,有些参数如果是要随递归不断更新(也就是如果递归返回 ...
- python在线练习
不管学习那门语言都希望能做出实际的东西来,这个实际的东西当然就是项目啦,不用多说大家都知道学编程语言一定要做项目才行. 这里整理了70个Python实战项目列表,都有完整且详细的教程,你可以从中选择自 ...
- python的22个基本语法
"人生苦短,我用Python".Python编程语言是最容易学习.并且功能强大的语言.只需会微信聊天.懂一点英文单词即可学会Python编程语言.但是很多人声称自己精通Python ...
- eclipse中安装jetty插件并使用
一.eclipse中jetty插件安装: 打开eclipse,依次点击菜单Help->Eclipse Marketplace,在Find后面的框中输入jetty,选择第一项进行install即可 ...
- FAAS -- Serverless
FAAS概念,无服务器运算,功能即服务,function-as-a-service 初创企业-大型企业.民间组织-政府机构 ===>>>> 上云 云计算第三代技术 -- Ser ...
- SpringBoot整合Shiro权限框架实战
什么是ACL和RBAC ACL Access Control list:访问控制列表 优点:简单易用,开发便捷 缺点:用户和权限直接挂钩,导致在授予时的复杂性,比较分散,不便于管理 例子:常见的文件系 ...
- String被final修饰
源码:
- Hbase之过滤器的使用
一.过滤器概念 基础API中的查询操作在面对大量数据的时候是非常物无力的,这里Hbase提供了高级的查询方法:Filter(过滤器).过滤器可以根据簇.列.版本等更多的条件来对数据进行过滤,基于Hba ...
- Linux下使用acme.sh申请和管理Let’s Encrypt证书
关于Let's Encrypt 免费SSL证书 Let's Encrypt 作为一个公共且免费 SSL 的项目逐渐被广大用户传播和使用,是由 Mozilla.Cisco.Akamai.IdenTrus ...