web-dev-qa-db-ja.com

多くの結合で遅いクエリを最適化する方法

私の状況:

  • クエリは約90,000台の車両を検索します
  • クエリは毎回時間がかかります
  • JOINされるすべてのフィールドのインデックスがすでにあります。

どうすれば最適化できますか?

これがクエリです:

SELECT vehicles.make_id,
       vehicles.fuel_id,
       vehicles.body_id,
       vehicles.transmission_id,
       vehicles.colour_id,
       vehicles.mileage,
       vehicles.vehicle_year,
       vehicles.engine_size,
       vehicles.trade_or_private,
       vehicles.doors,
       vehicles.model_id,
       Round(3959 * Acos(Cos(Radians(51.465436)) *
                         Cos(Radians(vehicles.gps_lat)) *
                                           Cos(
                                           Radians(vehicles.gps_lon) - Radians(
                                           -0.296482)) +
                               Sin(
                                      Radians(51.465436)) * Sin(
                               Radians(vehicles.gps_lat)))) AS distance
FROM   vehicles
       INNER JOIN vehicles_makes
         ON vehicles.make_id = vehicles_makes.id
       LEFT JOIN vehicles_models
         ON vehicles.model_id = vehicles_models.id
       LEFT JOIN vehicles_fuel
         ON vehicles.fuel_id = vehicles_fuel.id
       LEFT JOIN vehicles_transmissions
         ON vehicles.transmission_id = vehicles_transmissions.id
       LEFT JOIN vehicles_axles
         ON vehicles.axle_id = vehicles_axles.id
       LEFT JOIN vehicles_sub_years
         ON vehicles.sub_year_id = vehicles_sub_years.id
       INNER JOIN members
         ON vehicles.member_id = members.id
       LEFT JOIN vehicles_categories
         ON vehicles.category_id = vehicles_categories.id
WHERE  vehicles.status = 1
       AND vehicles.date_from < 1330349235
       AND vehicles.date_to > 1330349235
       AND vehicles.type_id = 1
       AND ( vehicles.price >= 0
             AND vehicles.price <= 1000000 )  

これは車両テーブルのスキーマです:

CREATE TABLE IF NOT EXISTS `vehicles` (
  `id` int(11) NOT NULL AUTO_INCREMENT,
  `number_plate` varchar(100) NOT NULL,
  `type_id` int(11) NOT NULL,
  `make_id` int(11) NOT NULL,
  `model_id` int(11) NOT NULL,
  `model_sub_type` varchar(250) NOT NULL,
  `engine_size` decimal(12,1) NOT NULL,
  `vehicle_year` int(11) NOT NULL,
  `sub_year_id` int(11) NOT NULL,
  `mileage` int(11) NOT NULL,
  `fuel_id` int(11) NOT NULL,
  `transmission_id` int(11) NOT NULL,
  `price` decimal(12,2) NOT NULL,
  `trade_or_private` tinyint(4) NOT NULL,
  `postcode` varchar(25) NOT NULL,
  `gps_lat` varchar(50) NOT NULL,
  `gps_lon` varchar(50) NOT NULL,
  `img1` varchar(100) NOT NULL,
  `img2` varchar(100) NOT NULL,
  `img3` varchar(100) NOT NULL,
  `img4` varchar(100) NOT NULL,
  `img5` varchar(100) NOT NULL,
  `img6` varchar(100) NOT NULL,
  `img7` varchar(100) NOT NULL,
  `img8` varchar(100) NOT NULL,
  `img9` varchar(100) NOT NULL,
  `img10` varchar(100) NOT NULL,
  `is_featured` tinyint(4) NOT NULL,
  `body_id` int(11) NOT NULL,
  `colour_id` int(11) NOT NULL,
  `doors` tinyint(4) NOT NULL,
  `axle_id` int(11) NOT NULL,
  `category_id` int(11) NOT NULL,
  `contents` text NOT NULL,
  `date_created` int(11) NOT NULL,
  `date_edited` int(11) NOT NULL,
  `date_from` int(11) NOT NULL,
  `date_to` int(11) NOT NULL,
  `member_id` int(11) NOT NULL,
  `inactive_id` int(11) NOT NULL,
  `status` tinyint(4) NOT NULL,
  PRIMARY KEY (`id`),
  KEY `type_id` (`type_id`),
  KEY `make_id` (`make_id`),
  KEY `model_id` (`model_id`),
  KEY `fuel_id` (`fuel_id`),
  KEY `transmission_id` (`transmission_id`),
  KEY `body_id` (`body_id`),
  KEY `colour_id` (`colour_id`),
  KEY `axle_id` (`axle_id`),
  KEY `category_id` (`category_id`),
  KEY `vehicle_year` (`vehicle_year`),
  KEY `mileage` (`mileage`),
  KEY `status` (`status`),
  KEY `date_from` (`date_from`),
  KEY `date_to` (`date_to`),
  KEY `trade_or_private` (`trade_or_private`),
  KEY `doors` (`doors`),
  KEY `price` (`price`),
  KEY `engine_size` (`engine_size`),
  KEY `sub_year_id` (`sub_year_id`),
  KEY `member_id` (`member_id`),
  KEY `date_created` (`date_created`)
) ENGINE=MyISAM  DEFAULT CHARSET=utf8 AUTO_INCREMENT=136237 ;

説明:

1   SIMPLE  vehicles    ref     type_id,make_id,status,date_from,date_to,price,mem...   type_id     4   const   85695   Using where
1   SIMPLE  members     index   PRIMARY     PRIMARY     4   NULL    3   Using where; Using index; Using join buffer
1   SIMPLE  vehicles_makes  eq_ref  PRIMARY     PRIMARY     4   tvs.vehicles.make_id    1   Using index
1   SIMPLE  vehicles_models     eq_ref  PRIMARY     PRIMARY     4   tvs.vehicles.model_id   1   Using index
1   SIMPLE  vehicles_fuel   eq_ref  PRIMARY     PRIMARY     4   tvs.vehicles.fuel_id    1   Using index
1   SIMPLE  vehicles_transmissions  eq_ref  PRIMARY     PRIMARY     4   tvs.vehicles.transmission_id    1   Using index
1   SIMPLE  vehicles_axles  eq_ref  PRIMARY     PRIMARY     4   tvs.vehicles.axle_id    1   Using index
1   SIMPLE  vehicles_sub_years  eq_ref  PRIMARY     PRIMARY     4   tvs.vehicles.sub_year_id    1   Using index
1   SIMPLE  vehicles_categories     eq_ref  PRIMARY     PRIMARY     4   tvs.vehicles.category_id    1   Using index
21
ChimeraTheory

WHERE句の改善

EXPLAINは、MySQLが1つのインデックス(type_id)句に複数の基準がある場合でも、WHERE句に一致する行を選択します。

WHERE句のすべての条件にインデックスを利用できるようにして、結果セットのサイズをできるだけ早く減らすには、vehiclesテーブルの次の列に複数列のインデックスを追加します。

(status, date_from, date_to, type_id, price)

列は、カーディナリティが最も高いものから最も少ないものの順にする必要があります。

例えば、 vehicles.date_fromstatusよりも明確な値を持つ可能性が高いため、date_fromstatusの前の列、次のように:

(date_from, date_to, price, type_id, status)

これにより、クエリ実行の最初の部分で返される行が減り、EXPLAIN結果の最初の行の行数が少なくなることを示します。

また、MySQLはEXPLAIN結果のWHEREに複数列のインデックスを使用することにも気づくでしょう。万一そうなっていない場合は、複数列インデックスをヒントまたは強制する必要があります。

不要なJOINを削除する

結合されたテーブルのフィールドを使用していないようです。結合を削除してください。これにより、クエリの追加の作業がすべて削除され、1つの単純な実行プラン(EXPLAIN結果の1行)になります。

JOINされたテーブルごとに、結果セットの行ごとに追加のルックアップが発生します。したがって、WHERE句が車両から5,000行を選択する場合、車両への8つの結合があるため、5,000 * 8 = 40,000ルックアップになります。それはあなたのデータベースサーバーから尋ねることがたくさんあります。

14
Marcus Adams

行のallの正確な距離の高価な計算の代わりに、境界ボックスを使用して、ボックス内の行についてのみ正確な距離を計算します。

最も簡単な例は、興味のある最小/最大の経度と緯度を計算し、それをWHERE句に追加することです。この方法では、行のサブセットに対してのみ距離が計算されます。

WHERE
    vehicles.gps_lat > min_lat ANDd vehicles.gps_lat < max_lat AND
    vehicles.gps_lon > min_lon AND vehicles.gps_lon < max_lon

より複雑なソリューションについては、以下を参照してください。

4
Mariusz Jamro

これなしでSQLは速くなりますか?

Round(3959 * Acos(Cos(Radians(51.465436)) *
  Cos(Radians(vehicles.gps_lat)) *
  Cos(Radians(vehicles.gps_lon) - 
  Radians(-0.296482)) + 
  Sin(Radians(51.465436)) * 
  Sin(Radians(vehicles.gps_lat)))) AS distance

数学の方程式を実行すると非常に高価です

おそらく、距離を事前に計算するマテリアライズドビューを検討する必要があり、そのビューから選択できます。データの動的度によっては、頻繁にデータを更新する必要がない場合があります。

3
Churk

@Randyのインデックスより少し具体的に言うと、彼の意図は、クエリ基準を利用するためにCOMPOUNDインデックスを作成することだったと思います... MINIMUMに基づいて構築された1つのインデックス...

( status, type_id, date_from )

しかし、date_toとpriceを含めるように拡張することもできますが、その詳細レベルのインデックスが実際にどの程度役立つかはわかりません

( status, type_id, date_from, date_to, price )

コメントごとに編集

これらすべての個別のインデックスは必要ありません...はい、主キー自体。ただし、その他の場合は、一般的なクエリ基準に基づいて複合インデックスを作成し、その他を削除する必要があります...エンジンは、クエリに最適なものについて混乱する可能性があります。常に特定のステータス、タイプ、日付を検索していることがわかっている場合(車両検索を想定)、それを1つのインデックスとして作成します。クエリがそのような情報だけでなく、その基準内の価格も探している場合、追加の基準として価格を満たし、価格を通過するいくつかのインデックス付きレコードはすでに非常に近くなっています。

年/メーカーに関係なく、自動送信と手動送信のみのようなクエリを提供する場合、はい、それは独自のインデックスになる可能性があります。ただし、標準的に他の「一般的な」基準がある場合は、クエリで使用される可能性があるセカンダリとしてそれを追加します。例:2ドアと4ドアの手動変速機を探す場合は、(transmission_id、category_id)にインデックスを設定します。

繰り返しになりますが、何らかの「最小」条件に基づいて基準のフィールドを絞り込むのに役立つものが何でも必要です。 「一般的に」適用される可能性があるインデックスに追加の列を追加する場合、それはパフォーマンスの向上にのみ役立つはずです。

1
DRapp

これを答えとして明確にするには:これらのインデックスがまだない場合は、追加することを検討する必要があります

これらのインデックスも持っていますか:

vehicles.status
vehicles.date_from
vehicles.date_to
vehicles.type_id
vehicles.price
1
Randy