MySQL の高度な記事 - インデックス、プレフィックス インデックス、インデックス プッシュダウン、SQL 最適化、主キー設計をカバー

ナビゲーション:   

[Java メモ + ピットを踏む概要] Java の基礎 + 上級 + JavaWeb + SSM + SpringBoot + セント レジス テイクアウェイ + SpringCloud + ダークホース ツーリズム + Guli Mall + Xuecheng Online + MySQL 上級章 + デザイン モード + Nioke のインタビューの質問

目次

8. カバーインデックスの優先順位付け

8.1 カバリングインデックスとは何ですか?

8.1.0 コンセプト 

8.0.1 カバーリングインデックスの場合、「等しくない」インデックスが有効になります

8.0.2 カバリングインデックスの場合、左側のファジークエリインデックスが有効になります

8.2 カバーインデックスの長所と短所

9. 文字列にインデックスを追加する

9.1 プレフィックスインデックス

9.2 プレフィックスインデックスはカバリングインデックスを使用できません

10. インデックスのプッシュダウン

10.1 はじめに

10.2 ICPの使用条件

10.3 ICPのオン/オフ

10.4 ICP の使用例

10.5 ICP を有効にした場合と無効にした場合のパフォーマンスの比較

11. 通常のインデックスと一意のインデックス

11.1 クエリのおおよそのパフォーマンス

11.2 通常のインデックス更新パフォーマンスが向上、バッファ変更

11.3 変更バッファの使用シナリオ

12. SQLの最適化

12.1 EXISTS と IN の違い

12.2 COUNT(*) または COUNT(1) を推奨する

12.3 SELECT(*) の代わりに SELECT(field) を提案する

12.4 最適化に対する LIMIT 1 の影響

12.5 COMMIT をより頻繁に使用する

13. 主な設計アイデア

13.1 自動インクリメント主キーの欠点

13.2 ビジネスフィールドを主キーとして使用しないようにする

13.3 淘宝網注文番号の主キーの設計

13.4 推奨される主キーの設計

13.4.1 中核事業と非中核事業の主キー戦略の選択

13.4.2 UUIDの特徴

13.4.3 MySQL 8.0 の主キー スキーム: 順序付けされた UUID

13.4.4 MySQL8.0 より前の主キー スキーム: 手動割り当て

13.3.5 スノーフレークアルゴリズム


8. カバーインデックスの優先順位付け

8.1 カバリングインデックスとは何ですか?

8.1.0 コンセプト 

カバリングインデックス:クエリ結果を満たすデータを含むインデックスをカバリングインデックスと呼び、テーブルに返すなどの操作が必要ありません。

インデックスは行を効率的に検索する方法の 1 つですが、一般にデータベースはインデックスを使用して列のデータを検索することもできるため、行全体を読み取る必要はありません。結局のところ、インデックス リーフ ノードにはインデックス付けされたデータが格納され、インデックスを読み取ることで目的のデータが取得できる場合は、行を読み取る必要はありません。

カバー インデックス非クラスター化インデックスの形式で、クエリ内の SELECT、JOIN、および WHERE 句で使用されるすべての列が含まれます (つまり、インデックス付きフィールドは、カバーされるクエリ条件に関係するフィールドとまったく同じです)。簡単に言えば、インデックス列 + 主キーには、SELECT と FROM の間でクエリされた列が含まれます

8.0.1 カバーリングインデックスの場合、「等しくない」インデックスが有効になります

カバーするインデックスがない場合、「等しくない」インデックスは無効になります。

カバーするインデックスがない場合、「等しくない」を使用するとインデックスが失敗します。インデックスを使用する場合、非クラスター化インデックス B+ ツリー内のすべてのリーフ ノードを順番に走査する必要があり、時間計算量は O(n) であり、レコードを見つけた後にテーブルに戻る必要があるためです。フル テーブル スキャンほど優れていないため、クエリ オプティマイザーはフル テーブル スキャンを選択します。

CREATE INDEX idx_age_name ON student(age, NAME);
#查所有字段,并且使用“不等于”,索引失效
EXPLAIN SELECT * FROM student WHERE age <> 20;

カバーインデックスの場合、「等しくない」インデックスが有効になります。

カバーインデックスでは、チェック対象の 2 つのフィールドが結合インデックスでカバーされ、パフォーマンスが向上します。非クラスター化インデックス B+ ツリー内のすべてのリーフ ノードを順番に走査する必要はありますが、時間計算量は O(n) ですが、テーブルを返す必要がないため、インデックスを使用しない場合よりも全体的な効率が高くなります。 、クエリ オプティマイザーはインデックスを再度使用します。

CREATE INDEX idx_age_name ON student(age, NAME);
#查的两个字段正好被联合索引“idx_age_name ”覆盖了,索引成功
EXPLAIN SELECT age,name FROM student WHERE age <> 20;

8.0.2 カバリングインデックスの場合、左側のファジークエリインデックスが有効になります

カバーするインデックスがない場合、左ファジー クエリによりインデックスが失敗します。

#没覆盖索引的情况下,左模糊查询导致索引失效
CREATE INDEX idx_age_name ON student(age, NAME);
EXPLAIN SELECT * FROM student WHERE NAME LIKE '%abc';

カバリングインデックスの場合、左側のファジークエリインデックスが有効になります。

主な理由は、非クラスター化インデックス B+ ツリーがテーブルに戻らずにリーフ ノードを走査するため、効率がテーブル全体のスキャンよりも高く、クエリ オプティマイザーが高効率のソリューションを選択するためです。

#有覆盖索引的情况下,左模糊查询索引生效
CREATE INDEX idx_age_name ON student(age, NAME);
EXPLAIN SELECT id,age,NAME FROM student WHERE NAME LIKE '%abc';

上記のすべてでは、宣言されたインデックスが使用されていますが、次の状況は当てはまりません。クエリ列にはさらに多くの classId があり、その結果、インデックスは使用されません。

CREATE INDEX idx_age_name ON student(age, NAME);
EXPLAIN SELECT id,age,NAME,classId FROM student WHERE NAME LIKE '%abc';

8.2 カバーインデックスの長所と短所

利点:

1. テーブルに戻らないようにします (Innodb テーブルのインデックス作成のための 2 番目のクエリ)。

Innodb はクラスタード インデックスの順に格納されます。lnnodb の場合、セカンダリ インデックスはリーフ ノードの行の主キー情報を格納します。セカンダリ インデックスを使用してデータをクエリする場合は、対応するキー値を見つけた後、実際に必要なデータを取得するには、主キーを介して二次クエリを実行する必要があります。

カバリングインデックスでは、必要なデータをセカンダリインデックスのキー値で取得できるため、主キーのセカンダリクエリを回避でき、IO操作が軽減され、クエリ効率が向上します。

2. ランダム IO をシーケンシャル IO に変更してクエリ効率を向上させることができます。

カバーインデックスはキー値の順に格納されるため、IO 集中型の範囲検索では、各行のデータ I0 がディスクからランダムに読み取られ、インデックス検索では読み取り IO がシーケンシャル IO になります。

カバリング インデックスを使用するとツリー検索の数が減り、クエリのパフォーマンスが大幅に向上するため、カバリング インデックスの使用は一般的なパフォーマンス最適化方法です。

短所:

特定の問題を詳細に分析する必要があります。

インデックス付きフィールドの維持には常にコストがかかります。したがって、カバーインデックスをサポートするインデックスの数を確立する際には、トレードオフを考慮する必要があります。これはビジネス DBA、つまりビジネス データ アーキテクトの仕事です。

9. 文字列にインデックスを追加する

9.1 プレフィックスインデックス

教師テーブルがあり、テーブル定義は次のとおりです。

create table teacher(
ID bigint unsigned primary key,
email varchar(64),
...
)engine=innodb;

講師は電子メール アドレスでログインする必要があるため、ビジネス コードに次のようなステートメントを含める必要があります。

mysql> select col1, col2 from teacher where email='xxx';

電子メールフィールドにインデックスがない場合、このステートメントはテーブル全体のスキャンのみを実行できます。

MySQL はプレフィックス インデックスをサポートしています。デフォルトでは、プレフィックスの長さを指定せずにインデックスを作成すると、インデックスには文字列全体が含まれます。

mysql> alter table teacher add index index1(email);
#或
mysql> alter table teacher add index index2(email(6));

データ構造とストレージの点で、これら 2 つの異なる定義の違いは何ですか? 以下の図は、これら 2 つの指標の模式図です。

同様に

Index1 が使用される場合(インデックスには文字列全体が含まれます)、実行順序は次のようになります。

  1. Index1 のインデックス ツリーから「[email protected]」のインデックス値を満たすレコードを検索し、ID2 の値を取得します。
  2. テーブルに戻って、主キーの値が ID2 である行を主キーで見つけ、電子メールの値が正しいと判断し、この行レコードを結果セットに追加します。
  3. Index1 のインデックス ツリー上で見つかった位置にある次のレコードを取得すると、email='[email protected]' の条件が満たされなくなっていることがわかり、ループが終了します。

このプロセスでは、主キー インデックスからデータを 1 回取得するだけでよいため、システムは 1 行のみがスキャンされたとみなします。

Index2 が使用される場合(インデックスには文字列プレフィックス email(6) が含まれます)、実行シーケンスは次のようになります。

  1. Index2 インデックス ツリーから 'zhangs' のインデックス値を満たすレコードを検索します。最初に見つかったレコードは ID1 です。
  2. テーブルに戻り、主キーの主キー値が ID1 である行を見つけ、電子メールの値が「 [email protected] 」ではないと判断し、この行のレコードを破棄します。
  3. インデックス 2 で見つかった位置にある次のレコードを取得し、それがまだ「zhangs」であることを確認し、ID2 を取り出して、テーブルに戻ってID インデックスの行全体をフェッチし、値が正しいと判断します。今回は、この行を結果セットに追加します。
  4. Index2 で取得した値が 'zhangs' でなくなるまで、前の手順を繰り返します。ループは終了します。

つまり、プレフィックス インデックスを使用して長さを定義すると、余分なクエリ コストを追加することなくスペースを節約できます。識別度については前述しましたが、識別度が高ければ高いほど良いです識別の度合いが高いほど、重複キーの値が少なくなるからです。

9.2 プレフィックスインデックスはカバリングインデックスを使用できません

非クラスター化インデックス ツリーで見つかったデータはプレフィックスと ID であるため、プレフィックスは完全なデータではなく、クラスター化インデックス ツリーに返す必要があります。

したがって、プレフィックス インデックスを使用しても、カバーするインデックスのクエリ パフォーマンスを最適化する必要はありません。これは、プレフィックス インデックスを使用するかどうかを選択するときに考慮する必要がある要素でもあります。

10. インデックスのプッシュダウン

10.1 はじめに

インデックス条件プッシュダウン(ICP、インデックス条件プッシュダウン) は MySQL 5.6 の新機能で、インデックスを使用してストレージ エンジン層でデータをフィルタリングするための最適化された方法です。

  • ICPがない場合:結合インデックスのフィールドがあいまいクエリ(非左あいまい)の場合、フィールド判定後、以下のフィールドは直接条件判定に使用できず、必ず戻ってから判定する必要があります。テーブル。
  • ICP が有効になった後: 結合インデックス内のフィールドがファジー クエリ (左ファジーではない) の場合、フィールドが判定された後、次のいくつかのフィールドを直接判定できます。判定がフィルタリングされた後、テーブルに戻ってクエリを確認します。フィールドの条件は共同インデックスジャッジに含まれていません。主な最適化ポイントは、テーブルに戻る前にフィルタリングして、テーブルに戻る回数を減らすことです。主な用途:ファジィクエリ(非左ファジィ)では、インデックス内のフィールドの後ろのフィールドの順序が狂うため、テーブルに戻って判定する必要がありますが、インデックスプッシュダウンを使用する場合は、リターンする必要はありません。テーブルに出力され、判断はジョイント インデックス ツリーで直接行われます。

ICP がない場合、ストレージ エンジンはインデックスを走査してベース テーブル内の行を見つけ、それらを MySQL サーバーに返します。MySQL サーバーは WHERE の背後にある条件が予約されているかどうかを評価します。
ICP が有効になった後、インデックス内の列のみを使用して WHERE 条件の一部をフィルタリングできる場合、MySQL サーバーは WHERE 条件のこの部分をストレージ エンジン フィルタに入れます。次に、ストレージ エンジンはインデックス エントリを使用してデータをフィルタリングし、この条件が満たされる場合にのみテーブルから行を読み取ります。

利点: ICP により、ストレージ エンジンがベース テーブルにアクセスする必要がある回数と、MySQL サーバーがストレージ エンジンにアクセスする必要がある回数が削減されます。ただし、ICP の高速化効果は、ストレージ エンジン内で ICP によってフィルタリングされたデータの割合に依存します。 

例:

インデックス プッシュダウンをサポートしないジョイント インデックス:たとえば、インデックス (名前、年齢)、「z%」などのクエリ名、年齢=? 、ファジークエリにより年齢の順序が狂います。結合インデックスツリーをクエリする場合、名前のみが検索され、その後の年齢は条件によって直接判断できず、テーブルに戻ってから年齢を判断する必要があります。

インデックス プッシュダウンをサポートするジョイント インデックス:たとえば、インデックス (名前、年齢)、「z%」などのクエリ名、年齢とアドレス。ジョイント インデックス ツリーをクエリするときに名前をチェックするだけでなく、その後の年齢も判断します。フィルターしてテーブル判定アドレスを返します。

CREATE INDEX idx_name_age ON student(name,age);
#索引失败;非覆盖索引时,左模糊导致索引失效
EXPLAIN SELECT * FROM student WHERE name like '%bc%' AND age=30;
#索引成功;MySQL5.6引入索引下推,where后面的name和age都在联合索引里,可以又过滤又索引,不用回表,索引生效
EXPLAIN SELECT * FROM student WHERE `name` like 'bc%' AND age=30;
#索引成功;name走索引,age用到索引下推过滤,classid不在联合索引里,需要回表。
EXPLAIN SELECT * FROM student WHERE `name` like 'bc%' AND age=30 AND classid=2;

利点: 一部のシナリオでは、ICP はテーブルの戻り数を大幅に削減し、パフォーマンスを向上させることができます。ICP を使用すると、ストレージ エンジンがベース テーブルにアクセスする必要がある回数と、MySQL サーバーがストレージ エンジンにアクセスする必要がある回数を減らすことができます。ただし、ICP の高速化効果は、ストレージ エンジン内で ICP によってフィルタリングされたデータの割合に依存します。

10.2 ICPの使用条件

  • テーブルのアクセス タイプは、range、ref、eq_ref、または ref_or_null です。
  • ストレージ エンジン: ICP は InnDB および MyISAM ストレージ エンジンに使用できます。
  • セカンダリ インデックスが必要です: InnoDB テーブルの場合、ICP はセカンダリ インデックスにのみ使用されます。ICP の目標は、行全体の読み取りの数を減らし、それによって I/O 操作を減らすことです。
  • カバーインデックスであってはなりません: SQL がカバーインデックスを使用する場合、ICP 最適化メソッドはサポートされません。この場合、ICP を使用しても I/O は削減されないためです。
  • 相関サブクエリの条件では ICP を使用できません
  • バージョン 5.6 以降である必要があります。MySQLバージョン 5.6 は導入され、デフォルトで有効になっています。以前のバージョンはインデックス プッシュダウンをサポートしていません。
  • where フィールドはインデックス列にある必要があります: ICP ですべての where 条件をフィルターできるわけではありません。where 条件のフィールドがインデックス列にない場合でも、テーブル全体のレコードをサーバーに読み取る必要があります。フィルタリングしているところ。

10.3 ICPのオン/オフ

  • インデックス条件のプッシュダウンはデフォルトで有効になっています。システム変数optimizer_switchを設定することで制御できます: index_condition_pushdown
# 打开索引下推
SET optimizer_switch = 'index_condition_pushdown=on';

# 关闭索引下推
SET optimizer_switch = 'index_condition_pushdown=off';
  • インデックス条件がプッシュダウンされると、EXPLAINステートメントの出力のExtra列の内容が[Using Indexcondition]として表示されます

10.4 ICP の使用例

  • 主キーインデックス(簡略図)

セカンダリ インデックス zip_last_first (簡略図、データ ページ、その他の情報はここでは省略されています)

10.5 ICP を有効にした場合と無効にした場合のパフォーマンスの比較

11. 通常のインデックスと一意のインデックス

パフォーマンスの観点から、一意のインデックスと通常のインデックスのどちらを選択しますか? 選択の根拠は何ですか?

主キーが ID であるテーブルがあるとします。テーブルにはフィールド k があり、フィールド k の値が繰り返されないと仮定して、k にインデックスがあります。

このテーブルのテーブル作成ステートメントは次のとおりです。

mysql> create table test(
id int primary key,
k int not null,
name varchar(16),
index (k)
)engine=InnoDB;

表中のR1~R5の(ID,k)値は、それぞれ(100,1)、(200,2)、(300,3)、(500,5)、(600,6)となります。

11.1 クエリのおおよそのパフォーマンス

クエリを実行するステートメントが select id from test where k=5 であるとします。

  • 通常のインデックスの場合、条件を満たす最初のレコード(5,500)を見つけた後、k=5の条件を満たさない最初のレコードが見つかるまで次のレコードを検索する必要があります。
  • 一意のインデックスの場合、インデックスが一意性を定義するため、条件を満たす最初のレコードが見つかった後、検索は停止します。

では、この違いによってどのようなパフォーマンスの差が生じるのでしょうか? 答えは、非常に少ないです

11.2 通常のインデックス更新パフォーマンスが向上、バッファ変更

書き込みキャッシュ (変更バッファ):

データ ページを更新する必要がある場合、データ ページがメモリ内にある場合は直接更新され、データ ページがメモリ内にない場合、 InooDB はデータの整合性に影響を与えることなく。このデータ ページをディスクから読み取る必要はありません。次のクエリでこのデータ ページにアクセスする必要がある場合、データ ページをメモリに読み取り、変更バッファ内のこのページに関連する操作を実行します。このようにして、データ ロジックの正確性を保証できます。

マージ:変更バッファ内の操作を元のデータ ページに適用して最新の結果を取得するプロセスは、マージと呼ばれます。このデータ ページにアクセスするとマージがトリガーされるだけでなく、システムには定期的にマージするバックグラウンド スレッドがあります。マージ操作は、通常のデータベースのシャットダウン中にも実行されます。

更新操作を最初に変更バッファーに記録してディスク読み取りを減らすことができれば、ステートメントの実行速度が大幅に向上します。さらに、データをメモリに読み込むにはバッファ プールが必要となるため、この方法ではメモリの占有を回避し、メモリ使用率を向上させることもできます。

一意のインデックスの更新には変更バッファを使用できません。実際には、通常のインデックスのみが使用できます。

区別してください:

  • バッファ プール バッファ プールを使用してデータを読み取ります
  • REDOログにはREDOログ・バッファがあり、バッファ・プール内の更新データをREDOログ・バッファに書き込みます。トランザクションがコミットされると、REDOログ・バッファはフラッシュに従ってREDOログ・ファイルまたはページ・キャッシュにフラッシュされます。戦略。

11.3 変更バッファの使用シナリオ

  • 通常のインデックスとユニークなインデックスを選択するにはどうすればよいですか? 実際、これら 2 種類のインデックスの間でクエリ機能に違いはありません。主に考慮すべき点は、更新パフォーマンスへの影響です。したがって、共通のインデックスを選択することをお勧めします

  • 実際に使用してみると、大量のデータを含むテーブルを更新および最適化するには、通常のインデックスと変更バッファを組み合わせて使用​​することが非常に明白であることがわかります。

  • 変更バッファの状況には適していません。すべての更新の直後にこのレコードへのクエリが続く場合は、変更バッファをオフにする必要があります。他の場合には、変更バッファにより更新パフォーマンスが向上する場合があります。

  • トランザクションがコミットされると、変更バッファ操作もREDO ログに記録されるため、クラッシュが回復したときに変更バッファも取得できます。

  • 一意のインデックスは変更バッファ最適化メカニズムを使用しないため、ビジネスが許容できる場合は、パフォーマンスの観点から非一意のインデックスを優先することをお勧めします。しかし、「ビジネスが保証されない可能性がある」場合、どう対処すればよいでしょうか。

    • まず、ビジネスの正確さが優先されます。パフォーマンスの問題を議論するためには、「ビジネスコードが重複データを書き込まないことが保証されている」という前提があります。ビジネスが保証できない場合、またはデータベースが制約となるビジネスが必要な場合は、一意のインデックスを作成するしかありません。この場合、このセクションの重要性は、大量のデータがゆっくりと挿入され、メモリ ヒット率が低い場合の追加のトラブルシューティングのアイデアを提供することです。
    • その後、一部の「アーカイブ ライブラリ」シナリオでは、一意のインデックスの使用を検討できます。たとえば、オンライン データは半年だけ保存する必要があり、その後は履歴データがアーカイブ ライブラリに保存されます。この時点で、データのアーカイブにより、一意のキーの競合がないことがすでに保証されています。アーカイブ効率を向上させるために、テーブル内の固有のインデックスを共通のインデックスに変更することを検討できます。

12. SQLの最適化

12.1 EXISTS と IN の違い

質問:

どの状況でEXISTSを使用し、どの状況でINを使用する必要があるのか​​がよくわかりません。選択基準は、テーブルのインデックスが使用できるかどうかです。

答え:

12.2 COUNT(*) または COUNT(1) を推奨する

行数をカウントするには、可能な限り COUNT(1)、COUNT(*) を使用します。COUNT(1)、COUNT(*) の場合、クエリ オプティマイザーはインデックスを持つセカンダリ インデックス ツリーを優先して選択し、最も小さいインデックスを占有します。統計用のスペース: クラスター化インデックス ツリー統計は、非クラスター化インデックス ツリーにアクセスするときに使用されますが、これには多くのスペースが必要です。もちろん、COUNT (最小スペース副インデックスフィールド) を使用することもできますが、オプティマイザによる自動選択ほど手間はかかりません。

SELECT COUNT(*) FROM student;
SELECT COUNT(1) FROM student;

 質問: MySQL でデータ テーブルの行数をカウントするには、SELECT COUNT(*)、SELECT COUNT(1)、および SELECT COUNT (特定のフィールド) の 3 つの方法がありますが、これら 3 つの方法間のクエリ効率はどのくらいですか?

回答: 特定のフィールド内の null 以外のデータ行の数を数えたい場合は、別の問題になりますが、結局のところ、実行効率を比較する前提は、結果が同じであるということです。

COUNT(*)と COUNT(1): COUNT(*)と COUNT(1) はどちらもすべての結果に対してCOUNT(*)を実行します。COUNT (*) と COUNT(1)のには基本的に違いはありません(わずかな違いはありますが、それでも 2 つの実行効率は同等であると考えてください)。WHERE句がある場合はフィルタリング条件を満たすすべてのデータ行をカウントし、WHERE句がない場合はデータテーブル内のデータ行数をカウントします。

MylSAM 統計には O(1) のみが必要です: MylSAM ストレージ エンジンの場合、各 MyISAM データ テーブルにはrow_count 値を格納するメタ情報があるため、統計データ テーブルの行数には O(1) の複雑さだけが必要です。 , 一貫性はテーブルレベルのロックによって保証されます。InnoDB ストレージ エンジンの場合、innoDB はトランザクションをサポートし、行レベルのロックと MVCC メカニズムを使用するため、MyISAM のような row_count 変数を維持できないため、テーブル全体 (O(n) の複雑さ) をスキャンする必要があり、ループ+カウントはカウントによって行われます。

選択の提案: ImnoDB で、COUNT (特定のフィールド)を使用してデータ行数をカウントする場合は、セカンダリ インデックスを使用するようにしてください。主キーはクラスター化インデックスであり、クラスター化インデックスのリーフ ノードにはレコード全体が含まれるため、統計中にメモリにロードされるデータの量が多くなり、パフォーマンスが低下します。COUNT(*) および COUNT(1) の場合、特定の行を検索する必要はなく、行数をカウントするだけでよく、システムは統計用に占有するスペースが少ないセカンダリ インデックスを自動的に使用します複数のセカンダリ インデックスがある場合は、key_len が小さいセカンダリ インデックスがスキャンに使用されます。セカンダリ インデックスがない場合は、主キー インデックスが統計に使用されます。

12.3 SELECT(*) の代わりに SELECT(field) を提案する

テーブル クエリでは、フィールドを指定することをお勧めします。クエリのフィールド リストとして * を使用せず、SELECT <フィールド リスト> クエリを使用することをお勧めします。理由:

① 解析プロセス中に、MySQL はデータ ディクショナリにクエリを実行して、「*」をすべての列名に順番に変換します。これには多くのリソースと時間が消費されます。

②カバリングインデックスは使用できません

12.4 最適化に対する LIMIT 1 の影響

これは、テーブル全体をスキャンする SQL ステートメントを対象としています。結果セットが 1 つだけであることが確実な、LIMIT 1 を追加すると、結果が 1 つ見つかってもスキャンは続行されなくなり、クエリが高速化されます。

データ テーブルでフィールドに対して一意のインデックスが確立されている場合は、そのインデックスを介してクエリを実行できます。テーブル全体をスキャンしない場合は、LIMIT 1 を追加する必要はありません。

12.5 COMMIT をより頻繁に使用する

可能な限りプログラム内で COMMIT を使用してください。これにより、プログラムのパフォーマンスが向上し、COMMIT によって解放されるリソースによって需要が軽減されます。

COMMIT によってリリースされたリソース:

  • ロールバックセグメント上のデータを復元するために使用される情報
  • プログラムステートメントによって取得されたロック
  • REDO/UNDOログバッファ内のスペース
  • 上記 3 つのリソースで内部支出を管理します

13. 主な設計アイデア

実用的な質問について話しましょう。淘宝網のデータベースの主キーはどのように設計されていますか?

特定の間違った法外な回答が今でも毎年インターネット上で広まっており、いわゆる MySQL 軍事規制になっている場合もあります。その中でも、最も明らかな間違いの 1 つは、MySQL の主キーの設計に関するものです。

ほとんどの人の答えは自信に満ちています。主キーとして INT の代わりに 8 バイトの BIGINT を使用します。違います

このような答えはデータベース レベルでのみ得られ、ビジネスの観点から主キーについて考える必要はありません。主キーは自動インクリメントIDですか? 現時点では、自動インクリメントを主キーとして使用しても、アーキテクチャ設計に合格しない可能性があります

13.1 自動インクリメント主キーの欠点

自動インクリメントIDを主キーとして使用するため分かりやすく、ほとんどのデータベースが自動インクリメント型をサポートしていますが、実装が異なります。自己インクリメント ID はシンプルであるだけでなく、一般的に次のような問題点もあります。

  • 信頼性が低い

    自動インクリメント ID バックトラッキングには問題がありますが、この問題は MySQL 8.0 の最新バージョンまで修正されていませんでした。

    バックトラッキングの問題:たとえば、新しいテーブルに主キーが 1、2、3 である 3 つのデータ行を挿入します。このとき、コマンドを使用してテーブルの値がSHOW CREATE TABLE4AUTO_INCREMENTであることを確認しますが、問題ありません。

    次に、ID=3 のデータ行を削除しても、AUTO_INCREMENT再度クエリされた値は 4 のままであり、問​​題ありません。

    ただし、 MySQLを再起動すると、この値は 4 ではなく 3 に戻り、バックトラッキングが発生します。

  • セキュリティが低い

    公開されたインターフェイスでは、対応する情報を非常に簡単に推測できます。たとえば、/User/1/ のようなインターフェイスは、ユーザー ID の値とユーザーの総数を簡単に推測でき、インターフェイスを通じてデータを簡単にクロールすることもできます。

  • 業績不振

    自動インクリメント ID はパフォーマンスが低いため、データベース サーバー側で生成する必要があります。

  • 自己インクリメント値を知るには追加の実行関数が必要であり、パフォーマンスに影響します。

    ビジネスでは、挿入されたばかりの自己インクリメント値を知るために、last_insert_id() に似た関数を実行する必要もあります。これには、もう 1 回ネットワーク インタラクションが必要です。大規模同時実行システムでは、SQL ステートメントが 1 つ増えると、パフォーマンスのオーバーヘッドが1 つ増えることになります。

  • グローバルは一意ではなく、自己増加するロック競合が高同時実行時のパフォーマンスに影響を与える

    最も重要な点は、自動インクリメント ID はローカルに一意であり、現在のデータベース インスタンス内でのみ一意であり、グローバルに一意ではなく、どのサーバー間でも一意であるということです。現在の分散システムにとって、これはまさに悪夢です。

  • サブデータベースとテーブルが移行されると、自動インクリメントは適用されなくなります。

13.2 ビジネスフィールドを主キーとして使用しないようにする

会員情報を一意に識別するためには、会員情報テーブルに主キーを設定する必要がある。では、理想的な目標を達成するには、このテーブルの主キーをどのように設定すればよいでしょうか? ここではビジネス分野を主キーとして考えます。

テーブルデータは次のとおりです。

この表では、どのフィールドがより適切ですか?

  • カード番号(cardno)を選択してください

会員カード番号 (cardno) の方が適切であると思われます。会員カード番号は空にすることはできず、一意であり、会員記録を識別するために使用できるためです。

mysql> CREATE TABLE demo.membermaster
-> (
-> cardno CHAR(8) PRIMARY KEY, -- 会员卡号为主键
-> membername TEXT,
-> memberphone TEXT,
-> memberpid TEXT,
-> memberaddress TEXT,
-> sex TEXT,
-> birthday DATETIME
-> );
Query OK, 0 rows affected (0.06 sec)

異なる会員カード番号は異なる会員に対応し、フィールド「cardno」は特定の会員を一意に識別します。この場合、会員カード番号は会員と一対一に対応し、システムは正常に動作することができる。

しかし、実際には会員カード番号は使い回しされる可能性があります。例えば、張三さんは転職により元の住所を離れ、商店に消費に行かなくなった(会員カードは返却された)ため、商店の会員ではなくなった。しかし、商人は会員カードが空になることを望まなかったので、カード番号「10000001」の会員カードを王武に送りました。

システム設計上、今回の変更は会員情報テーブルのカード番号「10000001」の会員情報を変更するだけであり、データの整合性には影響しません。つまり、会員証番号が「10000001」の会員情報を変更すると、システムの各モジュールが変更後の会員情報を取得することになり、「一部のモジュールは変更前の会員情報を取得し、一部のモジュールは変更前の会員情報を取得する」ということはありません。変更された後のメンバー情報を取得すると、システム内でデータの不整合が発生します。」したがって、情報システムレベルでは問題ありません。
しかし、このシステムをビジネスレベルで利用するという観点から見ると、加盟店に影響を与える大きな問題があります。

たとえば、すべての販売フローの詳細を記録する販売フロー テーブル (trans) があります。2020年12月1日、張三さんは店で本を購入し、89元を支払いました。次に、以下に示すように、Zhang San が書籍を購入した記録がシステム内に残ります。

次に、2020年12月1日の会員販売実績を確認してみましょう。

mysql> SELECT b.membername,c.goodsname,a.quantity,a.salesvalue,a.transdate
-> FROM demo.trans AS a
-> JOIN demo.membermaster AS b
-> JOIN demo.goodsmaster AS c
-> ON (a.cardno = b.cardno AND a.itemnumber=c.itemnumber);
+------------+-----------+----------+------------+---------------------+
| membername | goodsname | quantity | salesvalue | transdate |
+------------+-----------+----------+------------+---------------------+
|     张三   | 书         | 1.000    | 89.00      | 2020-12-01 00:00:00 |
+------------+-----------+----------+------------+---------------------+
1 row in set (0.00 sec)

王呉に会員証「10000001」を再発行した場合、会員情報フォームを変更させていただきます。クエリが生成される場合:

mysql> SELECT b.membername,c.goodsname,a.quantity,a.salesvalue,a.transdate
-> FROM demo.trans AS a
-> JOIN demo.membermaster AS b
-> JOIN demo.goodsmaster AS c
-> ON (a.cardno = b.cardno AND a.itemnumber=c.itemnumber);
+------------+-----------+----------+------------+---------------------+
| membername | goodsname | quantity | salesvalue | transdate |
+------------+-----------+----------+------------+---------------------+
| 王五        | 书        | 1.000    | 89.00      | 2020-12-01 00:00:00 |
+------------+-----------+----------+------------+---------------------+
1 row in set (0.01 sec)

今回得られた結果は次のとおりです。Wang Wu は 2020 年 12 月 1 日に本を購入し、89 元を費やしました。明らかに間違っています!結論: 会員カード番号を主キーとして使用しないでください。

  • 会員電話番号またはID番号を選択してください

会員の電話番号を主キーとして使用できますか? とんでもない。実際の運用では、携帯電話番号も事業者によって回収され、他人に再発行されます。

ID番号はどうなるのでしょうか?それは可能のようです。ID カードは重複することがないため、ID 番号と個人は 1 対 1 に対応します。しかし、問題は、ID 番号は個人のプライバシーに属しており、顧客がそれを教えたがらない可能性があることです。会員にID番号の登録を義務化すれば、多くの顧客が離れてしまうだろう。実際、顧客の電話にもこの問題があるため、会員情報フォームを設計する際に ID 番号と電話番号を空白にすることを許可しています。

したがって、ビジネス関連のフィールドを主キーとして使用しないことをお勧めします結局のところ、プロジェクト設計技術者として、プロジェクトのライフサイクル全体を通じて、プロジェクトのビジネス要件によりどのビジネス フィールドが繰り返されたり再利用されたりするのかを予測できる人は誰もいません。

経験: MySQL を初めて使い始めるとき、多くの人はビジネス フィールドを主キーとして使用するという間違いを犯しがちです。彼らはビジネス ニーズを理解していることを当然だと思っていますが、実際の状況は多くの場合予期せぬものであり、変更のコストがかかります。主キーの設定が非常に高いです

13.3 淘宝網注文番号の主キーの設計

タオバオの電子商取引事業では、注文サービスが中核事業となっている。すみません、淘宝網の主キーは注文テーブルでどのように設計されていますか? 自動インクリメントIDですか?

タオバオを開いて注文情報を確認します。

上の図からわかるように、注文番号は自動インクリメント ID ではありません上記の 4 つの注文番号を詳しく見てみましょう。

1550672064762308113
1481195847180308113
1431156171142308113
1431146631521308113

注文番号は 19 桁で、最後の 5 桁はすべて同じ 08113 です。また、注文番号の最初の 14 桁は単調増加です。

大胆に推測すると、タオバオの注文 ID のデザインは次のようになります。

订单ID = 时间 + 去重字段 + 用户ID后6位尾号

このような設計は世界的にユニークであり、分散システムのクエリに非常に適しています。

13.4 推奨される主キーの設計

13.4.1 中核事業と非中核事業の主キー戦略の選択

非コア業務: アラーム、ログ、監視、その他の情報など、対応するテーブルの主キーの自動インクリメント ID。

コア ビジネス : 主キーの設計は、少なくとも世界的にユニークであり、単調増加である必要があります。グローバルな一意性は各システム間で一意であることが保証されており、挿入がデータベースのパフォーマンスに影響を与えないことを期待して単調増加しています。順序付けされた UUIDに変換するには、MySQL8.0 を使用することをお勧めします。具体的には、関数 uuid_to_bin(@uuid,true) を使用して、UUID を順序付けされた UUID に変換します。

13.4.2  UUIDの特徴

ここでは、最も単純な主キー設計である UUID をお勧めします。

グローバルに一意で、36 バイトを占有しており、データの順序が乱れており、挿入パフォーマンスが低下しています。

UUID を認識します。

  • なぜ UUID は世界的に一意なのでしょうか?
  • UUID が 36 バイト必要なのはなぜですか?
  • UUID に順序がないのはなぜですか?

MySQL データベースの UUID 構成は次のとおりです。

UUID = 时间+UUID版本(16字节)- 时钟序列(4字节) - MAC地址(12字节)

例として UUID 値 e0ea12d4-6473-11eb-943c-00155dbaa39d を見てみましょう。

なぜ UUID は世界的に一意なのでしょうか? 

UUID の時刻部分は60 ビットを占め、保存されるタイムスタンプは TIMESTAMP に似ていますが、1582-10-15 00:00:00.00 から現在までの 100ns のカウントを表します。UUID保存の時間精度はTIMESTAMPEよりも高く、時間次元での重複確率が1/100nsに低減されていることがわかります。

クロック シーケンスは、クロックがダイヤルバックされて時刻が重複する可能性を回避するためのものですMAC アドレスはグローバルな一意性のために使用されます

UUID が 36 バイト必要なのはなぜですか?

UUID は文字列として保存され、無駄な「-」文字列を使用して設計されているため、合計 36 バイトが必要です。

UUID がランダムで順序付けされていないのはなぜですか?

UUID の設計では、時間の下位ビットが先頭に配置され、この部分のデータは常に変化し、順序が狂うためです。

13.4.3 MySQL 8.0 の主キー スキーム: 順序付けされた UUID

順序への変換:時間の上位ビットと下位ビットを入れ替えると、時間は単調増加し、単調増加になります。MySQL 8.0 では、ロータイムとハイタイムのストレージ方式を置き換えることができるため、UUID は順序付けされた UUID になります。

スペース占有の最適化: MySQL 8.0 は、UUID のスペース占有問題も解決し、UUID 文字列内の無意味な「-」文字列を削除し、文字列をバイナリ タイプで保存することで、ストレージ スペースを 16 バイトに削減します。

上記の関数は、 MySQL8.0 が提供するuuid_to_bin 関数を通じて実現できます。同様に、MySQL も変換用の bin_to_uuid 関数を提供します。

SET @uuid = UUID();
SELECT @uuid,uuid_to_bin(@uuid),uuid_to_bin(@uuid,TRUE);

UUID は、関数 uuid_to_bin(@uuid,true) によって順序付けされた UUID に変換されますグローバルに一意 + 単調増加、これが私たちが望む主キーではないでしょうか。

注文された UUID パフォーマンス テスト:

16 バイトの順序付き UUID は、パフォーマンスとストレージ容量の点で、以前の 8 バイトの自己増分 ID と比較してどうですか?

テストを行ってみましょう。各データは 500 バイトを占め、3 つのセカンダリ インデックスが含まれる 1 億個のデータを挿入します。最終結果は次のようになります。

上図から、1億個のデータの順序付けされたUUIDを挿入するのが最も速いことがわかり、実際のビジネス利用では順序付けされたUUIDをビジネス側で生成することができますSQL 対話の数をさらに減らすこともできます。

さらに、順序付けされた UUID は自己インクリメント ID より 8 バイト多くなりますが、3G のストレージ容量が増加するだけであり、これは許容範囲です。

現在のインターネット環境では、自己増加する ID を主キーとするデータベース設計は推奨されません。順序付けされた UUID のようなグローバルに一意な実装の方が推奨されます。

さらに、実際のビジネス システムでは、ユーザーの末尾番号やコンピュータ ルームの情報などのビジネス属性やシステム属性に主キーを追加することもできます。このような主キーの設計では、アーキテクトのレベルがさらに試されます。

13.4.4 MySQL8.0 より前の主キー スキーム: 手動割り当て

フィールドを主キーとして手動で割り当てます。

たとえば、各マシンで生成されたデータをマージする必要がある場合、主キーの重複の問題が発生する可能性があるため、各ブランチのメンバーシップ テーブルの主キーを設計します。

本社の MySQL データベースに管理情報テーブルを作成し、このテーブルに現在の会員番号の最大値を記録するフィールドを追加できます。

メンバーを追加する場合、店舗はまず本部の MySQL データベースから最大値を取得し、これに 1 を加えて、この値を新しいメンバーの "id" として使用し、同時に現在の ID を更新します。本社のMySQLデータベースの管理情報テーブルのメンバー数の最大値。

このようにして、各ストアがメンバーを追加するとき、同じ本社 MySQL データベース内のデータ テーブル フィールドを操作するため、各ストアがメンバーを追加するときにメンバー番号が競合する問題が解決されます。

13.3.5 スノーフレークアルゴリズム

注文されたID。

Long データ型の 64 ビット整数: 1 ビットの符号ビット、41 ビットのタイムスタンプ、10 ビットの作業マシン ID、および12ビットのシリアル番号で構成されます。

アドバンテージ:

  • 順序付け:生成されたすべての ID は時間の傾向に従って増加します
  • 分散型かつ非反復型:分散システム全体で重複する ID は生成されません。

欠点:

  • マシン クロックに依存する:マシン クロックに依存すると、マシン クロックがダイヤルバックされると、重複した ID が生成されます。
  • 同期されていない分散クロックはインクリメントの失敗につながります。インクリメントは単一マシン上で行われますが、分散環境では各マシンのクロックが同期されず、グローバルなインクリメントにならない可能性があります。
  • 精度の損失: 64 ビットの 2 進数は 10 進数の 19 桁に変換されますが、フロントエンド js が保証できるのは最初の 16 桁の精度のみです。フロントエンドがこのデータを取得すると、最後の 3 桁が丸められます。 . 精度が失われます。

おすすめ

転載: blog.csdn.net/qq_40991313/article/details/130804019