パフォーマンスのために、また InnoDB を使用していると仮定すると、おそらく次のようにデータを少し非正規化します。
CREATE TABLE CITY (
CITY_ID INT PRIMARY KEY
);
CREATE TABLE CITY_DISTANCE (
CITY1_ID INT,
CITY2_ID INT,
DISTANCE NUMERIC NOT NULL,
PRIMARY KEY (CITY1_ID, DISTANCE, CITY2_ID),
FOREIGN KEY (CITY1_ID) REFERENCES CITY (CITY_ID),
FOREIGN KEY (CITY2_ID) REFERENCES CITY (CITY_ID)
);
都市の各ペアには、CITY_DISTANCE に同じ DISTANCE を含む 2 つの行があります (方向ごとに 1 つ)。これは明らかに非常に大きくなり、データの不一致につながる可能性があります (データベースは、同じ都市間で一致しない DISTANCE 値からそれ自体を防御しません)。また、DISTANCE は論理的に PK に属していませんが、我慢してください...
InnoDB テーブルはクラスター化されています。つまり、この特定の方法で PK を宣言することにより、テーブル全体を B ツリーに配置します。これは、次のようなクエリに特に適しています。
SELECT CITY2_ID, DISTANCE
FROM CITY_DISTANCE
WHERE CITY1_ID = 1
ORDER BY DISTANCE
LIMIT 5
このクエリは、 で識別される都市に最も近い 5 つの都市を返します。これは、1
上記の B ツリーの単純な範囲スキャンで満たすことができます。
id select_type table type possible_keys key key_len ref rows Extra
1 SIMPLE CITY_DISTANCE ref PRIMARY PRIMARY 4 const 6 "Using where; Using index"
ところで、クラスター化されたテーブルのセカンダリ インデックスは PK をカバーする必要があるため、InnoDB は 2 番目の FK のために (CITY2_ID に) もう 1 つのインデックスを自動的に作成します。これには CITY1_ID と DISTANCE も含まれます。これを利用して DISTANCE の重複を回避できるかもしれません (明示的に {CITY2_ID, DISTANCE, CITY1_ID} にインデックスを作成し、FK にそれを再利用させ、CHECK (CITY1_ID < CITY2_ID))、しかし MySQL クエリ オプティマイザーはおそらく処理するほど賢くありません。そのような構造で必要となるクエリ。