使用点优化空间 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”。据我了解,这意味着行根本不受该子句的限制。我还尝试交换 fromjoin 以使主要 table 成为 places,但没有任何变化。

你必须扫描所有 1368960 个点并检查到每个点的距离。这很费时间。

所有优化都涉及将搜索限制在“边界框”内。下面显示了使用 SPATIAL 索引以及其他 4 个索引的方法。

http://mysql.rjweb.org/doc.php/find_nearest_in_mysql

原来要解决这个问题,我需要的是:

  1. 使 position 列 NOT NULL(POINT 不支持 DEFAULT,因此我手动将所有空值设置为 POINT(0, 0) 并且在插入记录时也必须这样做)。这是对索引的要求:
  2. ALTER TABLE places ADD SPATIAL INDEX (position).
  3. 使用 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 的建议和指点。希望对路过的人有所帮助