簡體   English   中英

用點優化空間 mysql 查詢

[英]Optimising spatial mysql query with point

我在 MySQL 5.7 上有兩個表,如下所示:

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 選擇類型 桌子 分區 類型 可能的鍵 鑰匙 key_len 參考 過濾 額外的
1 基本的 <派生2> NULL 全部 NULL NULL NULL NULL 1 100 使用臨時的; 使用文件排序
1 基本的 地名 NULL 參考 地點名稱語言索引 地點名稱語言索引 12 常量 1368960 100 NULL
1 基本的 地方 NULL eq_ref 基本的 基本的 4 msdplaces.place_names.place_id 1 100 NULL
2 衍生的 NULL NULL NULL NULL NULL NULL NULL NULL NULL 沒有使用表

如您所見,該表非常大(1368960 行),並且將來會變得更大。 在計算點和行之間的 ST_DISTANCE_SPHERE 之前,我想盡可能地減少查找行(例如,通過將它們限制為 80 公里的半徑,或者甚至只是給定點周圍的 1 經度/緯度度)。或者任何其他優化可以使查詢更快,因為目前它非常慢。

到目前為止,我在互聯網上找到的所有內容都來自 5.7 版之前,因此它必須手動計算距離,而不是使用本機 POINT 數據類型和 ST_DISTANCE_SPHERE 函數——這些比手動處理三角函數要快得多,所以我想保留它們,但我不反對將 POINT 列拆分為單獨的緯度和經度,如果這應該具有優勢的話。

如何優化此查詢以使表大小對性能的影響盡可能小?

編輯:我在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不是 NULL。)結果:

ID 選擇類型 桌子 分區 類型 可能的鍵 鑰匙 key_len 參考 過濾 額外的
1 基本的 <派生2> NULL 全部 NULL NULL NULL NULL 1 100 使用哪里; 使用臨時的; 使用文件排序
1 基本的 地名 NULL 參考 地點名稱語言索引 地點名稱語言索引 12 常量 1368960 100 NULL
1 基本的 地方 NULL eq_ref 基本的 基本的 4 mydb.place_names.place_id 1 100 NULL
2 衍生的 NULL NULL NULL NULL NULL NULL NULL NULL NULL 沒有使用表

結果似乎與沒有查詢的 MBRContains() 部分相同,我仍然看到可怕的“rows = 1368960”。 據我了解,這意味着行根本不受該子句的限制。 我也嘗試from交換和join讓主表成為places ,但沒有任何改變。

您所擁有的必須掃描所有 1368960 個點並檢查每個點的距離。 這很耗時。

所有優化都涉及將搜索限制為“邊界框”。 下面顯示了一種使用SPATIAL索引以及其他 4 個索引的方法。

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

事實證明,要解決這個問題,我需要的是:

  1. 使position列 NOT NULL (POINT 不支持 DEFAULT,所以我手動將所有 null 值設置為 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 的建議和指導。 希望這對路過的其他人有所幫助。

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM