web-dev-qa-db-ja.com

MySQL地理空間クエリは、インデックスが使用されているにもかかわらず非常に遅い

InnoDbテーブルから距離で(正確ではない)レコードをフェッチし、距離でソートする必要があります。テーブルには1000万のレコードがあります。

私の最高の時間はこれまでのところ8秒(距離による順序なしで3秒)で、これは使用できません。どうすればこれを改善できますか?

SRID 4326として定義されたポイントカラムがあります。MySQL8.0.12を使用しています。

SELECT mp.hash_id, 
ROUND(ST_Distance(ST_SRID(POINT(8.53955, 47.37706), 4326), mp.geo_pt), 2) AS distance
  FROM member_profile mp 
  WHERE
    MBRCONTAINS(ST_GeomFromText(
      CONCAT('POLYGON((', ST_X(POINT (8.53955, 47.37706)) - 0.43415340086831, ' ',
        ST_Y(POINT (8.53955, 47.37706)) - 0.43415340086831, ',',
        ST_X(POINT (8.53955, 47.37706)) + 0.43415340086831, ' ',
        ST_Y(POINT (8.53955, 47.37706)) - 0.43415340086831, ',',
        ST_X(POINT (8.53955, 47.37706)) + 0.43415340086831, ' ',
        ST_Y(POINT (8.53955, 47.37706)) + 0.43415340086831, ',',
        ST_X(POINT (8.53955, 47.37706)) - 0.43415340086831, ' ',
        ST_Y(POINT (8.53955, 47.37706)) + 0.43415340086831, ',',
        ST_X(POINT (8.53955, 47.37706)) - 0.43415340086831, ' ',
        ST_Y(POINT (8.53955, 47.37706)) - 0.43415340086831, ')) ')
           , 4326), geo_pt)
-- ST_Distance(ST_GeomFromText('POINT (8.53955 47.37706)', 4326), mp.geo_pt) <= 25000 -- need 16 sec
-- order by distance -- need 8 sec with MBRContains, 100 sec with ST_Distance
LIMIT 50;

空間インデックスが作成されました:

CREATE SPATIAL INDEX geo_pt_index ON mp (geo_pt);

EXPLAINは、geo_ptインデックスが使用されていることを示しています。

my.cnf

[mysqld]
datadir=/var/lib/mysql
socket=/var/lib/mysql/mysql.sock
symbolic-links=0
log-error=/var/log/mysqld.log
pid-file=/var/run/mysqld/mysqld.pid
innodb_buffer_pool_size = 12G
innodb_log_file_size = 512M
innodb_flush_log_at_trx_commit = 2
innodb_flush_method = O_DIRECT
key_buffer_size = 1G
secure-file-priv = ""

このサーバーはこのデータベースにのみ割り当てられており、負荷はありません(クエリを実行する場合を除く)。 IOPSのボトルネックはありません。 innodb_buffer_pool_sizeは、データセット全体をメモリに保持できるサイズになっています。

サーバーインスタンスには16 GBのメモリがあり、高速NVMe SSDを使用します(IOPSボトルネックはありません)。サーバーは、この1つのデータベースのみをホストし、クエリ以外の負荷はありません。ディスクの30%が使用されます。

SHOW GLOBAL STATUS出力: https://Pastebin.com/EMeNL8yT

SHOW GLOBAL VARIABLES出力: https://Pastebin.com/yxzYn10E

MySQLチューナー出力: https://Pastebin.com/NRWFQDMQ

私は本日8.0.11から8.0.12に更新しましたが、以前のMySQL Tuner Recommendationsのほとんどすべての関連する提案に従いました。 MySQLの更新は、速度が同じになる前に、空間検索で修正されたバグに関して行われました。

表示警告(クエリ実行後):

Level,Code,Message
Note,1003,/* select#1 */ select `***`.`mp`.`member_id` AS `member_id`,round(st_distance(st_pointfromtext('POINT(8.53955 47.37706)',4326),`***`.`mp`.`geo_pt`),2) AS `distance` from `***`.`member_profile` `mp` where mbrcontains(<cache>(st_geomfromtext(concat('POLYGON((',(st_x(point(8.53955,47.37706)) - 0.43415340086831),' ',(st_y(point(8.53955,47.37706)) - 0.43415340086831),',',(st_x(point(8.53955,47.37706)) + 0.43415340086831),' ',(st_y(point(8.53955,47.37706)) - 0.43415340086831),',',(st_x(point(8.53955,47.37706)) + 0.43415340086831),' ',(st_y(point(8.53955,47.37706)) + 0.43415340086831),',',(st_x(point(8.53955,47.37706)) - 0.43415340086831),' ',(st_y(point(8.53955,47.37706)) + 0.43415340086831),',',(st_x(point(8.53955,47.37706)) - 0.43415340086831),' ',(st_y(point(8.53955,47.37706)) - 0.43415340086831),')) '),4326)),`***`.`mp`.`geo_pt`) order by `distance` limit 50

説明:

id,select_type,table,partitions,type,possible_keys,key,
key_len,ref,rows,filtered,Extra
1,SIMPLE,mp,\N,range,geo_pt_index,geo_pt_index,34,\N,23,100.00,Using where; Using filesort

テーブルの作成:

CREATE TABLE `member_profile` (
  `member_id` bigint(20) NOT NULL AUTO_INCREMENT,
  `hash_id` varchar(32)
        CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci DEFAULT NULL,
  `geo_pt` point NOT NULL /*!80003 SRID 4326 */,
  PRIMARY KEY (`member_id`),
  UNIQUE KEY `hash_id` (`hash_id`),
  SPATIAL KEY `geo_pt_index` (`geo_pt`)
) ENGINE=InnoDB AUTO_INCREMENT=10498210
            DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci

インデックスを表示:

Table,Non_unique,Key_name,Seq_in_index,Column_name,Collation,
Cardinality,Sub_part,
Packed,Null,Index_type,Comment,Index_comment,Visible

member_profile,0,PRIMARY,1,member_id,A,9936492,\N,\N,,BTREE,,,YES
member_profile,0,hash_id,1,hash_id,A,9936492,\N,\N,YES,BTREE,,,YES
member_profile,1,geo_pt_index,1,geo_pt,A,9936492,32,\N,,SPATIAL,,,YES
7
nenad007

「ポイントカラムがSRID 4326として定義されています。MySQL8.0.12を使用しています。」

同様の問題があり、SRIDを0に変更するとパフォーマンスが大幅に向上します。副作用が耐えられないかどうかはわかりませんが、少なくとも試してみてください。あなたがそうするなら、緯度と経度の他の順序を忘れないでください;)

KRピート

2
Peter Baumert

My.cnf [mysqld]セクションで検討すべき提案

max_connect_errors=10  # from 100, why give a hacker/cracker so many chances?
thread_cache_size=30  # from 9 since MySQL needs 8 to get started
innodb_io_capacity_max=60000  # from 2000  use that NVME for performance
innodb_io_capacity=30000  # from 200 why stick with a low limit with NVME
key_buffer_size=16M  # from 1G conserve RAM for more useful purpose
innodb_buffer_pool_dump_pct=90  # from 25 to reduce WARM up time
innodb_change_buffer_max_size=15  # from 25% for your low chg,del,ins need
innodb_lru_scan_depth=128  # from 1025 to conserve CPU every SECOND
innodb_read_io_threads=64  # from 4 see dba.stackexchange Question 5666
innodb_write_io_threads=64  # from 4 see 9/12/11 RolondaMySQLDBA info

skype IDを含む連絡先情報については、プロファイル、ネットワークプロファイルを確認し、連絡を取ってください。

0
Wilson Hauck

MySQL GISは常に遅いです。しかし、1,000万行のテーブルでORDER BY DISTANCEなしの3秒は遅いと言われていますか?検討する

  1. このメソッドを使用して ポイントを作成
  2. WKTを使用してボックスの境界を作成することで、単純化したものを作成します。これがどのように役立つかはわかりませんが、MySQLです。
  3. ST_Distance() < upperlimitを実行するのではなく、ST_Buffer( polygon, upperlimit )を実行し、それを_ST_Contains_の呼び出しで使用することを検討してください。
  4. PostgreSQL/PostGISに移行し、ST_DWithin(geom,geom,upperlimit)を使用することを検討してください。 PostGISは優れた索引付けを備えています。実際には、インデックス全体でこのすべてを実行できます KNNをサポートしているため
0
Evan Carroll

サイドの問題

ハッシュなどの16進値にはasciiを使用します。

16進数のハッシュをバイナリにパックします。

hash_id BINARY(16)
HEX(hash_id)   -- when reading
hash_id = UNHEX(...) -- when writing

AUTO_INCREMENT idを削除して、hash_idを使用してください。

ハッシュを取り除く方が良いでしょう。データが大きくなりすぎてRAMに収まらない場合は、I/Oバウンドになります。

高速アルゴリズム

(最初の質問はGISに関するものですが、2番目の質問は「最も近い場所を見つける」ことの高速化に関するものです。)

LIMIT 50と同等の場合、次のアルゴリズムはおそらく200行未満を処理します(そしてそれらのみの大圏距離を計算します)。 (79,901より良いですか?)

http://mysql.rjweb.org/doc.php/latlng (コードを含む)

変数/ステータスの分析

あなたはまだあまり走っていないようです。したがって、ここで言うことはあまりありません。

所見:

  • バージョン:8.0.12
  • 16 GBのRAM
  • 稼働時間= 05:49:58;一部のGLOBAL STATUS値は、まだ意味がない場合があります。
  • これはSHOW GLOBAL STATUSでしたか?
  • Windowsで実行していません。
  • 64ビットバージョンの実行
  • 完全に(またはほとんど)InnoDBを実行しているようです。

より重要な問題:

key_buffer_size = 50M
long_query_time = 2

Slowlogをオンにして、遅いクエリを識別できるようにします。

詳細およびその他の観察:

( (key_buffer_size - 1.2 * Key_blocks_used * 1024) / _ram ) = (1024M - 1.2 * 16 * 1024) / 16384M = 6.2%-RAMの割合がkey_bufferで無駄になりました。--key_buffer_sizeを減らしてください。

( Key_blocks_used * 1024 / key_buffer_size ) = 16 * 1024 / 1024M = 0.00%-key_bufferの使用率。最高水位標。 -不要なメモリ使用を回避するためにkey_buffer_sizeを小さくします。

( table_open_cache ) = 4,000-キャッシュするテーブル記述子の数-通常は数百が適切です。

( Innodb_buffer_pool_pages_free / Innodb_buffer_pool_pages_total ) = 443,594 / 786432 = 56.4%-現在使用されていないbuffer_poolの部分-innodb_buffer_pool_sizeが必要以上に大きいですか?

( Innodb_os_log_written / (Uptime / 3600) / innodb_log_files_in_group / innodb_log_file_size ) = 87,040 / (20998 / 3600) / 2 / 512M = 1.4e-5-比率-(分を参照)

( Uptime / 60 * innodb_log_file_size / Innodb_os_log_written ) = 20,998 / 60 * 512M / 87040 = 2.16e+6-InnoDBログローテーション間の分5.6.8以降、これは動的に変更できます。 my.cnfも必ず変更してください。 -(ローテーション間の60分の推奨はやや恣意的です。)innodb_log_file_sizeを調整します。 (AWSでは変更できません。)

( innodb_print_all_deadlocks ) = innodb_print_all_deadlocks = OFF-すべてのデッドロックをログに記録するかどうか。 -デッドロックに悩まされている場合は、これをオンにします。注意:デッドロックが多い場合、ディスクに大量に書き込まれる可能性があります。

( join_buffer_size / _ram ) = 262,144 / 16384M = 0.00%-スレッドごとに0-N。 JOINを高速化する可能性があります(クエリ/インデックスを修正する方が良い)(すべてのエンジン)インデックススキャン、範囲インデックススキャン、全テーブルスキャン、各全JOINなどに使用されます。サイズが大きい場合は、join_buffer_sizeを減らしてメモリ負荷を回避します。 RAMの1%未満を推奨します。小さい場合は、RAMの0.01%に増やして、クエリを改善します。

( query_prealloc_size / _ram ) = 8,192 / 16384M = 0.00%-解析用。 RAMの割合

( query_alloc_block_size / _ram ) = 8,192 / 16384M = 0.00%-解析用。 RAMの割合

( net_buffer_length / max_allowed_packet ) = 16,384 / 64M = 0.02%

( (Com_show_create_table + Com_show_fields) / Questions ) = (7 + 7) / 698 = 2.0%-いたずらなフレームワーク-スキーマの再発見に多くの労力を費やしています。 -サードパーティベンダーに文句を言う。

( (Com_insert + Com_update + Com_delete + Com_replace) / Com_commit ) = (0 + 20 + 0 + 0) / 0 = INF-コミットごとのステートメント(すべてのInnoDBを想定)-低:トランザクション内でクエリをグループ化するのに役立つ場合があります。高:長いトランザクションはさまざまなことを負担します。

( Select_scan / Com_select ) = 96 / 355 = 27.0%-全テーブルスキャンを実行する選択の%。 (ストアドルーチンにだまされる可能性があります。)-インデックスを追加する/クエリを最適化する

( expire_logs_days ) = 0-binlogを自動的にパージするまでの時間(この日数が経過した後)-大きすぎる(またはゼロ)=ディスク領域を消費します。小さすぎる=ネットワーク/マシンのクラッシュに迅速に対応する必要がある(log_bin = OFFの場合は関係ありません)

( slave_pending_jobs_size_max / max_allowed_packet ) = 128M / 64M = 2-並列スレーブスレッドの場合-slave_pending_jobs_size_maxはmax_allowed_pa​​cket未満であってはなりません

( slow_query_log ) = slow_query_log = OFF-遅いクエリをログに記録するかどうか。 (5.1.12)

( long_query_time ) = 10-「遅い」クエリを定義するためのカットオフ(秒)。 -提案2

( back_log / max_connections ) = 151 / 151 = 100.0%

( Threads_created / Connections ) = 4 / 214 = 1.9%-プロセス作成の迅速性-thread_cache_sizeを増やします(Windows以外)

異常に大きい:

Com_create_db = 0.17 /HR
Com_drop_db = 0.34 /HR
Com_show_profiles = 1.9 /HR
Innodb_buffer_pool_pages_flushed / max(Questions, Queries) = 0.365
Innodb_buffer_pool_pages_free = 443,594
Select_range / Com_select = 33.2%
Ssl_session_cache_size = 128
innodb_purge_threads = 4
innodb_undo_tablespaces = 2
max_error_count = 1,024
max_length_for_sort_data = 4,096
optimizer_trace_max_mem_size = 1.05e+6
slave_pending_jobs_size_max = 128MB

異常な文字列:

Ssl_session_cache_mode = SERVER
default_authentication_plugin = caching_sha2_password
event_scheduler = ON
explicit_defaults_for_timestamp = ON
ft_boolean_syntax = + -><()~*:&
have_query_cache = NO
have_ssl = YES
have_symlink = DISABLED
innodb_buffer_pool_dump_at_shutdown = ON
innodb_buffer_pool_load_at_startup = ON
innodb_fast_shutdown = 1
innodb_undo_log_truncate = ON
log_syslog = ON
master_info_repository = TABLE
optimizer_trace = enabled=off,one_line=off
optimizer_trace_features = greedy_search=on, range_optimizer=on, dynamic_range=on, repeated_subselect=on
relay_log_info_repository = TABLE
slave_rows_search_algorithms = INDEX_SCAN,HASH_SCAN
ssl_ca = ca.pem
ssl_cert = server-cert.pem
ssl_key = server-key.pem
0
Rick James