阿里云MongoDB学习

MongoDB-第五章-索引

2020-03-16  本文已影响0人  weipeng2k

索引

        索引如同字典的目录一样,是用来加速查询的。具有正确索引的查询会比没有索引的查询快几个数量级,当随着数据量级变大时,愈发明显。

索引简介

        首先我们准备一个集合author_test_collection,集合中的文档主要包括了,以下几个字段:nameage,其中name的类型是字符串,而age是整型。

记住,任何文档都会包含一个_id字段

        集合中包含了100万个元素。

> db.author_test_collection.count();
1000000

        查找nameAuthor-3000的文档,通过explain命令,可以输出执行计划。

> db.author_test_collection.find({"name":"Author-3000"}).explain("executionStats")
{
    "queryPlanner" : {
        "plannerVersion" : 1,
        "namespace" : "test.author_test_collection",
        "indexFilterSet" : false,
        "parsedQuery" : {
            "name" : {
                "$eq" : "Author-3000"
            }
        },
        "winningPlan" : {
            "stage" : "COLLSCAN",
            "filter" : {
                "name" : {
                    "$eq" : "Author-3000"
                }
            },
            "direction" : "forward"
        },
        "rejectedPlans" : [ ]
    },
    "executionStats" : {
        "executionSuccess" : true,
        "nReturned" : 1,
        "executionTimeMillis" : 652,
        "totalKeysExamined" : 0,
        "totalDocsExamined" : 1000000,
        "executionStages" : {
            "stage" : "COLLSCAN",
            "filter" : {
                "name" : {
                    "$eq" : "Author-3000"
                }
            },
            "nReturned" : 1,
            "executionTimeMillisEstimate" : 570,
            "works" : 1000002,
            "advanced" : 1,
            "needTime" : 1000000,
            "needYield" : 0,
            "saveState" : 7812,
            "restoreState" : 7812,
            "isEOF" : 1,
            "invalidates" : 0,
            "direction" : "forward",
            "docsExamined" : 1000000
        }
    },
    "serverInfo" : {
        "host" : "c40e51fb2caa",
        "port" : 27017,
        "version" : "3.6.9",
        "gitVersion" : "167861a164723168adfaaa866f310cb94010428f"
    },
    "ok" : 1
}

        我们先不用关注执行计划中输出的内容细节,看几个主要的数据指标。

问题 分析
查询走全表扫描 stage的描述是COLLSCAN,这个表示全表扫描
执行耗时比较长 executionTimeMillis执行耗时在652毫秒,这个已经很长了
工作开销很大 works是MongoDB将操作分解为更细力度的操作单元,这里要耗费1000002个

        通过使用ensureIndex可以创建集合的索引,这里根据name属性创建对应的索引:

> db.author_test_collection.ensureIndex({"name": 1})
{
    "createdCollectionAutomatically" : false,
    "numIndexesBefore" : 1,
    "numIndexesAfter" : 2,
    "ok" : 1
}

        ensureIndex会在指定的key上建立索引,同时第二个参数表示该索引的方向,其中1表示升序,而-1表示降序。对于大多数查询场景来说,区别不大,因为对于索引的执行总是从中间开始,但是升序会保证最开始的数据会在内存中,这是一个细微的差别。

MongoDB的索引和MySQL索引类似,都是左匹配。

        使用getIndexes命令,可以查看当前集合上的索引情况。

> db.author_test_collection.getIndexes()
[
    {
        "v" : 2,
        "key" : {
            "_id" : 1
        },
        "name" : "_id_",
        "ns" : "test.author_test_collection"
    },
    {
        "v" : 2,
        "key" : {
            "name" : 1
        },
        "name" : "name_1",
        "ns" : "test.author_test_collection"
    }
]

        可以看到,集合author_test_collection上有两个索引,其中name_1是新添加的。新增索引后,再次执行先前的执行计划,看一下效果。

> db.author_test_collection.find({"name":"Author-3000"}).explain("executionStats")
{
    "queryPlanner" : {
        "plannerVersion" : 1,
        "namespace" : "test.author_test_collection",
        "indexFilterSet" : false,
        "parsedQuery" : {
            "name" : {
                "$eq" : "Author-3000"
            }
        },
        "winningPlan" : {
            "stage" : "FETCH",
            "inputStage" : {
                "stage" : "IXSCAN",
                "keyPattern" : {
                    "name" : 1
                },
                "indexName" : "name_1",
                "isMultiKey" : false,
                "multiKeyPaths" : {
                    "name" : [ ]
                },
                "isUnique" : false,
                "isSparse" : false,
                "isPartial" : false,
                "indexVersion" : 2,
                "direction" : "forward",
                "indexBounds" : {
                    "name" : [
                        "[\"Author-3000\", \"Author-3000\"]"
                    ]
                }
            }
        },
        "rejectedPlans" : [ ]
    },
    "executionStats" : {
        "executionSuccess" : true,
        "nReturned" : 1,
        "executionTimeMillis" : 1,
        "totalKeysExamined" : 1,
        "totalDocsExamined" : 1,
        "executionStages" : {
            "stage" : "FETCH",
            "nReturned" : 1,
            "executionTimeMillisEstimate" : 0,
            "works" : 2,
            "advanced" : 1,
            "needTime" : 0,
            "needYield" : 0,
            "saveState" : 0,
            "restoreState" : 0,
            "isEOF" : 1,
            "invalidates" : 0,
            "docsExamined" : 1,
            "alreadyHasObj" : 0,
            "inputStage" : {
                "stage" : "IXSCAN",
                "nReturned" : 1,
                "executionTimeMillisEstimate" : 0,
                "works" : 2,
                "advanced" : 1,
                "needTime" : 0,
                "needYield" : 0,
                "saveState" : 0,
                "restoreState" : 0,
                "isEOF" : 1,
                "invalidates" : 0,
                "keyPattern" : {
                    "name" : 1
                },
                "indexName" : "name_1",
                "isMultiKey" : false,
                "multiKeyPaths" : {
                    "name" : [ ]
                },
                "isUnique" : false,
                "isSparse" : false,
                "isPartial" : false,
                "indexVersion" : 2,
                "direction" : "forward",
                "indexBounds" : {
                    "name" : [
                        "[\"Author-3000\", \"Author-3000\"]"
                    ]
                },
                "keysExamined" : 1,
                "seeks" : 1,
                "dupsTested" : 0,
                "dupsDropped" : 0,
                "seenInvalidated" : 0
            }
        }
    },
    "serverInfo" : {
        "host" : "c40e51fb2caa",
        "port" : 27017,
        "version" : "3.6.9",
        "gitVersion" : "167861a164723168adfaaa866f310cb94010428f"
    },
    "ok" : 1
}

        可以看到stage已经从 COLLSCAN 变为了 IXSCAN,也就是说从全表扫描变为了按照索引扫描。有了索引,就好比查字典时有了字母的顺序,使得不用翻遍整个字典才能找到需要的内容,工作量也会减少。可以看到work只有4(2 + 2),从1000002下降到4,是非常显著的,相差了百万个量级。

        但是并不是我们将需要查询的字段都放到索引中就可以,有时我们的查询需要考虑顺序,比如:

db.author_test_collection.find({"name":"Author-2000"}).sort({"age":1})

        该查询用来查询名称为Author-2000的所有作者,同时按照age正序(小到大)排序返回。可以输出以下执行计划(重点部分)。

"executionStages" : {
            "stage" : "SORT",
            "nReturned" : 1,
            "executionTimeMillisEstimate" : 0,
            "works" : 5,
            "advanced" : 1,
            "needTime" : 3,
            "needYield" : 0,
            "saveState" : 0,
            "restoreState" : 0,
            "isEOF" : 1,
            "invalidates" : 0,
            "sortPattern" : {
                "age" : 1
            },
            "memUsage" : 118,
            "memLimit" : 33554432,
            "inputStage" : {
                "stage" : "SORT_KEY_GENERATOR",
                "nReturned" : 1,
                "executionTimeMillisEstimate" : 0,
                "works" : 3,
                "advanced" : 1,
                "needTime" : 1,
                "needYield" : 0,
                "saveState" : 0,
                "restoreState" : 0,
                "isEOF" : 1,
                "invalidates" : 0,
                "inputStage" : {
                    "stage" : "FETCH",
                    "nReturned" : 1,
                    "executionTimeMillisEstimate" : 0,
                    "works" : 2,
                    "advanced" : 1,
                    "needTime" : 0,
                    "needYield" : 0,
                    "saveState" : 0,
                    "restoreState" : 0,
                    "isEOF" : 1,
                    "invalidates" : 0,
                    "docsExamined" : 1,
                    "alreadyHasObj" : 0,
                    "inputStage" : {
                        "stage" : "IXSCAN",
                        "nReturned" : 1,
                        "executionTimeMillisEstimate" : 0,
                        "works" : 2,
                        "advanced" : 1,
                        "needTime" : 0,
                        "needYield" : 0,
                        "saveState" : 0,
                        "restoreState" : 0,
                        "isEOF" : 1,
                        "invalidates" : 0,
                        "keyPattern" : {
                            "name" : 1
                        },
                        "indexName" : "name_1",
                        "isMultiKey" : false,
                        "multiKeyPaths" : {
                            "name" : [ ]
                        },
                        "isUnique" : false,
                        "isSparse" : false,
                        "isPartial" : false,
                        "indexVersion" : 2,
                        "direction" : "forward",
                        "indexBounds" : {
                            "name" : [
                                "[\"Author-2000\", \"Author-2000\"]"
                            ]
                        },
                        "keysExamined" : 1,
                        "seeks" : 1,
                        "dupsTested" : 0,
                        "dupsDropped" : 0,
                        "seenInvalidated" : 0
                    }
                }
            }
        },
        "allPlansExecution" : [ ]

        可以看到首先查询的数据会消耗2个work通过IXSCAN索引扫描命中数据,然后经过FETCH阶段,消耗2个work将数据取出,取出的数据需要经历SORT_KEY_GENERATOR消耗1个work进行排序器的生成(虽然只有一个数据),最终通过SORT阶段消耗5个work将数据完成排序后返回。

总计开销是10个work

        对于数据的查询输入,有nameage两个参数,在age上有排序,下面在nameage上建立索引。

db.author_test_collection.ensureIndex({"name":1, "age":1})

        然后再次执行,可以看到执行计划有所变化。

"executionStages" : {
            "stage" : "FETCH",
            "nReturned" : 1,
            "executionTimeMillisEstimate" : 0,
            "works" : 3,
            "advanced" : 1,
            "needTime" : 0,
            "needYield" : 0,
            "saveState" : 0,
            "restoreState" : 0,
            "isEOF" : 1,
            "invalidates" : 0,
            "docsExamined" : 1,
            "alreadyHasObj" : 0,
            "inputStage" : {
                "stage" : "IXSCAN",
                "nReturned" : 1,
                "executionTimeMillisEstimate" : 0,
                "works" : 2,
                "advanced" : 1,
                "needTime" : 0,
                "needYield" : 0,
                "saveState" : 0,
                "restoreState" : 0,
                "isEOF" : 1,
                "invalidates" : 0,
                "keyPattern" : {
                    "name" : 1,
                    "age" : 1
                },
                "indexName" : "name_1_age_1",
                "isMultiKey" : false,
                "multiKeyPaths" : {
                    "name" : [ ],
                    "age" : [ ]
                },
                "isUnique" : false,
                "isSparse" : false,
                "isPartial" : false,
                "indexVersion" : 2,
                "direction" : "forward",
                "indexBounds" : {
                    "name" : [
                        "[\"Author-2000\", \"Author-2000\"]"
                    ],
                    "age" : [
                        "[MinKey, MaxKey]"
                    ]
                },
                "keysExamined" : 1,
                "seeks" : 1,
                "dupsTested" : 0,
                "dupsDropped" : 0,
                "seenInvalidated" : 0
            }
        }

        可以看到首先消耗2个work根据name_1_age_1的索引就可以拿到一个指定nameage排好序的结果,然后将其通过FETCH阶段,消耗3个work将数据返回即可。整个消耗work只有原来的一半。

        在建立索引时,不仅要考虑输入的场景字段,还要考虑会在哪些字段上排序。

每个集合上的索引默认最大个数是64个,一般来说是够用了。

唯一索引

        通过建立索引,可以让查询变得更加有效率,这里提到的索引和MySQL中的索引是一个含义。在关系数据库中,还有一种索引称之为唯一索引(Unique),它指的是这个索引能够唯一的确定一条记录,并且能够用这个约束来约束集合中的所有数据。

        在MongoDB中,每个集合天生就有一个唯一索引,_id,它表示一行数据的id。使用者也可以创建自己的唯一索引,例如:

db.author_test_collection.ensureIndex({"name":1, "age":1}, {"unique":true})

        通过创建索引时,增加{"unique":true},可以将该索引设置为唯一索引。

执行计划与Hint

        以如下执行计划为例,进行执行计划的内容分析。

{
    "queryPlanner" : {
        "plannerVersion" : 1,
        "namespace" : "test.author_test_collection",
        "indexFilterSet" : false,
        "parsedQuery" : {
            "name" : {
                "$eq" : "Author-2000"
            }
        },
        "winningPlan" : {
            "stage" : "FETCH",
            "inputStage" : {
                "stage" : "IXSCAN",
                "keyPattern" : {
                    "name" : 1,
                    "age" : 1
                },
                "indexName" : "name_1_age_1",
                "isMultiKey" : false,
                "multiKeyPaths" : {
                    "name" : [ ],
                    "age" : [ ]
                },
                "isUnique" : false,
                "isSparse" : false,
                "isPartial" : false,
                "indexVersion" : 2,
                "direction" : "forward",
                "indexBounds" : {
                    "name" : [
                        "[\"Author-2000\", \"Author-2000\"]"
                    ],
                    "age" : [
                        "[MinKey, MaxKey]"
                    ]
                }
            }
        },
        "rejectedPlans" : [
            
        ]
    },
    "executionStats" : {
        "executionSuccess" : true,
        "nReturned" : 1,
        "executionTimeMillis" : 1,
        "totalKeysExamined" : 1,
        "totalDocsExamined" : 1,
        "executionStages" : {
            "stage" : "FETCH",
            "nReturned" : 1,
            "executionTimeMillisEstimate" : 0,
            "works" : 3,
            "advanced" : 1,
            "needTime" : 0,
            "needYield" : 0,
            "saveState" : 0,
            "restoreState" : 0,
            "isEOF" : 1,
            "invalidates" : 0,
            "docsExamined" : 1,
            "alreadyHasObj" : 0,
            "inputStage" : {
                "stage" : "IXSCAN",
                "nReturned" : 1,
                "executionTimeMillisEstimate" : 0,
                "works" : 2,
                "advanced" : 1,
                "needTime" : 0,
                "needYield" : 0,
                "saveState" : 0,
                "restoreState" : 0,
                "isEOF" : 1,
                "invalidates" : 0,
                "keyPattern" : {
                    "name" : 1,
                    "age" : 1
                },
                "indexName" : "name_1_age_1",
                "isMultiKey" : false,
                "multiKeyPaths" : {
                    "name" : [ ],
                    "age" : [ ]
                },
                "isUnique" : false,
                "isSparse" : false,
                "isPartial" : false,
                "indexVersion" : 2,
                "direction" : "forward",
                "indexBounds" : {
                    "name" : [
                        "[\"Author-2000\", \"Author-2000\"]"
                    ],
                    "age" : [
                        "[MinKey, MaxKey]"
                    ]
                },
                "keysExamined" : 1,
                "seeks" : 1,
                "dupsTested" : 0,
                "dupsDropped" : 0,
                "seenInvalidated" : 0
            }
        },
        "allPlansExecution" : [
            
        ]
    }
    "ok" : 1
}

        在执行计划queryPlanner中会包含本地选取的执行计划,和被拒绝的执行计划。在executionStats中会有详细的执行计划过程说明,从整体上会有以下内容需要注意:

项目 含义
executionStats.executionSuccess 是否执行成功
executionStats.nReturned 查询的返回条数
executionStats.executionTimeMillis 整体执行时间
executionStats.totalKeysExamined 索引扫描次数
executionStats.totalDocsExamined 文档扫描次数

        在executionStages中有当前查询的不同阶段明细,也就是由这些Stage组成的本次查询,Stage好比阶段,一次查询由多个阶段来完成,阶段主要分为以下内容,它们负责不同的工作。

阶段名称 含义
COLLSCAN 全表扫描
IXSCAN 索引扫描
FETCH 根据索引去检索指定文档,有点回表的感觉
SORT 表明在内存中进行了排序,在开始未加age索引时有该阶段
LIMIT 使用limit限制返回数
SKIP 使用skip进行跳过
IDHACK 针对_id进行查询
COUNT count()之类进行count运算
PROJECTION 限定返回字段时候stage的返回
SHARDING_FILTER 通过mongos对分片数据进行查询
SHARD_MERGE 将各个分片返回数据进行merge

索引管理

        单个索引的名称长度一般不能超过121个字节,也就是60个字符,这个很容易做到。在之前建立索引时,只罗列了字段名,所以默认的索引名称都是字段+索引方向构成,可以在创建索引的时候,给一个好理解的名称。

db.author_test_collection.ensureIndex({"name":1, "age":1}, {"name":"index_name"})

        在创建索引时,可以在第二个参数传递索引的名字。

        索引的建立需要消耗不少资源,当索引建立时,mongodb对于请求不能够立即响应,因此建立索引的时间需要放在访问量较低的时候,同时也不能够停机进行索引建立。可以通过增加{"background": true}参数,例如:

db.author_test_collection.ensureIndex({"name":1, "age":1}, {"name":"index_name"}, {"background": true})

        这时,索引建立会在后台慢慢完成,MongoDB能够处理请求的同时完成索引在后台建立,虽然慢了些,但是不会阻塞请求。

地理空间索引

        MongoDB支持空间索引,因此可以方便的完成基于地理坐标的查询和搜索。我们定义一个Poi对象:

public class Poi implements Serializable {
    private static final long serialVersionUID = -6595624172489524435L;
    /**
     * ID
     */
    private Long num;
    /**
     * 名称
     */
    private String name;
    /**
     * 城市
     */
    private String cityName;
    /**
     * 地区
     */
    private String areaName;
    /**
     * 街道
     */
    private String streetName;
    /**
     * 位置
     */
    private Double[] location;
    /**
     * 类目名
     */
    private String categoryName;
    /**
     * 属性名
     */
    private String propertyName;
    /**
     * 联系方式
     */
    private String[] contactNumbers;
}

        该对象声明了地理位置的名称,市、区和街道地址,以及地理位置(经纬度),通过使用以下测试将数据完成初始化。

@Test
public void init() throws Exception {
    Path path = Paths.get("src/test/resources", "hangzhou-2018-poi.csv");
    List<String> allLines = Files.readAllLines(path);

    allLines.parallelStream()
            .map(s -> {
                String[] split = s.split(",");
                if (split.length == 8) {
                    try {
                        Long num = Long.parseLong(split[0].trim());
                        String name = split[1].trim();
                        String streetName = split[2].trim();
                        Double longitude = Double.parseDouble(split[3].trim());
                        Double latitude = Double.parseDouble(split[4].trim());
                        String[] address = split[5].split(";");
                        String cityName = null;
                        String areaName = null;
                        if (address.length == 2) {
                            cityName = address[0].trim();
                            areaName = address[1].trim();
                        } else {
                            cityName = address[0].trim();
                        }
                        String[] cate = split[6].split(";");
                        String categoryName = cate[0].trim();
                        String propertyName = cate[1].trim();
                        String[] contacts = split[7].split(";");

                        Poi poi = new Poi();
                        poi.setNum(num);
                        poi.setName(name);
                        poi.setLocation(new Double[]{longitude, latitude});
                        poi.setCityName(cityName);
                        poi.setAreaName(areaName);
                        poi.setStreetName(streetName);
                        poi.setCategoryName(categoryName);
                        poi.setPropertyName(propertyName);
                        poi.setContactNumbers(contacts);
                        return poi;
                    } catch (Exception ex) {
                        System.err.println(s);
                        return null;
                    }
                } else {
                    return null;
                }
            })
            .filter(Objects::nonNull)
            .forEach(poi -> mongoTemplate.save(poi, "poi"));
}

上述测试文件和类型定义,可以在项目工程mongodb-guide-chapter5中找到。当前数据大约有11万左右的POI信息。

        在开始检索之前,需要建立地理位置索引,这里可以用如下命令进行建立:

db.poi.createIndex({"location":"2d"})

        建立完成后,使用MongoTemplate来查询一下,离笔者家最近的(2公里)的火锅店。

Query query = new Query();
Criteria category = Criteria.where("categoryName").is("美食");
Criteria property = Criteria.where("propertyName").is("火锅");
query.addCriteria(category).addCriteria(property);
NearQuery nearQuery = NearQuery.near(30.2542303641, 120.029316088)
        .maxDistance(2/111.0d)
        .distanceMultiplier(111)
        .query(query);

GeoResults<Poi> poi = mongoTemplate.geoNear(nearQuery, Poi.class, "poi");
System.out.println(poi);
poi.forEach(System.out::println);

        可以看到对应的输出:

GeoResults: [averageDistance: 1.0906241539249202 org.springframework.data.geo.CustomMetric@10650953, results: GeoResult [content: Poi{num=18631, name='锅sir时尚火锅(西溪店)', cityName='杭州市', areaName='余杭区', streetName='五常街道丰岭路150号', location=[30.2518344901, 120.027799463], categoryName='美食', propertyName='火锅', contactNumbers=[15088631350]}, distance: 0.31474643777909406 org.springframework.data.geo.CustomMetric@10650953, ],GeoResult [content: Poi{num=929494, name='华记肉蟹煲', cityName='杭州市', areaName='余杭区', streetName='五常大道156号', location=[30.2478319766, 120.02888752], categoryName='美食', propertyName='火锅', contactNumbers=[13858025848]}, distance: 0.7118123988834714 org.springframework.data.geo.CustomMetric@10650953, ],GeoResult [content: Poi{num=138637, name='贵州闵孔维火锅', cityName='杭州市', areaName='余杭区', streetName='乐玛摄影乐品美学馆南(联胜路东)', location=[30.2481295413, 120.025993642], categoryName='美食', propertyName='火锅', contactNumbers=[15158096376]}, distance: 0.771100041115541 org.springframework.data.geo.CustomMetric@10650953, ],GeoResult [content: Poi{num=154459, name='铜顺祥老北京炭火锅(五常店)', cityName='杭州市', areaName='余杭区', streetName='五常大道169-2号楼达峰科技园对面', location=[30.2464425735, 120.028698033], categoryName='美食', propertyName='火锅', contactNumbers=[13396553333]}, distance: 0.8671627646732913 org.springframework.data.geo.CustomMetric@10650953, ],GeoResult [content: Poi{num=29323, name='重庆老基地主题火锅(杭州总店)', cityName='杭州市', areaName='余杭区', streetName='留下五常街道天目山西路15号宏丰生活广场北面C2-103号', location=[30.240354346, 120.028189614], categoryName='美食', propertyName='火锅', contactNumbers=[0571-88667515]}, distance: 1.5453050765681462 org.springframework.data.geo.CustomMetric@10650953, ],GeoResult [content: Poi{num=300403, name='锅内锅外(西溪印象城店)', cityName='杭州市', areaName='西湖区', streetName='五常大道1号杭州西溪印象城L401', location=[30.249636746, 120.043730212], categoryName='美食', propertyName='火锅', contactNumbers=[0571-85100078]}, distance: 1.6792517080143305 org.springframework.data.geo.CustomMetric@10650953, ],GeoResult [content: Poi{num=313858, name='煮火火锅', cityName='杭州市', areaName='西湖区', streetName='五常大道1号(近西溪湿地公园)西溪印象城L2层L248', location=[30.2495355104, 120.044319313], categoryName='美食', propertyName='火锅', contactNumbers=[0571-88639773]}, distance: 1.7449906504405666 org.springframework.data.geo.CustomMetric@10650953, ]]
GeoResult [content: Poi{num=18631, name='锅sir时尚火锅(西溪店)', cityName='杭州市', areaName='余杭区', streetName='五常街道丰岭路150号', location=[30.2518344901, 120.027799463], categoryName='美食', propertyName='火锅', contactNumbers=[15088631350]}, distance: 0.31474643777909406 org.springframework.data.geo.CustomMetric@10650953, ]
GeoResult [content: Poi{num=929494, name='华记肉蟹煲', cityName='杭州市', areaName='余杭区', streetName='五常大道156号', location=[30.2478319766, 120.02888752], categoryName='美食', propertyName='火锅', contactNumbers=[13858025848]}, distance: 0.7118123988834714 org.springframework.data.geo.CustomMetric@10650953, ]
GeoResult [content: Poi{num=138637, name='贵州闵孔维火锅', cityName='杭州市', areaName='余杭区', streetName='乐玛摄影乐品美学馆南(联胜路东)', location=[30.2481295413, 120.025993642], categoryName='美食', propertyName='火锅', contactNumbers=[15158096376]}, distance: 0.771100041115541 org.springframework.data.geo.CustomMetric@10650953, ]
GeoResult [content: Poi{num=154459, name='铜顺祥老北京炭火锅(五常店)', cityName='杭州市', areaName='余杭区', streetName='五常大道169-2号楼达峰科技园对面', location=[30.2464425735, 120.028698033], categoryName='美食', propertyName='火锅', contactNumbers=[13396553333]}, distance: 0.8671627646732913 org.springframework.data.geo.CustomMetric@10650953, ]
GeoResult [content: Poi{num=29323, name='重庆老基地主题火锅(杭州总店)', cityName='杭州市', areaName='余杭区', streetName='留下五常街道天目山西路15号宏丰生活广场北面C2-103号', location=[30.240354346, 120.028189614], categoryName='美食', propertyName='火锅', contactNumbers=[0571-88667515]}, distance: 1.5453050765681462 org.springframework.data.geo.CustomMetric@10650953, ]
GeoResult [content: Poi{num=300403, name='锅内锅外(西溪印象城店)', cityName='杭州市', areaName='西湖区', streetName='五常大道1号杭州西溪印象城L401', location=[30.249636746, 120.043730212], categoryName='美食', propertyName='火锅', contactNumbers=[0571-85100078]}, distance: 1.6792517080143305 org.springframework.data.geo.CustomMetric@10650953, ]
GeoResult [content: Poi{num=313858, name='煮火火锅', cityName='杭州市', areaName='西湖区', streetName='五常大道1号(近西溪湿地公园)西溪印象城L2层L248', location=[30.2495355104, 120.044319313], categoryName='美食', propertyName='火锅', contactNumbers=[0571-88639773]}, distance: 1.7449906504405666 org.springframework.data.geo.CustomMetric@10650953, ]

        返回的结果包含了符合要求的Poi信息,给出了平均的距离,想尝试的同学可以试着写一个找寻自家最近加油站的程序。

上一篇下一篇

猜你喜欢

热点阅读