MongoDB-第五章-索引

作者: weipeng2k | 来源:发表于2020-03-16 10:20 被阅读0次

    索引

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

    索引简介

            首先我们准备一个集合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信息,给出了平均的距离,想尝试的同学可以试着写一个找寻自家最近加油站的程序。

    相关文章

      网友评论

        本文标题:MongoDB-第五章-索引

        本文链接:https://www.haomeiwen.com/subject/akbwehtx.html