web-dev-qa-db-ja.com

結合とグループ化で非常に遅いクエリを最適化するにはどうすればよいですか?

私はこのGTFSデータベースを持っています:

Database schema

すべての***_id列にインデックスが付けられ、テーブル間に外部キーがあります(trips.route_idからroutes.route_idへのFKなど)。

したがって、基本的にストップとルート間のリンクはroutestripsstop_timesstopsです。

私は、物理的に互いに近いストップをすべて記載するテーブルを作成して埋め、stop_connectionsを埋めました。ものすごく単純。

ここで私が欲しいのは、ルートAから取得することです。このルートのストップの1つから到達できる他のすべてのルート。

だから基本的に私は次のように行きたいです:

A.route → A.trips (many) → A.trips.stop_times(many^2) → A.trips.stop_times.stops(many^3)

次に、stop_connectionsを使用して関連するストップを取得し、逆戻りしてこれらのストップからルートを取得します。

connected_stops → connected_stops.stop_times → connected_stops.stop_times.trips → connected_stops.stop_times.trips.routes

私はこれに似たテーブルを得るでしょう:

+---------------+-------------+
| from_route_id | to_route_id |
+---------------+-------------+
|          0001 |        0002 |
|          0001 |        0004 |
|          0001 |        0005 |
|          0002 |        0001 |
|          0002 |        0005 |
+---------------+-------------+

これは私のクエリです:

-- get from/to route IDs
select
    r.route_id as from_route_id,
    c_t.route_id as to_route_id

- start from route A
from routes r

-- going down on all trips on route A
left join trips t on t.route_id = r.route_id

-- going down on all stop_times for A's trips
left join stop_times st on st.trip_id = t.trip_id

-- going down on all stops use by A's trips/vehicles
left join stops s on s.stop_id = st.stop_id

-- get connected stops
inner join stop_connections c_s on c_s.from_stop_id = s.stop_id

-- going up to stop_times
inner join stop_times c_st on c_st.stop_id = c_s.to_stop_id

-- going up to trips
inner join trips c_t on c_t.trip_id = c_st.trip_id and c_t.route_id <> r.route_id

-- no need to actually go up to routes because we already have the route ID

-- I just want every from/to routes mentioned only once!
group by r.route_id, c_t.route_id -- this is my problem here

ここに説明があります:

+----+-------------+-------+--------+------------------------------------------+------------------+---------+----------------------------------+------+----------------------------------------------+
| id | select_type | table | type   | possible_keys                            | key              | key_len | ref                              | rows | Extra                                        |
+----+-------------+-------+--------+------------------------------------------+------------------+---------+----------------------------------+------+----------------------------------------------+
|  1 | SIMPLE      | r     | const  | PRIMARY                                  | PRIMARY          | 302     | const                            |    1 | Using index; Using temporary; Using filesort |
|  1 | SIMPLE      | t     | ref    | PRIMARY,trip_route_id                    | trip_route_id    | 302     | const                            | 1474 | Using where; Using index                     |
|  1 | SIMPLE      | st    | ref    | st_trip_id,st_stop_id                    | st_trip_id       | 302     | bicou_gtfs_rennes.t.trip_id      |   14 | Using where                                  |
|  1 | SIMPLE      | c_s   | ref    | from_to_stop_ids,from_stop_id,to_stop_id | from_to_stop_ids | 767     | bicou_gtfs_rennes.st.stop_id     |    1 | Using where; Using index                     |
|  1 | SIMPLE      | s     | eq_ref | PRIMARY                                  | PRIMARY          | 767     | bicou_gtfs_rennes.st.stop_id     |    1 | Using where; Using index                     |
|  1 | SIMPLE      | c_st  | ref    | st_trip_id,st_stop_id                    | st_stop_id       | 302     | bicou_gtfs_rennes.c_s.to_stop_id |  325 | Using where                                  |
|  1 | SIMPLE      | c_t   | eq_ref | PRIMARY,trip_route_id                    | PRIMARY          | 767     | bicou_gtfs_rennes.c_st.trip_id   |    1 | Using where                                  |
+----+-------------+-------+--------+------------------------------------------+------------------+---------+----------------------------------+------+----------------------------------------------+

このクエリを完了することができませんでした。 500、5000、50k、または100kの制限でこれは30秒未満で行われますが、行の総数がわからないので、数百万を期待します(rowsステートメントのすべてのEXPLAINの積ですか?私はクエリの最適化でかなり新しいです)。

制限付きで実行しているとき、SQLエンジンは行があると停止しますが、これは私が取得するものです。

+---------------+-------------+
| from_route_id | to_route_id |
+---------------+-------------+
| 0001          | 0150        |
| 0001          | 0150        |
| 0001          | 0150        |
| 0001          | 0150        |
| 0001          | 0150        |
| 0001          | 0150        |
| 0001          | 0150        |
| 0001          | 0150        |
| 0001          | 0150        |
| 0001          | 0150        |
+---------------+-------------+
10 rows in set (0.19 sec)

だから私は(上のクエリにある)グループを追加すると思っていましたが、今度はSQLエンジンがすべての行を調べてそれらを並べ替える必要があり、これには時間がかかります。

また、さまざまな結合方法を試しました。最初に左結合、次に内部結合、そしてSTRAIGHT_JOINです。これはどれも役に立たなかった。

これの目的はルックアップテーブルに入力することなので、そのクエリに数分かかる場合は問題ありません。しかし、私は1日中CPUとディスクを狂ったように使用しないようにしたいと思います。私は毎月かそこらでこのクエリを必要とする予定です。

MySQL 5.1.66-0+squeeze1を使用しています。

3
Benoit Duffez

OK、私は別のルックアップテーブルを追加してしまいました。

CREATE TABLE IF NOT EXISTS `stops_routes` (
  `id` int(11) NOT NULL AUTO_INCREMENT,
  `stop_id` varchar(100) NOT NULL,
  `route_id` varchar(100) NOT NULL,
  PRIMARY KEY (`id`),
  UNIQUE KEY `stop_route` (`stop_id`,`route_id`),
  KEY `stop_id` (`stop_id`),
  KEY `route_id` (`route_id`)
) ENGINE=InnoDB  DEFAULT CHARSET=utf8;

それを満たすことはかなり高速でした:

mysql> insert into stops_routes (stop_id, route_id)
    ->
    -> select
    ->     s.stop_id,
    ->     r.route_id as from_route_id
    ->
    -> from routes r
    -> left join trips t on t.route_id = r.route_id
    -> left join stop_times st on st.trip_id = t.trip_id
    -> left join stops s on s.stop_id = st.stop_id
    -> group by s.stop_id, r.route_id;
Query OK, 3496 rows affected (8.38 sec)
Records: 3496  Duplicates: 0  Warnings: 0

それを使用すると、非常に高速です。

mysql> select
    ->     r.route_id as from_route_id,
    ->     c_sr.route_id as to_route_id
    ->
    -> from routes r
    ->
    -> left join stops_routes sr on sr.route_id = r.route_id
    -> left join stop_connections c_s on c_s.from_stop_id = sr.stop_id
    -> left join stops_routes c_sr on c_sr.stop_id = c_s.to_stop_id
    ->
    -> where r.route_id <> c_sr.route_id
    -> group by r.route_id, c_sr.route_id
    -> limit 10;
+---------------+-------------+
| from_route_id | to_route_id |
+---------------+-------------+
| 0001          | 0002        |
| 0001          | 0003        |
| 0001          | 0004        |
| 0001          | 0005        |
| 0001          | 0006        |
| 0001          | 0008        |
| 0001          | 0009        |
| 0001          | 0011        |
| 0001          | 0014        |
| 0001          | 0031        |
+---------------+-------------+
10 rows in set (0.63 sec)

これで、最後のルックアップテーブル(GTFSネットワーク上のすべてのルート間の接続のセット)に入力できます。

mysql> insert into route_connections (from_route_id, to_route_id)
    -> select
    ->     r.route_id as from_route_id,
    ->     c_sr.route_id as to_route_id
    ->
    -> from routes r
    ->
    -> left join stops_routes sr on sr.route_id = r.route_id
    -> left join stop_connections c_s on c_s.from_stop_id = sr.stop_id
    -> left join stops_routes c_sr on c_sr.stop_id = c_s.to_stop_id
    ->
    -> where r.route_id <> c_sr.route_id
    -> group by r.route_id, c_sr.route_id;
Query OK, 2848 rows affected (0.31 sec)
Records: 2848  Duplicates: 0  Warnings: 0

驚くほど速い。エンジンはこれを最適化するためのステップを分割できなかったと思います。
1秒未満または1分未満のクエリのみを使用して同じ結果を取得できるかどうか(ルートからルート接続テーブルまで)が知りたいです。

2
Benoit Duffez