web-dev-qa-db-ja.com

インデックスを使用しないBETWEENのLEFT JOIN

table1の1つの列の値がtable2の2つの列の値の間にあるという基準で2つのテーブルを結合しようとしています。

表1

CREATE TABLE `values`(
  `id` INT,
  `name` VARCHAR(50),
  `num_addr` BIGINT UNSIGNED
);

表2

CREATE TABLE `ranges`(
  `id` INT,
  `range_name` VARCHAR(50),
  `range_start` BIGINT UNSIGNED,
  `range_end` BIGINT UNSIGNED,
  INDEX `idx_start` (`range_start` ASC),
  INDEX `idx_end` (`range_end` ASC),
  INDEX `idx_range` (`range_start` ASC, `range_end` ASC)
);

クエリ:

SELECT
  `v`.`name`,
  `v`.`num_addr`,
  `r`.`range_name`
FROM
  `values` `v`
  LEFT JOIN `ranges` `r` ON `v`.`num_addr` BETWEEN `range_start` AND `range_end`

クエリのEXPLAIN EXTENDEDは、インデックスが使用されていないことを示し、「各レコードの範囲がチェックされました(インデックスマップ:0x7)」という情報が含まれます。範囲テーブルには500,000を超える行があり、クエリは時間依存であるため、これはパフォーマンスの問題です。

# id, select_type, table, type, possible_keys, key, key_len, ref, rows, filtered, Extra
1, SIMPLE, r, ALL, idx_start,idx_end,idx_range, , , , 1, 100.00, Range checked for each record (index map: 0x7)

FORCE INDEX ON JOINは、オプティマイザが推奨インデックスのみを認識し、使用しないため、実際には事態を悪化させます。

そのような結合でインデックスを使用する方法はありますか?

その他の注意事項:

  1. BETWEENvalue >= range_start AND value <= range_endに変更しても、実行計画は変更されません。
  2. idx_startおよびidx_endインデックスを削除しても、状況は改善されません。
  3. インデックスをnum_addrに追加しても、結合の実行には影響しません。
  4. 間隔(range_start, range_end)はオーバーラップできます。
  5. 範囲外のnum_addrがあります。
  6. 良い例えは電話番号です:rangesテーブルには、レコード('UK', 44000000000000, 44999999999999)と別の('UK Vodafone', 44700000000000, 44799999999999)があります。
1
Matthew Sammut

範囲の重複は、最適化が特に難しい問題です。ただし、これは大きなパフォーマンスを提供する手法ですが、スキーマを大幅に変更する必要があります。

別のテーブルを追加します。 Prefixesとしましょう。次の2つの列があります。

prefix DECIMAL(4,0) NOT NULL,
range_id INT,   -- for JOINing to `ranges`
PRIMARY KEY(prefix)

次に、数値4432109...を探すには、まず4432range.id`の値でPrefixes. This will lead to one or moreを調べて、既存のテーブルをチェックインします。

例では、Prefixesには「UK」の100エントリと「UK Vodafone」の10エントリがあることに注意してください。これは、Prefixesを維持するための追加のコードを意味します。

これのバリエーションは、残りのranges列を新しいテーブルに移動し、テーブルrangesを削除することです。 (これを行うかどうかは、列の数と性質、コードの面倒、「プレフィックス」のサイズ、テーブルのサイズなどに依存します。)

1
Rick James