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


174

MySQLの主キーとしてINTとVARCHARを使用する間に測定可能なパフォーマンスの違いはありますか?VARCHARを参照リストの主キー(米国の州、国コードなど)として使用し、同僚がINT AUTO_INCREMENTをすべてのテーブルの主キーとして使用することを避けたいのですが。

ここで詳しく説明するように、私の議論は、INTとVARCHARのパフォーマンスの違いは無視できるということです。INTの外部キー参照はすべて、参照を理解するためにJOINを必要とするため、VARCHARキーは情報を直接表示します。

それで、この特定のユースケースとそれに関連するパフォーマンスの問題の経験がある人はいますか?


3
私は「いいえ」と回答した投稿を作成しましたが、実行したテストの詳細がいくつかありますが、それはMySQLではなくSQL Serverでした。だから私は私の答えを削除しました。
Timothy Khouri

17
@ティモシー-あなたはそれを削除するべきではありませんでした。私はそれを投票する過程にありました。ほとんどのSQLデータベースサーバーには、同様のクエリプランナーと同様のパフォーマンスボトルネックがあります。
Paul Tomblin、

9
@ティモシーはあなたの結果を再投稿してください。
Jake McGraw、

2
非常に多くのコメントと回答は、結合に使用されるキーがあることを前提としています。ではない。キーは、データの一貫性のために使用されます-行の重複を回避するため(同じエンティティを表す複数の行)。任意の列(または列のセット)を結合で使用でき、結合が1対0または多くの列であることを保証するには、列が一意である必要があるだけです。一意のインデックスがあればそれが保証され、意味がある必要はありません。
Charles Bretana、2016年

回答:


78

サロゲートキーの代わりに、いわゆる自然キーと呼ばれるものを使用することで、いくつかの結合されたクエリを回避できるという利点があります。この利点がアプリケーションで重要かどうかを判断できるのは、あなただけです。

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

データベースのすべてのテーブルにどちらの戦略も使用しないでください。場合によっては、ナチュラルキーの方が適していることもありますが、サロゲートキーの方が適している場合もあります。

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


3
そして、時には(私見、多くの場合)両方がより良い、他のテーブルのFK参照、および結合に使用するサロゲート、およびデータの一貫性を保証する自然なキー
Charles Bretana

@CharlesBretana面白いですね。FKと並行してデータ整合性のために自然キーを使用することは一般的な方法ですか?私が最初に考えたのは、大きなテーブルに必要な追加のストレージは、価値のあるものにならないかもしれないということでした。どんな情報でも大歓迎です。FYI-私はまともなプログラミングの背景がありますが、私のSQLの経験は主にSELECTクエリに限定されています
Rob

2
@CharlesBretana「両方を保存する」を読んだとき、「冗長性」と「正規化されていない」と思います。これは、「これは台無しになる可能性があります」および「どちらかが変更された場合、両方が変更されていることを確認する必要がある」ことを意味します。冗長性がある場合、冗長性は常にデータの不整合になる可能性があるため、非常に適切な理由(完全に許容できないパフォーマンスなど)があるはずです。
jpmc26 2013年

3
@ jpmc26、冗長性や正規化の問題は一切ありません。サロゲートキーは、自然キーの値に意味のある接続を持たないため、変更する必要はありません。正規化に関して、あなたは何の正規化問題について話しているのですか?正規化は関係の意味のある属性に適用されます。代理キーの数値(実際、代理キー自体の概念そのもの)は、正規化のコンテキストの外に完全にあります。
Charles Bretana、2013年

1
そして、他の質問、具体的には州のテーブルについて、このテーブルに代理キーがあり、たとえば、frpom 1から50の値がある場合、州の郵便番号に別の一意のインデックスまたはキーを配置しなかった場合、 (そして、私の意見では、州名についても同様です)では、誰かがサロゲートキーの値が異なり、郵便番号や州名が同じである2つの行を入力できないようにするにはどうすればよいでしょうか。「NJ」、「New Jersey」の2つの行がある場合、クライアントアプリはそれをどのように処理しますか?自然キーはデータの一貫性を保証します!
Charles Bretana 2017

81

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

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


25
そうだね。私の最大のデータベースの1つに、ユーゴスラビアとソビエト連邦のエントリがあります。それらが主キーではないことをうれしく思います。
ポールトンブリン

8
@Steve、それではなぜANSI SQLはON UPDATE CASCADEの構文をサポートするのですか?
ビルカーウィン

5
不変性はキーの要件ではありません。いずれの場合も、代理キーも変更されることがあります。必要に応じてキーを変更しても問題ありません。
nvogel

9
ポール、データベースでソビエト連邦をロシアに変更しましたか?そして、SUが決して存在しないふりをしますか?そして、SUへのすべての参照は今ロシアを指しますか?
Dainius 2012

6
@alga私はSUで生まれたので、それが何か知っています。
Dainius

52

オンラインでのベンチマークの不足に少し悩まされたので、自分でテストを実行しました。

通常のベーシックでは実行しないことに注意してください。意図せず結果に影響を与えた可能性のある要素がないかセットアップと手順を確認し、懸念事項をコメントに投稿してください。

設定は次のとおりです。

  • インテル®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;

次に、各テーブルの1000万行を、次のような本質を持つ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));
  • 上記と同様の他のテーブル、のためのmyindex = 'califo'ためのcharテーブルとmyindex = 'california'するためのvarcharテーブル。

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;示します(w /いくつかの列は表示されていません)。

|-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
| 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 | 
|-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|

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


今は遅いのはわかっていますが、where条件に対してあまり理想的でない文字列を選択した場合の結果を知りたいと思っていました。「califo [rnia]」は、最初の文字を比較した後に不一致を破棄できるため、実際の一致をさらに確認するだけでよいため、理想的でした。"newham"のようなものは、より多くの特性を比較してすべての不一致を排除することが新しいので、より興味深い結果をもたらしました。また、整数をそのように制限すると、それらに対するオッズもスタックさます。私はそれらに少なくとも 26の値を与えたでしょう。
Uueerdo

15
10年前の質問では、これは単なる推測ではなく、実際のベンチマークに依存する2つの回答のうちの1つにすぎません。
エイドリアンベイカー

1
ただし、テーブルには主キーがありません。これは実際にはInnoDBではソートされたデータ構造です。整数ソートと文字列ソートの速度は異なるはずです。
Melkor

1
INDEX代わりに使用する@MelkorフェアポイントPRIMARY KEY。私は私の推論を覚えていません-おそらく一意性制約がPRIMARY KEYあるINDEXと仮定しました。ただし、物がfederico-razzoli.com/primary-key-in-innodbの InnoDBにどのように格納されるかに関するセクションを読んで、私の結果は依然として主キーに適用されると思い、値ルックアップのパフォーマンスの違いに関する質問に答えます。また、あなたのコメントは、ソートアルゴリズムのパフォーマンスを検討することを示唆していますが、これは、私が調査するユースケース(セット内の値をルックアップする)には当てはまりません。
JanŻankowski19年

1
ルックアップ操作では、(バイナリ検索のように)主キーフィールドでの比較も必要です。この場合、intはvarcharよりも少し速いはずです。しかし、実験が示唆したように、それはそれほど明白ではありません(または主キーがないためにクエリがすべて遅くなったためか)。挿入とルックアップについても同じだと思います。
Melkor

38

長さに依存します。varcharが20文字で、intが4の場合、intを使用すると、インデックスはディスク上のインデックススペースのページあたり5ノードの数になります...つまり、トラバースインデックスには、5分の1の物理および/または論理読み取りが必要です。

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

同時に、データの一貫性を保証するために、重要なすべてのテーブル重複行は(意味のあるテーブルの属性に基づいて複製)を挿入することができないことを保証するために意味のある数字以外の代替キー(または一意のインデックス)を持っています。

あなたが話している特定の用途(状態のルックアップなど)の場合、テーブルのサイズが非常に小さいため、それは本当に重要ではありません。一般に、数千行未満のテーブルのインデックスによるパフォーマンスへの影響はありません。 ..


承知しました?行ベースのほとんどのデータ形式はありませんか?キーの他に他のデータがあります。因子5はトピックではありませんか?
ManuelSchneid3r 2017

1
@ manuelSchneid3r、なに?ユートピック?いいえ、要因5は「utopic」ではありません。20を4で割っただけです。「データ形式の行ベース」とはどういう意味ですか?インデックスは「行ベース」ではなく、バランスの取れたツリー構造です。
Charles Bretana 2017年

36

絶対違う。

INT、VARCHAR、CHAR間のパフォーマンスチェックをいくつか実行しました。

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

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


42
varcharの長さを知らなくても意味がありません...それらが100バイト幅である場合、4バイトintと同じパフォーマンスが得られないことが保証されます
Charles Bretana

6
また、使用しているデータベースとデータベースのバージョンを知ることも役立ちます。ほとんどの場合、パフォーマンスの調整は行われ、バージョンごとに改善されています。
デイブブラック

VARCHARは、間違いなく、インデックスのサイズのために重要。そして、インデックスはメモリにどれだけ収まるかを決定します。そして、メモリ内のインデックスは、そうでないものよりもはるかに高速です。10m行の場合、そのインデックスに250MBのメモリを使用でき、問題はなかった可能性があります。しかし、100mの行がある場合、そのメモリはそれほど細かくありません。
ポールドレイパー

9

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

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


2
これが高くつくことを確かに知っていますか?それとも推測しているだけですか?
スティーブマクロード

もちろん、それはrdbmsの実装に依存しますが、私が理解していることから、ほとんどのサーバーは、インデックス作成の目的で実際の値のハッシュを保持します。それでも、それが比較的短いハッシュ(たとえば、10バイト)である場合でも、2 4バイトの整数よりも2 10バイトのハッシュを比較するのはさらに困難です。
Joel Coehoorn、2008

結合に長い(ワイド)キーを使用しないでください...しかし、それがテーブル内の行に固有のものを最もよく表す場合は、一意のキー(またはインデックス-同じもの)をこれらの自然な値を使用したテーブル。参加するための鍵はありません。あなたはあなたの心の望みに何でも参加できます。データの整合性を確保するための鍵があります。
Charles Bretana、2013

6

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

外部キーとしての参照の場合、自動インクリメント整数をサロゲートとして使用することは、2つの主な理由から良いアイデアです。
-まず、通常、結合で発生するオーバーヘッドが少なくなります。
-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

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


3

サロゲート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

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


2

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

また、ユーザーにデータベースへのインターフェースの書き込みを許可する場合は、代理キーが役立ちます。サードパーティは、代理キーが非常にまれな状況でのみ変更されるという事実に依存できます。


2

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

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

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

RDMS:MySQL 5.6

本来、事故のインデックスは15桁のvarchar(数字と文字)です。事故指数が決して変わらなくなったら、私は代理キーを持たないようにしました。i7(8コア)コンピューターでは、DWは、ディメンションに応じて1200万レコードのロード後にクエリを実行するのが遅くなりました。何度もやり直し、bigintサロゲートキーを追加した後、平均20%の速度パフォーマンスの向上が得られました。しかし、パフォーマンスの向上は低いですが、有効な試みです。私はMySQLのチューニングとクラスタリングで働いています。


1
パーティショニングを調べる必要があるようですね。
jcoffland 2014年

2

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

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

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

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

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


0

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


0

いつものように、包括的な答えはありません。'場合によります!' そして私は面白くない。元の質問についての私の理解は、小さなテーブルのキーに関するものでした。たとえば、Country(整数IDまたはchar / varcharコード)は、アドレス/連絡先テーブルのような潜在的に巨大なテーブルへの外部キーです。

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

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

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

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


0

パフォーマンスの範囲を考慮して、確かに違いがあることを「はい」と言っておきます(標準の定義)。

1-コードまたはクエリでToUpper()、ToLower()、ToUpperInvarient()、またはToLowerInvarient()を使用する必要がなく、これら4つの関数のパフォーマンスベンチマークが異なるため、サロゲートintを使用するとアプリケーションでより高速になります。これに関するマイクロソフトのパフォーマンスルールを参照してください。(アプリの性能)

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

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

弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.