MongoDB数据库的地理位置索引是其重要特性之一,往往是大家选择MongoDB的原因之一。
该特性的功能可以说就是专门用来处理地图相关问题而存在的。
下面我们通过一些测试来考察一下其对关系型数据库的优化效果。

一、初始化Oracle与MongoDB数据库的测试环境

1、创建Oracle数据库的测试表mapinfo,其中category字段表示目标地点的类别,如“hotel”,“food”,“shop”等,xpos和ypos表示地理位置的坐标。

drop table mapinfo purge;
create table mapinfo (id number, category varchar2(10), 
name varchar(15), xpos number, ypos number);

2、创建mapinfo表的横坐标和纵坐标的字段索引,查询地理位置时,可以使用索引组合(Index Combine)来优化。

create index mapinfo_x on mapinfo (xpos);
create index mapinfo_y on mapinfo (ypos);

3、初始化随机创建10万行地理位置记录。

declare
icat varchar2(10);
x number;
y number;
n number;
begin
for i in 1..100000 loop
x:=trunc(dbms_random.value * 100, 4);
y:=trunc(dbms_random.value * 100, 4);
n:=trunc(dbms_random.value * 100);
select decode(trunc(dbms_random.value * 10),
1,'relax', 2,'hotel', 3,'marry', 4,'woman', 5,'food', 
6,'shop', 7,'kid', 8,'sport', 9,'home', 10,'car', 'other') 
into icat from dual;
insert into mapinfo values (i, icat, icat||n, x, y);
end loop;
commit;
end;
/

4、收集mapinfo表的统计信息。

exec dbms_stats.gather_table_stats(user,'mapinfo',cascade=>true)

5、导出mapinfo的10万行记录到JSON文件。

set lines 2000
set trim on
set trims on
set pages 0
spool mapinfo.json
select '{ "_id" : ' || id || ', "category" : "' || category || '", "name" : "' || name ||
       '", "location" : [' || xpos || ',' || ypos || '] }'
  from mapinfo
 order by id;
spool off

6、将JSON文件导入到MongoDB数据库中,并为其创建2D地理位置索引(Geospatial Index)。

mongoimport -h 127.0.0.1:28001 -d map -c mapinfo --file mapinfo.json 

db.mapinfo.ensureIndex({location:"2d"})

二、查询场景测试

1、以[50,50]为中心,查询一个单位的方形区域([49,49]~[51,51])的地点信息。

Oracle数据库:

SQL> select *
  2    from mapinfo
  3   where (xpos between 50 - 1 and 50 + 1)
  4     and (ypos between 50 - 1 and 50 + 1);

已选择37行。

已用时间:  00: 00: 00.30

执行计划
-------------------------------------------------------------------------------------------------
| Id  | Operation                           | Name      | Rows  | Bytes | Cost (%CPU)| Time     |
-------------------------------------------------------------------------------------------------
|   0 | SELECT STATEMENT                    |           |    40 |  1080 |    24   (9)| 00:00:01 |
|   1 |  TABLE ACCESS BY INDEX ROWID BATCHED| MAPINFO   |    40 |  1080 |    24   (9)| 00:00:01 |
|   2 |   BITMAP CONVERSION TO ROWIDS       |           |       |       |            |          |
|   3 |    BITMAP AND                       |           |       |       |            |          |
|   4 |     BITMAP CONVERSION FROM ROWIDS   |           |       |       |            |          |
|   5 |      SORT ORDER BY                  |           |       |       |            |          |
|*  6 |       INDEX RANGE SCAN              | MAPINFO_X |       |       |     7   (0)| 00:00:01 |
|   7 |     BITMAP CONVERSION FROM ROWIDS   |           |       |       |            |          |
|   8 |      SORT ORDER BY                  |           |       |       |            |          |
|*  9 |       INDEX RANGE SCAN              | MAPINFO_Y |       |       |     7   (0)| 00:00:01 |
-------------------------------------------------------------------------------------------------

MongoDB数据库:

> db.mapinfo.find({"location" : {"$within" : {"$box" : [[49,49],[51,51]]}}}).explain()
{
        "cursor" : "GeoBrowse-box",
        "isMultiKey" : false,
        "n" : 37,
        "nscannedObjects" : 37,
        "nscanned" : 37,
        "nscannedObjectsAllPlans" : 37,
        "nscannedAllPlans" : 37,
        "scanAndOrder" : false,
        "indexOnly" : false,
        "nYields" : 0,
        "nChunkSkips" : 0,
        "millis" : 2,
}

同样返回了37行记录,其中Oracle耗时300ms,而MongoDB仅耗时2ms。

2、以[50,50]为中心,查询一个单位的圆形区域(以[50,50]为圆心且半径为1的区域)的地点信息。

Oracle数据库:

SQL> select *
  2    from mapinfo
  3   where sqrt(power(xpos - 50, 2) + power(ypos - 50, 2)) <= 1;

已选择29行。

已用时间:  00: 00: 00.94

执行计划
-----------------------------------------------------------------------------
| Id  | Operation         | Name    | Rows  | Bytes | Cost (%CPU)| Time     |
-----------------------------------------------------------------------------
|   0 | SELECT STATEMENT  |         |    29 |   783 |   139   (3)| 00:00:01 |
|*  1 |  TABLE ACCESS FULL| MAPINFO |    29 |   783 |   139   (3)| 00:00:01 |
-----------------------------------------------------------------------------

MongoDB数据库:

> db.mapinfo.find({"location" : {"$within" : {"$center" : [[50,50],1]}}}).explain()
{
        "cursor" : "GeoBrowse-circle",
        "isMultiKey" : false,
        "n" : 29,
        "nscannedObjects" : 29,
        "nscanned" : 29,
        "nscannedObjectsAllPlans" : 29,
        "nscannedAllPlans" : 29,
        "scanAndOrder" : false,
        "indexOnly" : false,
        "nYields" : 0,
        "nChunkSkips" : 0,
        "millis" : 2,
}

同样返回了29行记录,其中Oracle耗时940ms,而MongoDB仅耗时2ms。

3、以[50,50]为中心,查询最近的三家“hotel”的地点信息。

Oracle数据库:

SQL> select *
  2    from (select *
  3            from mapinfo
  4           where category = 'hotel'
  5           order by sqrt(power(xpos - 50, 2) + power(ypos - 50, 2)))
  6   where rownum <= 3;

已用时间:  00: 00: 00.33

执行计划
-----------------------------------------------------------------------------------
| Id  | Operation               | Name    | Rows  | Bytes | Cost (%CPU)| Time     |
-----------------------------------------------------------------------------------
|   0 | SELECT STATEMENT        |         |     3 |   165 |   138   (2)| 00:00:01 |
|*  1 |  COUNT STOPKEY          |         |       |       |            |          |
|   2 |   VIEW                  |         | 10000 |   537K|   138   (2)| 00:00:01 |
|*  3 |    SORT ORDER BY STOPKEY|         | 10000 |   263K|   138   (2)| 00:00:01 |
|*  4 |     TABLE ACCESS FULL   | MAPINFO | 10000 |   263K|   137   (1)| 00:00:01 |
-----------------------------------------------------------------------------------

MongoDB数据库:

> db.mapinfo.find({"category" : "hotel", location:{$near: [50,50]}}).limit(3).explain()
{
        "cursor" : "GeoSearchCursor",
        "isMultiKey" : false,
        "n" : 3,
        "nscannedObjects" : 20,
        "nscanned" : 50,
        "nscannedObjectsAllPlans" : 20,
        "nscannedAllPlans" : 50,
        "scanAndOrder" : false,
        "indexOnly" : false,
        "nYields" : 0,
        "nChunkSkips" : 0,
        "millis" : 1,
}

返回了3行同样的记录,其中Oracle耗时330ms,而MongoDB仅耗时1ms。

可见由于MongoDB的2D地理位置索引的存在,其执行时间的优势是非常明显的,而且对于表or集合的建模是非常简单方便的。
如果需要存储的地理位置较多,可以采用MongoDB的Shard特性来进行分片存储。

Trackback

no comment untill now

Add your comment now

切换到手机版