使用点优化空间 mysql 查询
Optimising spatial mysql query with point
我在 MySQL 5.7 上有两个 table 看起来像这样:
create table places
(
id int auto_increment primary key,
position point null comment 'Coordinates of the city.',
constraint places_position_uindex
unique (position)
);
create table place_names
(
id int auto_increment primary key,
place_id int not null comment 'ID of place in table places.',
name char(255) not null comment 'Name of the place in the given language.',
country char(255) not null comment 'Name of the place''s country in the given language.',
language char(3) not null comment 'ISO 3 code of the language this record is in.'
);
create index place_names_language_index
on place_names (language);
create index place_names_name_language_index
on place_names (name, language);
我正在构建一个查询,以根据距给定点的距离获取给定地点的名称。我目前有:
SELECT
name,
ST_DISTANCE_SPHERE(position, p.point) AS distance,
administration,
country
FROM place_names
JOIN places ON place_names.place_id = places.id
JOIN (
SELECT
POINT(?, ?) AS point
) AS p
WHERE language = 'ENG'
ORDER BY distance
LIMIT 10;
如果我 EXPLAIN
这个查询我得到:
id
select_type
table
partitions
type
possible_keys
key
key_len
ref
rows
filtered
Extra
1
PRIMARY
<derived2>
NULL
ALL
NULL
NULL
NULL
NULL
1
100
Using temporary; Using filesort
1
PRIMARY
place_names
NULL
ref
place_names_language_index
place_names_language_index
12
const
1368960
100
NULL
1
PRIMARY
places
NULL
eq_ref
PRIMARY
PRIMARY
4
msdplaces.place_names.place_id
1
100
NULL
2
DERIVED
NULL
NULL
NULL
NULL
NULL
NULL
NULL
NULL
NULL
No tables used
如您所见,table 相当大(1368960 行)并且将来会变得更大。我想尽可能地减少查找行(例如,通过将它们限制在 80 公里的半径内,甚至在计算 ST_DISTANCE_SPHERE 之间的给定点周围仅 1 lon/lat 度)点和行。或者任何其他优化可以使查询更快,因为目前它慢得无法使用。
到目前为止我在互联网上找到的所有内容都来自 5.7 版本之前,因此它必须手动计算距离而不是使用本机 POINT 数据类型和 ST_DISTANCE_SPHERE 函数 - 这些比处理快得多手动三角函数,所以我想保留它们,但我不反对将 POINT 列拆分为单独的纬度和经度,如果这应该有优势的话。
如何优化此查询,使 table 大小对性能的影响尽可能小?
编辑:
我在 position
上添加了一个空间索引
create spatial index position
on places (position);
并将查询更改为以下内容以尝试使用索引,但它似乎根本没有被使用:
explain select
name,
ST_Distance_Sphere(position, p.point) as distance,
administration,
country
FROM place_names
join places on place_names.place_id = places.id
join (
select
POINT(30.5315, 56.3396) as point
) as p
WHERE
MBRContains(ST_GeomFromText('Polygon((29.0 55.0, 29.0 57.0, 31.0 57.0, 29.0 57.0, 29.0 55.0))'), places.position)
and
language = 'ENG'
order by distance
limit 10;
(请注意,为了添加索引,我必须使 position
NOT NULL。)结果:
id
select_type
table
partitions
type
possible_keys
key
key_len
ref
rows
filtered
Extra
1
PRIMARY
<derived2>
NULL
ALL
NULL
NULL
NULL
NULL
1
100
Using where; Using temporary; Using filesort
1
PRIMARY
place_names
NULL
ref
place_names_language_index
place_names_language_index
12
const
1368960
100
NULL
1
PRIMARY
places
NULL
eq_ref
PRIMARY
PRIMARY
4
mydb.place_names.place_id
1
100
NULL
2
DERIVED
NULL
NULL
NULL
NULL
NULL
NULL
NULL
NULL
NULL
No tables used
结果似乎与没有查询的 MBRContains() 部分相同,我仍然看到可怕的“行数 = 1368960”。据我了解,这意味着行根本不受该子句的限制。我还尝试交换 from
和 join
以使主要 table 成为 places
,但没有任何变化。
你必须扫描所有 1368960 个点并检查到每个点的距离。这很费时间。
所有优化都涉及将搜索限制在“边界框”内。下面显示了使用 SPATIAL
索引以及其他 4 个索引的方法。
原来要解决这个问题,我需要的是:
- 使
position
列 NOT NULL(POINT 不支持 DEFAULT,因此我手动将所有空值设置为 POINT(0, 0) 并且在插入记录时也必须这样做)。这是对索引的要求:
ALTER TABLE places ADD SPATIAL INDEX (position)
.
- 使用 MBRContains() 将查询限制为基于
position
的更少元素。当然,MBRWithin() 也可以。实际上,我将不得不根据纬度和经度手动构建边界框。
单独这样做似乎不起作用,但后来我发现主要问题不在空间列上,而是在连接上:place_id 列没有索引!哎呀
所以这是我最后的查询:
SELECT
p.id,
ST_Distance_Sphere(p.position, POINT(30.5315, 56.3396)) AS distance,
pn.name,
pn.administration,
pn.country
FROM (
SELECT id, position
FROM places
WHERE MBRContains(ST_GeomFromText('Polygon((29 55, 29 57, 31 57, 29 57, 29 55))'), position)
) p
JOIN place_names pn ON p.id = pn.place_id
WHERE pn.language = 'ENG'
ORDER BY distance
LIMIT 10;
感谢 Rick James 和 Akina 的建议和指点。希望对路过的人有所帮助
我在 MySQL 5.7 上有两个 table 看起来像这样:
create table places
(
id int auto_increment primary key,
position point null comment 'Coordinates of the city.',
constraint places_position_uindex
unique (position)
);
create table place_names
(
id int auto_increment primary key,
place_id int not null comment 'ID of place in table places.',
name char(255) not null comment 'Name of the place in the given language.',
country char(255) not null comment 'Name of the place''s country in the given language.',
language char(3) not null comment 'ISO 3 code of the language this record is in.'
);
create index place_names_language_index
on place_names (language);
create index place_names_name_language_index
on place_names (name, language);
我正在构建一个查询,以根据距给定点的距离获取给定地点的名称。我目前有:
SELECT
name,
ST_DISTANCE_SPHERE(position, p.point) AS distance,
administration,
country
FROM place_names
JOIN places ON place_names.place_id = places.id
JOIN (
SELECT
POINT(?, ?) AS point
) AS p
WHERE language = 'ENG'
ORDER BY distance
LIMIT 10;
如果我 EXPLAIN
这个查询我得到:
id | select_type | table | partitions | type | possible_keys | key | key_len | ref | rows | filtered | Extra |
---|---|---|---|---|---|---|---|---|---|---|---|
1 | PRIMARY | <derived2> | NULL | ALL | NULL | NULL | NULL | NULL | 1 | 100 | Using temporary; Using filesort |
1 | PRIMARY | place_names | NULL | ref | place_names_language_index | place_names_language_index | 12 | const | 1368960 | 100 | NULL |
1 | PRIMARY | places | NULL | eq_ref | PRIMARY | PRIMARY | 4 | msdplaces.place_names.place_id | 1 | 100 | NULL |
2 | DERIVED | NULL | NULL | NULL | NULL | NULL | NULL | NULL | NULL | NULL | No tables used |
如您所见,table 相当大(1368960 行)并且将来会变得更大。我想尽可能地减少查找行(例如,通过将它们限制在 80 公里的半径内,甚至在计算 ST_DISTANCE_SPHERE 之间的给定点周围仅 1 lon/lat 度)点和行。或者任何其他优化可以使查询更快,因为目前它慢得无法使用。
到目前为止我在互联网上找到的所有内容都来自 5.7 版本之前,因此它必须手动计算距离而不是使用本机 POINT 数据类型和 ST_DISTANCE_SPHERE 函数 - 这些比处理快得多手动三角函数,所以我想保留它们,但我不反对将 POINT 列拆分为单独的纬度和经度,如果这应该有优势的话。
如何优化此查询,使 table 大小对性能的影响尽可能小?
编辑:
我在 position
create spatial index position
on places (position);
并将查询更改为以下内容以尝试使用索引,但它似乎根本没有被使用:
explain select
name,
ST_Distance_Sphere(position, p.point) as distance,
administration,
country
FROM place_names
join places on place_names.place_id = places.id
join (
select
POINT(30.5315, 56.3396) as point
) as p
WHERE
MBRContains(ST_GeomFromText('Polygon((29.0 55.0, 29.0 57.0, 31.0 57.0, 29.0 57.0, 29.0 55.0))'), places.position)
and
language = 'ENG'
order by distance
limit 10;
(请注意,为了添加索引,我必须使 position
NOT NULL。)结果:
id | select_type | table | partitions | type | possible_keys | key | key_len | ref | rows | filtered | Extra |
---|---|---|---|---|---|---|---|---|---|---|---|
1 | PRIMARY | <derived2> | NULL | ALL | NULL | NULL | NULL | NULL | 1 | 100 | Using where; Using temporary; Using filesort |
1 | PRIMARY | place_names | NULL | ref | place_names_language_index | place_names_language_index | 12 | const | 1368960 | 100 | NULL |
1 | PRIMARY | places | NULL | eq_ref | PRIMARY | PRIMARY | 4 | mydb.place_names.place_id | 1 | 100 | NULL |
2 | DERIVED | NULL | NULL | NULL | NULL | NULL | NULL | NULL | NULL | NULL | No tables used |
结果似乎与没有查询的 MBRContains() 部分相同,我仍然看到可怕的“行数 = 1368960”。据我了解,这意味着行根本不受该子句的限制。我还尝试交换 from
和 join
以使主要 table 成为 places
,但没有任何变化。
你必须扫描所有 1368960 个点并检查到每个点的距离。这很费时间。
所有优化都涉及将搜索限制在“边界框”内。下面显示了使用 SPATIAL
索引以及其他 4 个索引的方法。
原来要解决这个问题,我需要的是:
- 使
position
列 NOT NULL(POINT 不支持 DEFAULT,因此我手动将所有空值设置为 POINT(0, 0) 并且在插入记录时也必须这样做)。这是对索引的要求: ALTER TABLE places ADD SPATIAL INDEX (position)
.- 使用 MBRContains() 将查询限制为基于
position
的更少元素。当然,MBRWithin() 也可以。实际上,我将不得不根据纬度和经度手动构建边界框。
单独这样做似乎不起作用,但后来我发现主要问题不在空间列上,而是在连接上:place_id 列没有索引!哎呀
所以这是我最后的查询:
SELECT
p.id,
ST_Distance_Sphere(p.position, POINT(30.5315, 56.3396)) AS distance,
pn.name,
pn.administration,
pn.country
FROM (
SELECT id, position
FROM places
WHERE MBRContains(ST_GeomFromText('Polygon((29 55, 29 57, 31 57, 29 57, 29 55))'), position)
) p
JOIN place_names pn ON p.id = pn.place_id
WHERE pn.language = 'ENG'
ORDER BY distance
LIMIT 10;
感谢 Rick James 和 Akina 的建议和指点。希望对路过的人有所帮助