私はWebアプリケーション(プロジェクト管理システム)を構築していますが、パフォーマンスに関してはこれについて疑問に思っています。
その中には、Issuesテーブルがあり、他のさまざまなテーブルにリンクする12個の外部キーがあります。これらのうち8つは、レコードをWebアプリケーションで意味のあるものにするために他のテーブルからタイトルフィールドを取得するために結合する必要がありますが、8つの結合を実行することを意味します。これらの結合ごとに1つのフィールド。
現在、永続的な理由で自動インクリメントの主キーを使用するように指示されています(シャーディングがGUIDを使用する必要がある場合を除く)が、varchar(最大長32)のパフォーマンスを使用するのはどれほど悪いですか?つまり、これらのテーブルのほとんどは、多くのレコードに含まれないでしょう(それらのほとんどは20未満である必要があります)。また、タイトルを主キーとして使用する場合、95%の時間で結合を行う必要がないため、SQLの95%でパフォーマンスヒットが発生することもあります(私はそう思います)。私が考えられる唯一の欠点は、ディスク容量の使用率が高くなることです(ただし、1日のダウンは非常に大きな問題です)。
列挙型の代わりにこのようなものの多くにルックアップテーブルを使用するのは、これらの値をすべて、アプリケーション自体を通じてエンドユーザーが構成できるようにする必要があるためです。
多くのレコードを持つことを除いて、varcharをテーブルの主キーとして使用することの欠点は何ですか?
PDATE-いくつかのテスト
だから私はこれについていくつかの基本的なテストを行うことにしました。 100000レコードあり、これらは基本クエリです。
ベースVARCHAR FKクエリ
SELECT i.id, i.key, i.title, i.reporterUserUsername, i.assignedUserUsername, i.projectTitle,
i.ProjectComponentTitle, i.affectedProjectVersionTitle, i.originalFixedProjectVersionTitle,
i.fixedProjectVersionTitle, i.durationEstimate, i.storyPoints, i.dueDate,
i.issueSecurityLevelId, i.creatorUserUsername, i.createdTimestamp,
i.updatedTimestamp, i.issueTypeId, i.issueStatusId
FROM ProjectManagement.Issues i
ベースINT FKクエリ
SELECT i.id, i.key, i.title, ru.username as reporterUserUsername,
au.username as assignedUserUsername, p.title as projectTitle,
pc.title as ProjectComponentTitle, pva.title as affectedProjectVersionTitle,
pvo.title as originalFixedProjectVersionTitle, pvf.title as fixedProjectVersionTitle,
i.durationEstimate, i.storyPoints, i.dueDate, isl.title as issueSecurityLevelId,
cu.username as creatorUserUsername, i.createdTimestamp, i.updatedTimestamp,
it.title as issueTypeId, is.title as issueStatusId
FROM ProjectManagement2.Issues i
INNER JOIN ProjectManagement2.IssueTypes `it` ON it.id = i.issueTypeId
INNER JOIN ProjectManagement2.IssueStatuses `is` ON is.id = i.issueStatusId
INNER JOIN ProjectManagement2.Users `ru` ON ru.id = i.reporterUserId
INNER JOIN ProjectManagement2.Users `au` ON au.id = i.assignedUserId
INNER JOIN ProjectManagement2.Users `cu` ON cu.id = i.creatorUserId
INNER JOIN ProjectManagement2.Projects `p` ON p.id = i.projectId
INNER JOIN ProjectManagement2.`ProjectComponents` `pc` ON pc.id = i.projectComponentId
INNER JOIN ProjectManagement2.ProjectVersions `pva` ON pva.id = i.affectedProjectVersionId
INNER JOIN ProjectManagement2.ProjectVersions `pvo` ON pvo.id = i.originalFixedProjectVersionId
INNER JOIN ProjectManagement2.ProjectVersions `pvf` ON pvf.id = i.fixedProjectVersionId
INNER JOIN ProjectManagement2.IssueSecurityLevels isl ON isl.id = i.issueSecurityLevelId
また、これらのクエリを実行し、次の追加を行いました。
これらの結果:
クエリタイプ:VARCHAR FK TIME/INT FK TIME
基本クエリ:〜4ms /〜52ms
特定のアイテムを選択:〜140ms /〜250ms
I.idでグループ化:〜4ms /〜2.8sec
による注文:〜231ms /〜2sec
制限:〜67ms /〜343ms
グループ化して一緒に制限:〜504ms /〜2秒
グループ化、注文、制限を一緒に:〜504ms /~2.3sec
今、私はどちらか一方(または両方)をより速くするためにどのような構成をとることができるかわかりませんが、VARCHAR FKはデータのクエリでより速く見えます(時にははるかに速く)ようです。
その速度の向上が追加のデータ/インデックスサイズに見合うかどうかを選択する必要があると思います。
主キーについては、次のルールに従います。
a)ビジネス上の意味はないはずです-開発中のアプリケーションから完全に独立している必要があるため、自動生成された整数を使用します。ただし、一意にするために追加の列が必要な場合は、それをサポートする一意のインデックスを作成します。
b)結合で実行する必要があります-varcharsと整数の結合は、主キーの長さが長くなると約2倍から3倍遅くなるため、キーを整数として使用します。すべてのコンピュータシステムはバイナリなので、文字列がバイナリに変更されてから、他のシステムと比較すると非常に遅いと思います
c)可能な限り最小のデータ型を使用します-テーブルに52 US州などの列がほとんどないことが予想される場合、2桁のコードにはCHAR(2)を使用できる可能性のある最小の型を使用しますが、私はまだtinyint (128)カラムと最大20億までの大きなint
また、たとえば、プロジェクト名が変更された場合(これは珍しいことではありません)、主キーから他のテーブルへの変更をカスケードする際に課題があります。
主キーに整数を順次インクリメントしていき、データベースシステムが将来の変更をサポートする組み込みの効率を実現します
テストでは、varcharとintキーのパフォーマンスの違いを比較するのではなく、複数の結合のコストを比較します。 1つのテーブルをクエリする方が多くのテーブルを結合するよりも高速であることは当然のことです。
atxdbaが指摘しているように、varchar主キーの欠点の1つはインデックスサイズの増加です。ルックアップテーブルにPKを除いて他のインデックスがない場合(これは非常にありそうもありませんが、可能です)、ルックアップを参照する各テーブルには、この列にインデックスがあります。
自然な主キーのもう1つの悪い点は、それらの値が変化し、カスケード更新が大量に発生する可能性があることです。すべてのRDMS、たとえばOracleでは、on update cascade
。一般的に、非常に悪い習慣と見なして主キーの値を変更します。自然な主キーが常に悪であるとは言いたくありません。ルックアップ値が小さく、決して変更されない場合、私は許容できると思います。
検討すべきオプションの1つは、マテリアライズドビューを実装することです。 Mysqlはこれを直接サポートしていませんが、基になるテーブルのトリガーを使用して目的の機能を実現できます。したがって、表示する必要があるすべてのものが1つのテーブルになります。また、パフォーマンスが許容できる場合は、現時点では存在しない問題に苦労しないでください。
最大の欠点は、PKの繰り返しです。ディスク領域の使用量の増加を指摘しましたが、明確にするために、インデックスサイズの増加が大きな懸念事項です。 innodbはクラスター化インデックスであるため、すべてのセカンダリインデックスは、最終的に一致するレコードを見つけるために使用するPKのコピーを内部的に格納します。
あなたは、テーブルが「小さい」ことが期待されていると言います(実際、20行は非常に小さい)。 RAMでinnodb_buffer_pool_sizeを
select sum(data_length+index_length) from information_schema.tables where engine='innodb';
その後、それを行うと、おそらくかなり座っているでしょう。ただし、一般的なルールとして、システムメモリ全体の少なくとも30%-40%を他のmysqlのオーバーヘッドとキャッシュに残すことをお勧めします。そして、それはそれが専用のDBサーバーであると想定しています。システムで他のものが実行されている場合は、それらの要件も考慮する必要があります。
@atxdbaの回答に加えて、ディスク容量に数値を使用した方が良い理由を説明したので、2つのポイントを追加したいと思います。
IssuesテーブルがVARCHAR FKベースで、20個の小さなVARCHAR(32)FKがあるとすると、レコードは20x32バイトの長さになり、他のテーブルはルックアップテーブルであるため、INT FKはTINYINT FKになる可能性があります。 20フィールドの場合、20バイトのレコード。私は数百のレコードについてはそれほど変わらないことを知っていますが、数百万に達すると、スペース節約に感謝するでしょう
速度の問題については、カバーインデックスの使用を検討します。このクエリでは、インデックスをカバーするためにルックアップテーブルから大量のデータを取得しておらず、VARCHAR FK/W/COVERINGで提供されるテストをもう一度実行しますインデックスと通常のINT FK。
それが役に立てば幸い、