我有两个表:一个有点,另一个有多边。
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;
此查询可以快速运行并使用索引,这是解释的一部分:
table | type | possible_keys | key | key_len ranges | range | poly | poly | 34
但是,当我尝试加入表points
中的几行时:
SELECT *
FROM points
LEFT JOIN ranges
ON MBRCONTAINS(poly, point)
WHERE points.id IN (1,2,3);
一切都崩溃了:
+----+-------------+------------+-------+---------------+---------+---------+------+--------+-------------+ | id | select_type | table | type | possible_keys | key | key_len | ref | rows | Extra | +----+-------------+------------+-------+---------------+---------+---------+------+--------+-------------+ | 1 | SIMPLE | points | range | PRIMARY | PRIMARY | 4 | NULL | 3 | Using where | | 1 | SIMPLE | ranges | ALL | poly | NULL | NULL | NULL | 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)
)))
)');
}
答案 0 :(得分:6)
我认为这是因为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 range其中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)))或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
答案 1 :(得分:3)
我已经成功使用了类似的查询,数据模型只有一个区别:点数据库上的空间键。就我而言:
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;
我的两分钱,
答案 2 :(得分:0)
如果您正在处理的是正方形,我只会处理表格中的4个数字,这些数字可以被编入索引,代表顶部,左侧,高度,宽度,然后运行您的查询,其中您的点有“X”坐标在左,左+宽和顶部,顶部+高度之间的“Y”坐标之间。
答案 3 :(得分:0)
您可以通过将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 ...