【问题标题】:Joins on spatial mysql indexes连接空间 mysql 索引
【发布时间】:2012-01-04 21:12:16
【问题描述】:

我有两张表:一张是点,另一张是多边形。

CREATE TABLE `points` (
  `id` int(10) unsigned NOT NULL AUTO_INCREMENT,
  `point` point NOT NULL,
  PRIMARY KEY (`id`)
) ENGINE=MyISAM;

CREATE TABLE `ranges` (
  `id` int(10) unsigned NOT NULL AUTO_INCREMENT,
  `poly` polygon NOT NULL,
  PRIMARY KEY (`id`),
  SPATIAL KEY `poly` (`poly`)
) ENGINE=MyISAM;

我想将范围连接到多边形内点上的点。查询看起来很简单:

SELECT * 
  FROM points 
    LEFT JOIN ranges 
      ON MBRCONTAINS(poly, point) 
  WHERE points.id = 2;

这个查询运行速度很快并且使用索引,解释的一部分:

表 |类型 |可能的键 |关键 | key_len
范围 |范围 |聚 |聚 | 34

但是,当我尝试加入表 points 中的几行时:

SELECT * 
  FROM points 
   LEFT JOIN ranges 
    ON MBRCONTAINS(poly, point) 
  WHERE points.id IN (1,2,3);

一切都崩溃了:

+----+-------------+------------+-------+--- ------------+---------+---------+------+--------+- ------------+
|编号 |选择类型 |表|类型 |可能的键 |关键 | key_len |参考 |行 |额外 |
+----+-------------+------------+-------+--------- ------+---------+----------+------+--------+------- ------+
| 1 |简单 |积分 |范围 |初级 |初级 | 4 |空 | 3 |使用位置 |
| 1 |简单 |范围 |全部 |聚 |空 |空 |空 | 155183 | |
+----+-------------+------------+-------+--------- ------+---------+----------+------+--------+------- ------+

添加FORCE INDEX (poly) 没有帮助。

测试查询的样本数据(对不起,只有php版本,我不常用SQL程序):

//points
for($i=0;$i<=500;$i++) {
    $point = mt_rand();
    mysql_query('INSERT INTO points (point) VALUES (POINTFROMWKB(POINT('.$point.', 0)))');
}

$qty = 20000;
$max = mt_getrandmax();
$add = $max / $qty
$end = 0;

//polys
while($end < $max) {
    $start = $end;
    $end = mt_rand($start, $start + $add);
    mysql_query('INSERT INTO ranges (poly) VALUES (
        GEOMFROMWKB(POLYGON(LINESTRING(
            POINT('.$start.', -1),
            POINT('.$end.',   -1),
            POINT('.$end.',    1),
            POINT('.$start.',  1),
            POINT('.$start.', -1)
          )))
    )');
}

【问题讨论】:

  • 您能解释一下“一切都崩溃了”是什么意思吗?错误信息难以阅读?!
  • 您能否为这些表发布一个示例插入,您是否尝试重写该 IN 条件以使用联接?
  • @David 'everthing wrong' - 查询不使用索引。
  • @Pentium10 我将在几个小时内制作样本插入。我可以用'points.id

标签: mysql optimization indexing geospatial


【解决方案1】:

我认为这是因为 MySQL 不支持合并空间索引。不确定它是否仍然正确,但我在过去的某个地方读过它。如果有 OR 语句,则不使用空间索引

在你的情况下,你在哪里做 points.id = 1,这是一个直接选择,返回一个结果,在 mbrcontains 中使用。那使用索引。

当您添加 points.in (1,2,3) 时,将返回 3 个结果,每个结果都需要映射到范围表,因此不起作用

结果

id  select_type     table   type    possible_keys   key     key_len     ref     rows    filtered    Extra
1   SIMPLE  points  range   PRIMARY     PRIMARY     4   NULL    3   100.00  Using where
1   SIMPLE  ranges  ALL     poly    NULL    NULL    NULL    6467418     100.00   

您可以通过执行以下操作在不使用点表的情况下简化测试:SELECT * FROM ranges where mbrcontains(poly, GEOMFROMWKB(POINT(0, 0)))

id  select_type     table   type    possible_keys   key     key_len     ref     rows    filtered    Extra
1   SIMPLE  ranges  range   poly    poly    34  NULL    1   100.00  Using where

现在这个; SELECT * FROM 范围,其中 mbrcontains(poly, GEOMFROMWKB(POINT(0, 0))) OR mbrcontains(poly, GEOMFROMWKB(POINT(10, 10)))

结果

id  select_type     table   type    possible_keys   key     key_len     ref     rows    filtered    Extra
1   SIMPLE  ranges  ALL     poly    NULL    NULL    NULL    6467418     100.00  Using where

请注意,在第二种情况下,您没有使用索引,只是在扫描。

您可以通过为每个特定点创建 UNION 来强制查询使用索引,但我不确定这是否会更快。我在本地做了一些测试,比你的第一个查询慢了一点。

EXPLAIN EXTENDED 
SELECT *
FROM points
FORCE INDEX (PRIMARY )
LEFT JOIN ranges
FORCE INDEX ( poly ) ON mbrcontains( poly, point )
WHERE points.id = 1
UNION DISTINCT
SELECT *
FROM points
FORCE INDEX (PRIMARY )
LEFT JOIN ranges
FORCE INDEX ( poly ) ON mbrcontains( poly, point )
WHERE points.id = 2
UNION DISTINCT
SELECT *
FROM points
FORCE INDEX (PRIMARY )
LEFT JOIN ranges
FORCE INDEX ( poly ) ON mbrcontains( poly, point )
WHERE points.id = 3

结果

id  select_type     table   type    possible_keys   key     key_len     ref     rows    filtered    Extra
1   PRIMARY     points  const   PRIMARY     PRIMARY     4   const   1   100.00   
1   PRIMARY     ranges  range   poly    poly    34  NULL    1   100.00  Using where
2   UNION   points  const   PRIMARY     PRIMARY     4   const   1   100.00   
2   UNION   ranges  range   poly    poly    34  NULL    1   100.00  Using where
3   UNION   points  const   PRIMARY     PRIMARY     4   const   1   100.00   
3   UNION   ranges  range   poly    poly    34  NULL    1   100.00  Using where
NULL    UNION RESULT    <union1,2,3>    ALL     NULL    NULL    NULL    NULL    NULL    NULL     

【讨论】:

【解决方案2】:

我已经成功地使用了类似的查询,只有一个数据模型不同:点数据库上的空间键。就我而言:

CREATE TABLE geopoints (
  pid int(11) NOT NULL AUTO_INCREMENT,
  description varchar(255) NOT NULL DEFAULT '',
  geopoint point NOT NULL,
  PRIMARY KEY (pid),
  SPATIAL KEY geopoint (geopoint) 
) ENGINE=MyISAM DEFAULT CHARSET=utf8;

在这样的查询中一切都很顺利:

SELECT pt.pid, x(geopoint), Y(geopoint), pl.pid, AsText(geopolygon) 
  FROM geopoints pt INNER JOIN geopolygons pl ON MBRCONTAINS(geopolygon, geopoint)
 WHERE pt.pid IN (1,2,4,5) AND pl.pid BETWEEN 1 AND 5;

我的两分钱,

【讨论】:

    【解决方案3】:

    如果您要处理的只是正方形,我将只处理表中的 4 个数字,这些数字可以表示顶部、左侧、高度、宽度的索引,然后在您的问题点的“X”坐标处运行您的查询Left、Left+Width 和 Top、Top+Height 之间的“Y”坐标之间。

    【讨论】:

    • 但是我用几何,因为它比BETWEEN快很多。
    • 实际上,MySQL 无法加入 BETWEEN。所以,我想在空间索引上进行这样的连接。但是,仍然没有成功。
    【解决方案4】:

    您可以通过将 subselect 封装在函数中来强制 MySQL 使用索引。

    例如:

    DELIMITER $$
    
    DROP FUNCTION IF EXISTS `GetMyPolygon`$$
    CREATE DEFINER=`root`@`localhost` FUNCTION `GetMyPolygon`(p POINT) RETURNS INTEGER
    BEGIN
    
    DECLARE ret INTEGER;
    
    SET ret = (SELECT range_id FROM ranges WHERE ST_CONTAINS(poly, p) ;
    
    RETURN ret;
    
    END$$
    

    如果多边形不重叠,则可以制作:

    SELECT *, GetMyPolygon(point) FROM points 
    

    如果它们确实重叠,但有一些重叠,您可以创建一个类似的函数,使 group_concat...

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2015-05-28
      • 2012-10-03
      • 2020-04-19
      相关资源
      最近更新 更多