web-dev-qa-db-ja.com

INTとVARCHAR主キーの間に実際のパフォーマンスの違いはありますか?

MySQLで主キーとしてINTとVARCHARを使用する場合、測定可能なパフォーマンスの違いはありますか?参照リストの主キーとしてVARCHARを使用したいので(米国の州、国コードを考えてください)、同僚はすべてのテーブルの主キーとしてINT AUTO_INCREMENTを使用しません。

詳細な here のように、私の引数は、INTとVARCHARのパフォーマンスの違いはごくわずかであるということです。情報。

だから、この特定のユースケースとそれに関連するパフォーマンスの懸念を経験した人はいますか?

156
Jake McGraw

-の代わりに natural key と呼ばれるものを使用することで、いくつかの結合されたクエリを回避できるという良い点を示します。 サロゲートキー このメリットがアプリケーションで重要かどうかを評価できるのはあなただけです。

つまり、大量のデータを処理したり、非常に頻繁に実行されたりするため、迅速に処理するために最も重要なクエリをアプリケーションで測定できます。これらのクエリが結合を削除することでメリットが得られ、varchar主キーを使用しても問題がない場合は、実行してください。

データベースのすべてのテーブルにどちらの戦略も使用しないでください。場合によっては、自然キーの方が優れている可能性がありますが、他の場合では、代理キーの方が優れています。

他の人々は、自然のキーが決して変更されたり重複したりすることは実際にはまれであるため、代理キーは通常価値があると指摘しています。

71
Bill Karwin

パフォーマンスではありません。それは良い主キーを作るものについてです。ユニークで時間とともに変化しません。国コードなどのエンティティは時間とともに変化することはなく、主キーの適切な候補になると考えるかもしれません。しかし、苦い経験はほとんどありません。

INT AUTO_INCREMENTは、「時間とともに一意かつ不変」の条件を満たします。したがって、好み。

78
Steve McLeod

長さによるインデックスには、物理​​的読み取りや論理的読み取りの5分の1が必要です。

そのため、機会があればパフォーマンスが問題になる場合は、テーブルと、これらのテーブルの行を参照する外部キーに、統合された意味のないキー(サロゲートと呼ばれる)を常に使用してください...

同時に、データの一貫性を保証するために、重要なすべてのテーブルはalso意味のある非数値代替キー(または一意のインデックス)を使用して、重複行を挿入できないようにします(意味のあるテーブル属性に基づいて複製します)。

あなたが話している特定の用途(状態検索など)では、テーブルのサイズが非常に小さいため、実際には問題になりません。一般に、数千行未満のテーブルのインデックスからのパフォーマンスへの影響はありません。 ..

34
Charles Bretana

絶対違う。

INT、VARCHAR、およびCHAR間のパフォーマンスチェックを何度か行いました。

PRIMARY KEY(一意でクラスター化された)を持つ1,000万件のレコードテーブルは、どの3つを使用しても、まったく同じ速度とパフォーマンス(およびサブツリーコスト)を持ちました。

つまり、アプリケーションに最適なものを使用してください。パフォーマンスについて心配する必要はありません。

33
Timothy Khouri

このオンラインのベンチマークがないことに少しイライラしていたので、自分でテストを実行しました。

ただし、私は通常の基本ではそれをしないので、結果に意図せず影響を与えた可能性のある要因についてはセットアップと手順を確認し、コメントに懸念を投稿してください

設定は次のとおりです。

  • Intel®Core™i7-7500U CPU @ 2.70GHz×4
  • 15.6 GiB RAM。テスト中に約8 GBの空き容量を確保しました。
  • 148.6 GB SSDドライブ、十分な空き容量。
  • Ubuntu 16.04 64ビット
  • MySQL Ver 14.14 Distrib 5.7.20、Linux(x86_64)向け

テーブル:

create table jan_int (data1 varchar(255), data2 int(10), myindex tinyint(4)) ENGINE=InnoDB;
create table jan_int_index (data1 varchar(255), data2 int(10), myindex tinyint(4), INDEX (myindex)) ENGINE=InnoDB;
create table jan_char (data1 varchar(255), data2 int(10), myindex char(6)) ENGINE=InnoDB;
create table jan_char_index (data1 varchar(255), data2 int(10), myindex char(6), INDEX (myindex)) ENGINE=InnoDB;
create table jan_varchar (data1 varchar(255), data2 int(10), myindex varchar(63)) ENGINE=InnoDB;
create table jan_varchar_index (data1 varchar(255), data2 int(10), myindex varchar(63), INDEX (myindex)) ENGINE=InnoDB;

次に、各テーブルの1,000万行に、本質が次のようなPHPスクリプトを入力しました。

$pdo = get_pdo();

$keys = [ 'alabam', 'massac', 'newyor', 'newham', 'delawa', 'califo', 'nevada', 'texas_', 'florid', 'ohio__' ];

for ($k = 0; $k < 10; $k++) {
    for ($j = 0; $j < 1000; $j++) {
        $val = '';
        for ($i = 0; $i < 1000; $i++) {
            $val .= '("' . generate_random_string() . '", ' . Rand (0, 10000) . ', "' . ($keys[Rand(0, 9)]) . '"),';
        }
        $val = rtrim($val, ',');
        $pdo->query('INSERT INTO jan_char VALUES ' . $val);
    }
    echo "\n" . ($k + 1) . ' millon(s) rows inserted.';
}

intテーブルでは、ビット($keys[Rand(0, 9)])Rand(0, 9)に置き換えられ、varcharテーブルでは、完全な米国の州名を使用しました。6文字にカットまたは拡張しません。 generate_random_string()は、10文字のランダムな文字列を生成します。

次に、MySQLを実行しました。

  • SET SESSION query_cache_type=0;
  • jan_intテーブルの場合:
    • SELECT count(*) FROM jan_int WHERE myindex = 5;
    • SELECT BENCHMARK(1000000000, (SELECT count(*) FROM jan_int WHERE myindex = 5));
  • 上記と同じ他のテーブルの場合、charテーブルにはmyindex = 'califo'varcharテーブルにはmyindex = 'california'を使用します。

各テーブルのBENCHMARKクエリの時間:

  • jan_int:21.30秒
  • jan_int_index:18.79秒
  • jan_char:21.70秒
  • jan_char_index:18.85秒
  • jan_varchar:21.76秒
  • jan_varchar_index:18.86秒

テーブルとインデックスのサイズに関して、show table status from janperformancetest;の出力は次のとおりです(一部の列は表示されていません)。

|-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
| Name              | Engine | Version | Row_format | Rows    | Avg_row_length | Data_length | Max_data_length | Index_length | Data_free | Auto_increment | Collation              |
|-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
| jan_int           | InnoDB |      10 | Dynamic    | 9739094 |             43 |   422510592 |               0 |            0 |   4194304 |           NULL | utf8mb4_unicode_520_ci |  
| jan_int_index     | InnoDB |      10 | Dynamic    | 9740329 |             43 |   420413440 |               0 |    132857856 |   7340032 |           NULL | utf8mb4_unicode_520_ci |   
| jan_char          | InnoDB |      10 | Dynamic    | 9726613 |             51 |   500170752 |               0 |            0 |   5242880 |           NULL | utf8mb4_unicode_520_ci |  
| jan_char_index    | InnoDB |      10 | Dynamic    | 9719059 |             52 |   513802240 |               0 |    202342400 |   5242880 |           NULL | utf8mb4_unicode_520_ci |  
| jan_varchar       | InnoDB |      10 | Dynamic    | 9722049 |             53 |   521142272 |               0 |            0 |   7340032 |           NULL | utf8mb4_unicode_520_ci |   
| jan_varchar_index | InnoDB |      10 | Dynamic    | 9738381 |             49 |   486539264 |               0 |    202375168 |   7340032 |           NULL | utf8mb4_unicode_520_ci | 
|-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|

私の特定のユースケースではパフォーマンスの違いはないという結論です

28
Jan Żankowski

短いコードの場合、おそらく違いはありません。これは、これらのコードを保持するテーブルが非常に小さく(せいぜい数千行)、頻繁に変更されない可能性が高いためです(最後に新しい米国州を追加したとき)。

キー間のバリエーションが広い大きなテーブルの場合、これは危険です。たとえば、ユーザーテーブルの電子メールアドレス/ユーザー名を使用することを検討してください。数百万人のユーザーがいて、それらのユーザーの一部が長い名前または電子メールアドレスを持っているとどうなりますか。そのキーを使用してこのテーブルを結合する必要があるときはいつでも、はるかに高価になります。

9
Joel Coehoorn

主キーについては、物理的に行を一意にするものはすべて主キーとして決定する必要があります。

外部キーとしての参照の場合、自動インクリメント整数をサロゲートとして使用することは、主に2つの理由で良い考えです。
-最初に、通常、結合で発生するオーバーヘッドが少なくなります。
-次に、一意のvarcharを含むテーブルを更新する必要がある場合、更新はすべての子テーブルにカスケードされ、すべての子テーブルとインデックスを更新する必要がありますが、intサロゲートでは、マスターテーブルとそのインデックスを更新するだけです。

サロゲートを使用することの欠点は、サロゲートの意味の変更を許可できる可能性があることです。

ex.
id value
1 A
2 B
3 C

Update 3 to D
id value
1 A
2 B
3 D

Update 2 to C
id value
1 A
2 C
3 D

Update 3 to B
id value
1 A
2 C
3 B

それはすべて、あなたがあなたの構造で本当に心配する必要があるものと、何を意味するかに依存します。

6
LeppyR64

代理AUTO_INCREMENTが痛い一般的なケース:

一般的なスキーマパターンは多対多マッピングです。

CREATE TABLE map (
    id ... AUTO_INCREMENT,
    foo_id ...,
    bar_id ...,
    PRIMARY KEY(id),
    UNIQUE(foo_id, bar_id),
    INDEX(bar_id) );

特にInnoDBを使用している場合、このパターンのパフォーマンスははるかに優れています。

CREATE TABLE map (
    # No surrogate
    foo_id ...,
    bar_id ...,
    PRIMARY KEY(foo_id, bar_id),
    INDEX      (bar_id, foo_id) );

どうして?

  • InnoDBセカンダリキーには追加のルックアップが必要です。ペアをPKに移動することにより、一方向では回避されます。
  • セカンダリインデックスは「カバー」されているため、追加のルックアップは必要ありません。
  • idと1つのインデックスを削除するため、このテーブルは小さくなります。

別のケース():

country_id INT ...
-- versus
country_code CHAR(2) CHARACTER SET ascii

多くの場合、初心者は「自然な」2バイトのほとんど変更されない2バイト文字列を使用する代わりに、country_codeを4バイトINTに正規化します。より速く、より小さく、より少ないJOIN、より読みやすい。

3
Rick James

質問はMySQLに関するものなので、大きな違いがあると言います。 Oracleについて(数字を文字列として保存する-はい、最初は信じられませんでした)については、それほど違いはありません。

テーブル内のストレージは問題ではありませんが、インデックスの更新と参照は問題です。主キーに基づいてレコードを検索することを含むクエリは頻繁に発生します。頻繁に発生するため、できるだけ速く実行する必要があります。

問題は、CPUがsiliconで4バイトと8バイトの整数を自然に処理することです。 2つの整数を比較するのは非常に高速です。1または2クロックサイクルで発生します。

今、文字列を見てください-それは多くの文字で構成されています(最近では文字ごとに1バイト以上)。 1つまたは2つのサイクルで2つの文字列の優先順位を比較することはできません。代わりに、違いが見つかるまで文字列の文字を繰り返す必要があります。一部のデータベースでは高速化するためのコツがあるはずですが、int比較は自然に行われ、CPUによってシリコンで高速に処理されるため、ここでは関係ありません。

私の一般的なルール-すべての主キーは、特にオブジェクト間に多くの関係があるORM(Hibernate、Datanucleusなど)を使用するOOアプリで自動インクリメントINTである必要があります-通常、常に単純に実装されますFKとDBがそれらを迅速に解決する機能は、アプリの応答性にとって重要です。

2
Volksman

HauteLookでは、自然キーを使用するようにテーブルの多くを変更しました。実際にパフォーマンスが向上しました。おっしゃるように、クエリの多くは結合の使用が少なくなり、クエリのパフォーマンスが向上しています。理にかなっている場合は、複合主キーも使用します。そうは言っても、一部のテーブルは、代理キーがあれば簡単に操作できます。

また、人々にデータベースへのインターフェイスを作成させる場合、代理キーが役立つ場合があります。サードパーティは、代理キーが非常にまれな状況でのみ変更されるという事実に依存できます。

私は同じジレンマに直面しました。道路事故、事故の車両、事故の死傷者の3つのファクトテーブルでDW(コンステレーションスキーマ)を作成しました。データには、1979年から2012年までに英国で記録されたすべての事故と60のディメンション表が含まれます。合わせて、約2,000万件のレコード。

ファクトテーブルの関係:

+----------+          +---------+
| Accident |>--------<| Vehicle |
+-----v----+ 1      * +----v----+
     1|                    |1
      |    +----------+    |
      +---<| Casualty |>---+
         * +----------+ *

RDMS:MySQL 5.6

本来、事故インデックスは15桁のvarchar(数字と文字)です。アクシデントインデックスが変更されない場合、代理キーを使用しないようにしました。 i7(8コア)コンピューターでは、ディメンションに応じて1,200万レコードの負荷が発生した後、DWのクエリが遅くなりすぎました。何度もやり直し、bigintサロゲートキーを追加した後、平均20%の速度のパフォーマンスが向上しました。まだパフォーマンスゲインは低いですが、有効な試行です。 MySQLのチューニングとクラスタリングで働いています。

1
Diego Duarte

パフォーマンスの範囲を考慮して、間違いなく違いがあります(すぐに使える定義):

1-アプリケーションでサロゲートintを使用する方が高速ですこれについては、Microsoftのパフォーマンスルールを参照してください。 (アプリケーションのパフォーマンス)

2-サロゲートintを使用すると、時間の経過とともにキーが変更されないことが保証されます。国コードでさえ変更される可能性があります。WikipediaでISOコードが時間とともにどのように変更されたかを参照してください。サブツリーの主キーを変更するには、多くの時間がかかります。 (データ保守のパフォーマンス)

3- PK/FKがintでない場合、NHibernateなどのORMソリューションに問題があるようです。 (開発者のパフォーマンス)

0
Shadi Namrouti

パフォーマンスへの影響についてはわかりませんが、少なくとも開発中は、自動インクリメントされた整数の「代理」キーと、意図した一意の「自然」キーの両方を含めることが妥協の可能性があるようです。これにより、パフォーマンスだけでなく、自然キーの変更可能性などの他の考えられる問題を評価する機会が与えられます。

0
George Jempty

いつものように、包括的な答えはありません。 '場合によります!'そして、私は面白くありません。元の質問の私の理解は、小さなテーブルのキーについてでした-Country(integer idまたはchar/varchar code)がaddress/contactテーブルのような潜在的に巨大なテーブルへの外部キーであるように。

DBからデータを戻す場合、2つのシナリオがあります。 1つ目は、州/国コードまたは名前を持つすべての連絡先をリストするリスト/検索クエリです(IDは役に立たないため、ルックアップが必要です)。もう1つは、国の名前を表示する必要がある単一の連絡先レコードを示す主キーの取得シナリオです。

後者の取得については、単一のレコードまたはいくつかのレコードのテーブルとキー読み取りをまとめているため、おそらくFKが何に基づいているかは重要ではありません。前者(検索またはリスト)のシナリオは、選択によって影響を受ける可能性があります。国を表示する必要があるため(少なくとも認識可能なコードと、おそらく検索自体に国コードが含まれる場合もあります)、サロゲートキーを介して別のテーブルに参加する必要がない可能性があります(実際にテストしていないため、ここでは注意が必要です)これは可能性が高いと思われますが)パフォーマンスを向上させます。それは確かに検索に役立つという事実にもかかわらず。

コードはサイズが小さいため、通常は国や州で3文字を超えないため、このシナリオでは自然キーを外部キーとして使用してもかまいません。

キーがより長いvarchar値に依存し、おそらくより大きなテーブルに依存するもう1つのシナリオ。代理キーにはおそらく利点があります。

0
Vinod