MySQLはIDに適切なデータ型を選択します

MySQLはIDに適切なデータ型を選択します

id のデータ型を選択するときは、データ ストレージ タイプだけでなく、MySQL がこのタイプを計算して比較する方法も考慮する必要があります。たとえば、MySQL は ENUM 型と SET 型を内部的には整数として保存しますが、文字列のシナリオでは文字列として比較します。 ID のデータ型を選択したら、長さや符号の有無などのプロパティを含め、ID を参照する関連データ テーブルのデータ型が一貫しており、完全に一貫していることを確認する必要があります。異なるデータ型を混在させるとパフォーマンスの問題が発生する可能性があります。また、パフォーマンスの問題がない場合でも、比較中に暗黙的にデータ変換が行われると、微妙なエラーが発生する可能性があります。しかし、実際の開発プロセス中にさまざまなデータ型を忘れると、予期しない問題が突然発生する可能性があります。

長さを選択する際には、できるだけ短いフィールド長さを選択し、将来の成長に余裕を持たせることも必要です。たとえば、州を格納するために使用する場合、値は数十個しかありません。このとき、INT ではなく TINYINT を使用する方が適切です。関連テーブルにもこの ID が格納されている場合、効率の差は非常に大きくなります。

ID に適用される一般的なタイプを次に示します。

  • 整数: 整数演算と比較は高速であり、AUTO_INCREMENT 属性を設定して自動的に増分できるため、通常は整数が最適な選択です。
  • ENUM と SET: 列挙体とセットは通常 ID として選択されませんが、「タイプ」、「ステータス」、「性別」を含む列には非常に適しています。たとえば、ドロップダウン メニューを格納するテーブルが必要な場合、通常は値と名前があります。この場合、列挙を主キーとして使用することもできます。
  • 文字列: 文字列を ID として使用することはできるだけ避けてください。まず、文字列は多くのスペースを占め、次に、通常は整数よりも遅くなります。 ID として文字列を選択する場合は、MD5、SHA1、UUID などの関数にも特別な注意を払う必要があります。各値は、順序のない広い範囲のランダムな値であるため、挿入とクエリが遅くなります。
    • 挿入中、インデックスはランダムな場所に作成されるため (ページング、ランダム ディスク アクセス、クラスター化インデックスの断片化が発生する)、挿入速度が低下します。
    • クエリを実行すると、隣接するデータ行がディスク上またはメモリ内で大きな距離にまたがる場合があり、これによってもパフォーマンスが低下する可能性があります。

UUID 値を使用する場合は、「-」文字を削除するか、UNHEX 関数を使用して 16 バイトの数値に変換し、BINARY(16) を使用して保存する必要があります。次に、HEX 関数を使用して 16 進形式で取得できます。 UUID を生成する方法は多数あり、ランダムに分散されるものもあれば、順序付けられているものもありますが、順序付けられたものでも整数ほどパフォーマンスは良くありません。

分散IDソリューションの概要

IDはデータの一意の識別子です。従来のアプローチは、UUIDとデータベースの自動増分IDを使用することです。今日、MySQLはますます広く使用され、トランザクションのサポートが必要なため、通常はInnodbストレージエンジンが使用されます。UUIDは長すぎて乱雑であるため、Innodbの主キーとしては適していません。自動増分IDの方が適していますが、ビジネス開発ではデータの量がますます大きくなり、データをテーブルに分割する必要があります。テーブルが分割された後、各テーブルのデータは独自のペースで自動増分され、IDの競合が発生する可能性が高くなります。このとき、一意の ID を生成するための別のメカニズムが必要になります。生成された ID は、分散 ID またはグローバル ID とも呼ばれます。分散 ID を生成するメカニズムを分析してみましょう。

データベース自動増分ID

この方法は、データベースの自動インクリメント ID に基づいています。別のデータベース インスタンスを使用し、このインスタンスに別のテーブルを作成する必要があります。

テーブル構造は次のとおりです。

データベース `SEQID` を作成します。

テーブルSEQID.SEQUENCE_IDを作成します(
	id bigint(20) unsigned NOT NULL auto_increment, 
	スタブ char(10) NOT NULL デフォルト ''
	主キー (id)、
	UNIQUE KEY スタブ (スタブ)
)ENGINE=MyISAM;

次のステートメントを使用して、自動増分IDを生成および取得できます。

始める;
SEQUENCE_ID(スタブ)VALUES('anyword')に置き換えます。
last_insert_id() を選択します。
専念;

スタブ フィールドには、ここでは特別な意味はありません。これは、データを挿入する際の利便性のためだけのものです。データを挿入できる場合にのみ、自動増分 ID を生成できます。挿入には replace を使用します。 replace は、まず指定されたスタブと同じ値のデータがあるかどうかを確認します。存在する場合は、まずそれを削除してから挿入します。存在しない場合は、直接挿入します。

分散 ID を生成するこのメカニズムには、別の MySQL インスタンスが必要です。これは実現可能ですが、パフォーマンスと信頼性の点で十分ではありません。業務システムで ID が必要になるたびに、データベースに ID の取得を要求する必要があり、パフォーマンスが低下します。このデータベース インスタンスがオフラインになると、すべての業務システムに影響が及びます。 ; したがって、この方法ではデータにはある程度の信頼性の低さがあります。

データベースマルチマスターモード

2 つのデータベースがマスター スレーブ クラスターを形成する場合、通常の状況ではデータベースの信頼性の問題を解決できます。ただし、マスター データベースに障害が発生し、データがスレーブ データベースに時間内に同期されない場合、ID の重複が発生します。このため、マルチマスターモード☞デュアルマスターモードのクラスターを使用できます。つまり、両方の MySQL インスタンスが独立して自動増分 ID を生成できるため、効率が向上します。ただし、他の変更が行われない場合、2 つの MySQL インスタンスは同じ ID を生成する可能性があります。 MySQL インスタンスごとに異なる開始値と自動増分ステップを構成する必要があります。

最初の MySQL インスタンス構成 (mysql_01):

set @@auto_increment_offset = 1; -- 開始値 set @@auto_increment_increment = 2; -- ステップ長

2 番目の MySQL インスタンス構成 (mysql_02):

set @@auto_increment_offset = 2; -- 開始値 set @@auto_increment_increment = 2; -- ステップ長

上記の設定後、2 つの MySQL インスタンスによって生成される ID シーケンスは次のようになります。
mysql_01: 開始値は 1、ステップ長は 2、ID 生成の順序は 1、3、5、7、9、... です。
mysql_02:、開始値は 2、ステップ長は 2、ID 生成の順序は 2、4、6、8、10、... です。

分散 ID を生成するこのソリューションでは、DistributIdService などの新しい分散 ID 生成アプリケーションを追加する必要があります。このアプリケーションは、ビジネス アプリケーションが ID を取得するためのインターフェイスを提供します。ビジネス アプリケーションで ID が必要な場合、RPC を介して DistributIdService を要求します。DistributIdService は、上記の 2 つの MySQL インスタンスから ID をランダムに取得します。

このソリューションを実装すると、MySQL インスタンスの 1 つがオフラインになっても DistributIdService には影響せず、DistributIdService は引き続き別の MySQL を使用して ID を生成できます。

ただし、このソリューションは拡張性に欠けます。2 つの MySQL インスタンスでは不十分で、パフォーマンスを向上させるために新しい MySQL インスタンスを追加する必要がある場合は、面倒になります。

さて、新しいインスタンス mysql_03 を追加したい場合はどうすればよいでしょうか?

  • まず、mysql_01 と mysql_02 のステップ サイズを 3 に変更する必要がありますが、これは手動でのみ変更できるため、時間がかかります。
  • 次に、mysql_01 と mysql_02 は常に増加しているため、mysql_01 と mysql_02 のステップ サイズを変更するのに十分な時間を確保するために、mysql_03 の開始値を大きく設定する必要がある場合があります。
  • 3 番目に、ステップ サイズを変更すると、重複した ID が発生する可能性があります。この問題を解決するには、マシンをシャットダウンする必要がある場合があります。

数値セグメントモード

このモードはバッチ取得として理解できます。たとえば、DistributIdService がデータベースから ID を取得するときに、複数の ID をバッチで取得してローカルにキャッシュできれば、業務アプリケーションの ID 取得効率が大幅に向上します。

たとえば、DistributIdService がデータベースから ID を取得するたびに、(1,1000] などの数値セグメントを取得します。この範囲は 1000 個の ID を表します。ビジネス アプリケーションが DistributIdService に ID の提供を要求すると、DistributIdService は毎回データベースを要求せずに、ローカルで数値を 1 から増分して返すだけで済みます。ローカルの数値が 1000 に増分されるまで、つまり現在の数値セグメントが使い果たされるまで、データベースにアクセスして次の数値セグメントを取得することはありません。

したがって、データベース テーブルを次のように変更する必要があります。

id_generatorテーブルを作成します(
  id int(10) NULLではない、
  current_max_id bigint(20) NOT NULL COMMENT '現在の最大ID',
  increment_step int(10) NOT NULL COMMENT '増分ステップ',
  主キー (`id`)
)ENGINE=InnoDB デフォルト文字セット=utf8;

このデータベース テーブルは、自動増分ステップと自動増分 ID の現在の最大値 (つまり、現在適用されている数値セグメントの最後の値) を記録するために使用されます。自動増分ロジックは DistributIdService に移動されたため、データベースではこのロジックは不要になりました。

このソリューションは、データベースに強く依存しなくなりました。データベースが利用できなくなった場合でも、DistributIdService は一定期間データベースのサポートを継続できます。ただし、DistributIdService が再起動されると、ID のセグメントが失われ、ID ホールが発生します。

DistributIdService の高可用性を向上させるには、クラスターが必要です。ビジネスが DistributIdService クラスターに ID の取得を要求すると、クラスターはランダムに DistributIdService ノードを選択して ID を取得します。各 DistributIdService ノードは、同じデータベースに接続されているため、複数の DistributIdService ノードが同時にデータベースに番号セグメントの取得を要求する場合があります。この場合、制御には楽観的ロックが必要です。たとえば、データベース テーブルにバージョン フィールドを追加します。番号セグメントを取得するときは、次の SQL を使用します。

id_generator を更新し、current_max_id=#{newMaxId}、version=version+1 を設定します。version は #{version} です。

DistributIdService では、oldMaxId + ステップ サイズに基づいて newMaxId が計算されるため、上記の更新が成功すれば、数値セグメントが正常に取得されたことになります。

データベース層の高可用性を実現するには、データベースをマルチマスター モードで展開する必要があります。各データベースでは、生成された番号セグメントが重複しないようにする必要があります。そのためには、元のアイデアを使用して、開始値とステップ サイズをデータベース テーブルに追加する必要があります。たとえば、MySQL サーバーが 2 つある場合は、次のようになります。
mysql_01 は数値セグメント (1,1001] を生成し、増分時のシーケンスは 1、3、4、5、7... になります。
mysql_02 は数値セグメント (2,1002] を生成し、増分時のシーケンスは 2、4、6、8、10... になります。

具体的な実装コードについては、tinyidを参照してください。

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

データベース自己増分 ID モード、データベース マルチマスター モード、および番号セグメント モードの 3 つのモードはすべて自己増分の概念に基づいています。スノーフレーク アルゴリズムの概念は、以下で簡単に理解できます。
Snowflake は Twitter のオープンソースの分散 ID 生成アルゴリズムです。アルゴリズムなので、上記の 3 つの分散 ID 生成メカニズムとは異なります。データベースに依存しません。

基本的な考え方は、分散 ID が固定の長い数値であるということです。長い数値は 8 バイト、つまり 64 ビットを占めます。元の Snowflake アルゴリズムでのビットの割り当ては次のとおりです。

  • 最初のビットは識別部分です。Javaではlongの最上位ビットが符号ビットなので、正の数は0、負の数は1となります。一般的に生成されるIDは正の数なので0に固定されます。
  • タイムスタンプ部分は41ビットを占め、これはミリ秒の時間です。通常、現在のタイムスタンプは保存されませんが、タイムスタンプの差(現在の時間 - 固定開始時間)が保存されるため、生成されたIDはより小さな値から開始できます。41ビットのタイムスタンプは69年間使用できます、(1L << 41)/(1000L * 60 * 60 * 24 * 365)= 69年
  • 作業マシン ID は 10 ビットを占め、比較的柔軟性があります。たとえば、最初の 5 ビットはデータ センターのコンピューター ルームの識別子として使用し、最後の 5 ビットは単一のコンピューター ルームのマシンの識別子として使用できます。1024 個のノードを展開できます。
  • シリアル番号部分は 12 ビットを占め、同じノードが同じミリ秒内に 4096 個の ID を生成することをサポートします。

このアルゴリズムのロジックによれば、このアルゴリズムを Java 言語で実装し、ツール メソッドにカプセル化するだけで済みます。その後、各ビジネス アプリケーションはこのツール メソッドを直接使用して分散 ID を取得できます。分散 ID を取得するために別のアプリケーションを構築することなく、各ビジネス アプリケーションが独自の作業マシン ID を持っていることを確認するだけで済みます。また、データベースにも依存しません。

具体的なコード実装

パッケージ com.yeming.tinyid.application;

静的 java.lang.System.* をインポートします。

/**
 * @著者 イェミン・ガオ
 * @Description: スノーフレークアルゴリズムの実装* <p>
 * SnowFlake アルゴリズムは 64 ビット ID を生成するために使用されます。64 ビット ID は長整数で保存でき、分散システムで一意の ID を生成するために使用できます。
 * 生成された ID には大まかな順序があります。 この実装では、生成された 64 ビット ID は 5 つの部分に分割できます。
 * 0 - 41 ビットのタイムスタンプ - 5 ビットのデータセンター ID - 5 ビットのマシン ID - 12 ビットのシリアル番号 * @date 2020/07/28 16:15
 */
パブリッククラスSnowFlake {
    /**
     * 開始タイムスタンプ */
    プライベート静的最終long START_STMP = 1480166465631L;

    /**
     * マシンIDが占める桁数 */
    プライベート静的最終long MACHINE_BIT = 5;
    /**
     * データセンターが占有するビット数 */
    プライベート静的最終長いDATACENTER_BIT = 5;
    /**
     * シリアル番号の桁数 */
    プライベート静的最終長いSEQUENCE_BIT = 12;

    /**
     * マシンIDの最大値*/
    プライベート静的最終long MAX_MACHINE_NUM = ~(-1L << MACHINE_BIT);
    /**
     * データセンターの最大値 */
    プライベート静的最終long MAX_DATACENTER_NUM = ~(-1L << DATACENTER_BIT);
    /**
     * 最大シリアル番号値*/
    プライベート静的最終long MAX_SEQUENCE = ~(-1L << SEQUENCE_BIT);
    /**
     *各パーツの左への変位*/
    プライベート静的最終ロング MACHINE_LEFT = SEQUENCE_BIT;
    プライベート静的最終長い DATACENTER_LEFT = SEQUENCE_BIT + MACHINE_BIT;
    プライベート静的最終long TIMESTMP_LEFT = DATACENTER_LEFT + DATACENTER_BIT;

    private long datacenterId; //データセンターprivate long machineId; //マシンIDprivate long sequence = 0L; //シリアル番号private long lastStmp = -1L; //最終タイムスタンプprivate SnowFlake(long datacenterId, long machineId) {
        データセンターID > MAX_DATACENTER_NUM || データセンターID < 0 の場合 {
            throw new IllegalArgumentException("datacenterId は MAX_DATACENTER_NUM より大きく、0 より小さくすることはできません");
        }
        マシンID > MAX_MACHINE_NUM || マシンID < 0 の場合 {
            throw new IllegalArgumentException("machineId は MAX_MACHINE_NUM より大きく、0 より小さくすることはできません");
        }
        this.datacenterId = データセンターId;
        this.machineId = マシンID;
    }

    /**
     * 次のIDを生成する
     *
     * @戻り値 long
     */
    プライベート同期された長いnextId(){
        長いcurrStmp = System.currentTimeMillis();
        (現在のスタンプ<最後のスタンプ)の場合{
            throw new RuntimeException("時計が逆方向に動きました。ID の生成を拒否します");
        }
        (現在のスタンプ == 最後のスタンプ)の場合{
            //同じミリ秒内に、シーケンス番号が自動的に増加します。sequence = (sequence + 1) & MAX_SEQUENCE;
            // 同じミリ秒内のシーケンスの数が最大値に達した if (sequence == 0L) {
                currStmp = getNextMill();
            }
        } それ以外 {
            //異なるミリ秒では、シーケンス番号は0に設定されます
            シーケンス = 0L;
        }
        最後のStmp = currStmp;
        return (currStmp - START_STMP) << TIMESTMP_LEFT //タイムスタンプ部分 | datacenterId << DATACENTER_LEFT //データセンター部分 | machineId << MACHINE_LEFT //マシンID部分 | sequence; //シリアル番号部分}

    プライベートlong getNextMill() {
        ロングミル = System.currentTimeMillis();
        while (ミル <= lastStmp) {
            ミル = System.currentTimeMillis();
        }
        リターンミル;
    }

    パブリック静的voidメイン(String[] args) {
        スノーフレーク snowFlake = new SnowFlake(2, 3);
        // データセンター識別子の最大値 long maxDatacenterNum = ~(-1L << DATACENTER_BIT);
        //マシン識別子の最大値 long maxMachineNum = ~(-1L << MACHINE_BIT);
        // シーケンス番号の最大値 long maxSequence = ~(-1L << SEQUENCE_BIT);
        out.println("データセンター ID の最大値: " + maxDatacenterNum + "; マシン ID の最大値: " + maxMachineNum + "; シーケンス番号の最大値: " + maxSequence);
        (int i = 0; i < (1 << 12); i++) の場合 {
            out.println(snowFlake.nextId());
        }
    }
}

スノーフレーク アルゴリズムとは、次のものを指します。

  • Baidu (uid ジェネレーター)
  • 美団(葉)

上記は、MySQL ID に適切なデータ型を選択する方法の詳細です。MySQL ID に適切なデータ型を選択する方法の詳細については、123WORDPRESS.COM の他の関連記事に注目してください。

以下もご興味があるかもしれません:
  • MySql データ型チュートリアル例の詳細な説明
  • MySQL 5.7 共通データ型
  • MySQL データ型の選択原則
  • mysql 10進データ型変換の実装
  • mysql データ型変換の実装
  • MySQL データ型 DECIMAL の使用方法の詳細な説明
  • MySQL の 10 進数データ型の小数点埋め込み問題の詳細な説明
  • MySQL データ型の詳細

<<:  選択タグ内のオプションをクリアする3つの方法

>>:  Springboot および Vue プロジェクトの Docker デプロイメントの実装手順

推薦する

MySQL における datetime と timestamp の違いと選択

目次1 違い1.1 スペース占有1.2 表現範囲1.3 タイムゾーン2 テスト3つの選択肢MySQL...

Vue の計算プロパティとプロパティリスニングについての簡単な説明

目次1. 計算プロパティ構文: 1. 省略形:文法: 2. 文章を完成させる: 2. モニタリング(...

Vue+Spring Bootで検証コード機能を実現

この記事では、検証コード機能を実装するためのvue+spring bootの具体的なコードを例として...

MySQL パーティション関数の詳細な説明と例の分析

まず、データベース パーティショニングとは何でしょうか?以前、MySQL のテーブル パーティショニ...

jsはシンプルなショッピングカートモジュールを実装します

この記事の例では、参考までに、シンプルなショッピングカートモジュールを実装するためのjsの具体的なコ...

MySQL 百万レベルのデータページングクエリ最適化ソリューション

データベースからクエリする必要があるテーブルに数万件のレコードがある場合、すべての結果を一度にクエリ...

Linux コマンドラインで電卓を使用する 5 つのコマンド

みなさんこんにちは。私は梁旭です。 Linux を使用するときに、計算を行う必要がある場合があり、そ...

mysql 5.7.19 最新バイナリインストール

まず、公式ウェブサイト http://dev.mysql.com/downloads/mysql/ ...

CentOS 8で自動更新を設定するための手順を完了する

データとコンピューターに対してできる最善のことは、それらを安全に保つことです。アップデートを有効にす...

MySQLデータファイルの保存場所を表示する方法

次のような疑問が湧くかもしれません。MySQLをローカル (自分のコンピュータ) にインストールした...

Vue のすべてのカプセル化方法の簡単な概要

目次1. カプセル化API 2. グローバルツールコンポーネントを登録する3. グローバル関数をカプ...

Vueは要素ツリーコントロールを通じてツリーテーブルを実装します

目次実装効果図依存関係をインストールするカスタムツリーコントロールその他の実装要約するVueでは、要...

MySQL sql_mode の使用に関する詳細な説明

目次序文sql_mode の説明最も重要なオプションすべてのオプション要約する序文前回の記事「MyS...

docker を使用して crownblog プロジェクトを Alibaba Cloud にデプロイする方法

フロントエンドプロジェクトのパッケージ化.env.productionを見つけて、自分のIPまたはド...

Vue バインディング オブジェクト、配列データを動的にレンダリングできないケースの詳細な説明

プロジェクトシナリオ: Dark Horse Vueプロジェクト管理の実践、製品分類の取得、拡張バー...