web-dev-qa-db-ja.com

複合インデックスはいつ使用する必要がありますか?

  1. データベースで複合インデックスを使用する必要があるのはいつですか?
  2. 複合インデックスを使用することによるパフォーマンスの影響は何ですか?)
  3. なぜ複合インデックスを使用する必要があるのですか?

たとえば、homesテーブルがあります。

CREATE TABLE IF NOT EXISTS `homes` (
  `home_id` int(10) unsigned NOT NULL auto_increment,
  `sqft` smallint(5) unsigned NOT NULL,
  `year_built` smallint(5) unsigned NOT NULL,
  `geolat` decimal(10,6) default NULL,
  `geolng` decimal(10,6) default NULL,
  PRIMARY KEY  (`home_id`),
  KEY `geolat` (`geolat`),
  KEY `geolng` (`geolng`),
) ENGINE=InnoDB  ;

geolatgeolngの両方に複合インデックスを使用するのは理にかなっていますか。

私は置き換えます:

  KEY `geolat` (`geolat`),
  KEY `geolng` (`geolng`),

で:

KEY `geolat_geolng` (`geolat`, `geolng`)

その場合:

  • どうして?
  • 複合インデックスを使用することによるパフォーマンスの影響は何ですか?)

更新:

多くの人が私が実行するクエリに完全に依存していると述べているため、実行される最も一般的なクエリは次のとおりです。

SELECT * FROM homes
WHERE geolat BETWEEN ??? AND ???
AND geolng BETWEEN ??? AND ???

更新2:

次のデータベーススキーマを使用します。

CREATE TABLE IF NOT EXISTS `homes` (
  `home_id` int(10) unsigned NOT NULL auto_increment,
  `primary_photo_group_id` int(10) unsigned NOT NULL default '0',
  `customer_id` bigint(20) unsigned NOT NULL,
  `account_type_id` int(11) NOT NULL,
  `address` varchar(128) collate utf8_unicode_ci NOT NULL,
  `city` varchar(64) collate utf8_unicode_ci NOT NULL,
  `state` varchar(2) collate utf8_unicode_ci NOT NULL,
  `Zip` mediumint(8) unsigned NOT NULL,
  `price` mediumint(8) unsigned NOT NULL,
  `sqft` smallint(5) unsigned NOT NULL,
  `year_built` smallint(5) unsigned NOT NULL,
  `num_of_beds` tinyint(3) unsigned NOT NULL,
  `num_of_baths` decimal(3,1) unsigned NOT NULL,
  `num_of_floors` tinyint(3) unsigned NOT NULL,
  `description` text collate utf8_unicode_ci,
  `geolat` decimal(10,6) default NULL,
  `geolng` decimal(10,6) default NULL,
  `display_status` tinyint(1) NOT NULL,
  `date_listed` timestamp NOT NULL default CURRENT_TIMESTAMP,
  `contact_email` varchar(100) collate utf8_unicode_ci NOT NULL,
  `contact_phone_number` varchar(15) collate utf8_unicode_ci NOT NULL,
  PRIMARY KEY  (`home_id`),
  KEY `customer_id` (`customer_id`),
  KEY `city` (`city`),
  KEY `num_of_beds` (`num_of_beds`),
  KEY `num_of_baths` (`num_of_baths`),
  KEY `geolat` (`geolat`),
  KEY `geolng` (`geolng`),
  KEY `account_type_id` (`account_type_id`),
  KEY `display_status` (`display_status`),
  KEY `sqft` (`sqft`),
  KEY `price` (`price`),
  KEY `primary_photo_group_id` (`primary_photo_group_id`)
) ENGINE=InnoDB  DEFAULT CHARSET=utf8 COLLATE=utf8_unicode_ci AUTO_INCREMENT=8 ;

次のSQLを使用します。

EXPLAIN SELECT  homes.home_id,
                    address,
                    city,
                    state,
                    Zip,
                    price,
                    sqft,
                    year_built,
                    account_type_id,
                    num_of_beds,
                    num_of_baths,
                    geolat,
                    geolng,
                    photo_id,
                    photo_url_dir
            FROM homes
            LEFT OUTER JOIN home_photos ON homes.home_id = home_photos.home_id
                AND homes.primary_photo_group_id = home_photos.home_photo_group_id
                AND home_photos.home_photo_type_id = 2
            WHERE homes.display_status = true
            AND homes.geolat BETWEEN -100 AND 100
            AND homes.geolng BETWEEN -100 AND 100

EXPLAINは以下を返します。

id  select_type  table        type  possible_keys                                    key                  key_len  ref     rows  Extra
----------------------------------------------------------------------------------------------------------
1   SIMPLE       homes        ref   geolat,geolng,display_status                     display_status       1        const   2     Using where
1  SIMPLE        home_photos  ref   home_id,home_photo_type_id,home_photo_group_id   home_photo_group_id  4        homes.primary_photo_group_id   4  

EXPLAINコマンドの読み方がよくわかりません。これは見た目が良くも悪くもなりますか。現在、geolatとgeolngに複合インデックスを使用していません。私はすべきですか?

120
Teddy

有益なクエリを使用する場合は、複合インデックスを使用する必要があります。次のような複合インデックス:

index( column_A, column_B, column_C )

これらのフィールドを使用して、結合、フィルタリング、および場合によっては選択を行うクエリが役立ちます。また、そのコンポジット内の列の左端のサブセットを使用するクエリにも役立ちます。したがって、上記のインデックスは、必要なクエリも満たします

index( column_A, column_B, column_C )
index( column_A, column_B )
index( column_A )

しかし、それは(少なくとも直接ではなく、より良いインデックスがなければ部分的に役立つかもしれません)必要なクエリには役立ちません

index( column_A, column_C )

Column_Bが欠落していることに注意してください。

元の例では、2つのディメンションの複合インデックスは、ほとんどの場合、両方のディメンションまたは左端のディメンションを単独でクエリするクエリに役立ちますが、右端のディメンション自体はクエリしません。常に2つのディメンションをクエリする場合は、複合インデックスを使用する方法がありますが、どちらが最初か(ほとんどの場合)は重要ではありません。

96
Mark Canlas

次の3つのクエリがあるとします。

クエリI:

SELECT * FROM homes WHERE `geolat`=42.9 AND `geolng`=36.4

クエリII:

SELECT * FROM homes WHERE `geolat`=42.9

クエリIII:

SELECT * FROM homes WHERE `geolng`=36.4

列ごとに個別のインデックスがある場合、3つのクエリはすべてインデックスを使用します。 MySQLでは、複合インデックス(geolatgeolng)がある場合、クエリIおよびクエリII(複合インデックスの最初の部分を使用)のみがインデックスを使用します。この場合、クエリIIIでは全表検索が必要です。

Multiple-Column Indexes マニュアルのセクションでは、複数のカラムインデックスがどのように機能するかが明確に説明されているため、マニュアルを再入力したくありません。

MySQLリファレンスマニュアルページ から:

複数列のインデックスは、インデックス付きの列の値を連結することで作成された値を含むソート済み配列と見なすことができます。

Geolat列とgeolng列に分離インデックスを使用する場合、独立して検索できる2つの異なるインデックスがテーブルにあります。

INDEX geolat
-----------
VALUE RRN
36.4  1
36.4  8
36.6  2
37.8  3
37.8  12
41.4  4

INDEX geolng
-----------
VALUE RRN
26.1  1
26.1  8
29.6  2
29.6  3
30.1  12
34.7  4

複合インデックスを使用する場合、両方の列に1つのインデックスのみがあります。

INDEX (geolat, geolng)
-----------
VALUE      RRN
36.4,26.1  1
36.4,26.1  8
36.6,29.6  2
37.8,29.6  3
37.8,30.1  12
41.4,34.7  4

RRNは相対レコード番号です(簡単にするために、IDと言うことができます)。最初の2つのインデックスは別々に生成され、3番目のインデックスは複合です。ご覧のように、geolatでインデックス付けされているため、コンポジットのgeolngに基づいて検索できますが、geolatまたは「geolat AND geolng」(geolngは第2レベルのインデックスであるため)で検索できます。

また、 MySQLがインデックスを使用する方法 マニュアルセクションもご覧ください。

48
Emre Yazici

複合インデックスが何をするかについての誤解があるかもしれません。多くの人は、where句がインデックス付きの列(あなたの場合はgeolatgeolng)をカバーする限り、複合インデックスを使用して検索クエリを最適化できると考えています。深く掘り下げましょう:

私はあなたの家の座標のデータはランダムな小数であると信じています:

home_id  geolat  geolng
   1    20.1243  50.4521
   2    22.6456  51.1564
   3    13.5464  45.4562
   4    55.5642 166.5756
   5    24.2624  27.4564
   6    62.1564  24.2542
...

geolatgeolngの値はほとんど繰り返されません。 geolatgeolngの複合インデックスは次のようになります。

index_id  geolat  geolng
   1     20.1243  50.4521
   2     20.1244  61.1564
   3     20.1251  55.4562
   4     20.1293  66.5756
   5     20.1302  57.4564
   6     20.1311  54.2542
...

したがって、複合インデックスの2番目の列は基本的にuselessです!複合インデックスを使用したクエリの速度は、おそらくgeolat列のインデックスとほぼ同じになるでしょう。

Willが述べたように、MySQLは spatial extension サポートを提供します。空間ポイントは、2つの個別のlatlng列ではなく、単一の列に格納されます。このような列には空間インデックスを適用できます。ただし、効率は私の個人的な経験に基づいて過大評価される可能性があります。空間インデックスは、2次元の問題を解決せず、R-Trees with quadratic splittingを使用して検索を高速化するだけである可能性があります。

トレードオフは、空間ポイント より多くのメモリを消費する であり、座標の格納に8バイトの倍精度数値を使用したためです。間違っている場合は修正してください。

18

複合インデックス

  • 0個以上の「=」句、プラス
  • 最大で1つの範囲句。

複合インデックスはtwo範囲を処理できません。 index cookbook でこれについてさらに説明します。

最も近いものを探す-質問が本当にある場合最適化について

WHERE geolat BETWEEN ??? AND ???
  AND geolng BETWEEN ??? AND ???

その後、noインデックスは実際に両方の次元を処理できます。

代わりに、「箱から出して考える」必要があります。 1つのディメンションがパーティション分割によって実装され、もう1つのディメンションがPRIMARY KEY、lat/lngルックアップの非常に大きなテーブルの効率が大幅に向上します。私の latlng blog は、地球上で「最近傍を検索」を実装する方法の詳細に進みます。コードが含まれています。

PARTITIONsは緯度範囲のストライプです。 PRIMARY KEYは意図的に経度で始まるため、有用な行が同じブロックにある可能性があります。ストアドルーチンは、order by... limit...そして、十分な数のコーヒーショップ(または何でも)ができるまでターゲットの周りに「正方形」を成長させます。また、大圏計算と日付変更線と極の処理も行います。

6
Rick James

複合インデックスは次のような非常に強力です。

  • 構造の完全性を強化する
  • FILTERED IDでの並べ替えを有効にする

構造の完全性を強制

複合インデックスは、単なる別のタイプのインデックスではありません。整合性を主キーとして強制することにより、テーブルに必要な構造を提供できます。

MysqlのInnodbはクラスタリングをサポートし、次の例は複合インデックスが必要な理由を示しています。

友達のテーブルを作成するには(ソーシャルネットワーク用)、2つの列が必要です:user_id, friend_id

テーブル構造

user_id (medium_int)
friend_id (medium_int)

Primary Key -> (user_id, friend_id)

美徳により、主キー(PK)は一意であり、複合PKを作成することにより、Innodbは、新しいレコードが追加されたときにuser_id, friend_idに重複がないことを自動的に確認します。これは、ユーザーがfriend_id = 2などのレコード(関係リンク)を1つ以上持つべきではないため、予想される動作です。

複合PKがなくても、代理キーを使用してこのスキーマを作成できます。

user_friend_id
user_id
friend_id

Primary Key -> (user_friend_id)

ここで、新しいレコードが追加されるたびに、user_id, friend_idの組み合わせを持つ前のレコードがまだ存在していないことを確認する必要があります。

そのため、複合インデックスは構造の整合性を強化できます。

フィルターされたIDで並べ替えを有効にする

投稿の時間(タイムスタンプまたは日時)で一連のレコードをソートすることは非常に一般的です。通常、これは特定のIDに投稿することを意味します。ここに例があります

テーブルUser_Wall_Posts(Facebookのウォール投稿を考えてみてください)

user_id (medium_int)
timestamp (timestamp)
author_id (medium_int)
comment_post (text)

Primary Key -> (user_id, timestamp, author_id)

user_id = 10のすべての投稿を照会して検索し、コメント投稿をtimestamp(日付)でソートします。

SQLクエリ

SELECT * FROM User_Wall_Posts WHERE user_id = 10 ORDER BY timestamp DES

複合PKにより、Mysqlはインデックスを使用して結果をフィルタリングおよびソートできます。 Mysqlは、結果を取得するために一時ファイルまたはファイルソートを使用する必要はありません。複合キーがなければ、これは不可能であり、非常に非効率的なクエリを引き起こします。

そのため、複合キーは非常に強力であり、「column_a, column_bを検索したいので、複合キーを使用します。現在のデータベーススキーマには、単一キーと同じ数の複合キーがあります。 。複合キーの使用を見落とさないでください!

5
ProfileTwist

複合インデックスは、group by句(この記事を確認してください http://dev.mysql.com/doc/refman/5.0/en/group-by-optimization.html )。注目してください:

GROUP BYのインデックスを使用するための最も重要な前提条件は、すべてのGROUP BY列が同じインデックスの属性を参照し、インデックスがキーを順番に格納することです(たとえば、これはBTREEインデックスであり、HASHインデックスではありません)

1
Alexander

空間検索を行うには、 R-Tree アルゴリズムが必要です。これにより、地理的領域を非常にすばやく検索できます。まさにこの仕事に必要なもの。

一部のデータベースには空間インデックスが組み込まれています。Googleの簡単な検索では、MySQL 5にそれらが含まれていることが示されています(SQLを見ると、MySQLを使用していると思われます)。

1
Will

白黒はなく、1つのサイズですべての答えに適合します。

クエリの作業負荷が複合インデックスの恩恵を受ける場合は、複合インデックスを使用する必要があります。

これを判断するには、クエリの作業負荷をプロファイルする必要があります。

複合インデックスは、そのインデックスからクエリを完全に満たすことができるときに機能します。

更新(投稿された質問の編集への応答):テーブルから*を選択している場合、複合インデックスは使用される場合があります。 EXPLAIN PLAN を実行する必要があります。

1
Mitch Wheat

私は@Mitchと一緒にいます、あなたのクエリに完全に依存しています。幸い、いつでもインデックスを作成および削除でき、クエリにEXPLAINキーワードを追加して、クエリアナライザーがインデックスを使用しているかどうかを確認できます。

exactlat/longペアを検索する場合、このインデックスはおそらく意味があります。ただし、特定の場所から一定の距離内にある家を探しているので、クエリは次のようになります( source を参照)。

select *, sqrt(  pow(h2.geolat - h1.geolat,  2) 
               + pow(h2.geolng - h1.geolng, 2) ) as distance
from homes h1, homes h2
where h1.home_id = 12345 and h2.home_id != h1.home_id
order by distance

インデックスはまったく役に立ちません。地理空間クエリの場合、 this のようなものが必要です。

更新:このクエリで:

SELECT * FROM homes
WHERE geolat BETWEEN ??? AND ???
AND geolng BETWEEN ??? AND ???

クエリアナライザーは、geolat単独のインデックス、geolng単独のインデックス、または場合によっては両方のインデックスを使用できます。複合インデックスを使用するとは思わない。しかし、実際のデータセットでこれらの順列をそれぞれ試してから、(a)EXPLAINが何を示しているかを確認し、(b)クエリに実際にかかる時間を測定するのは簡単です。

0
Jim Ferrans