web-dev-qa-db-ja.com

MySQLにUUID v4を保存する

見つかった関数ごとに、PHPを使用してUUIDを生成しています here

次に、それをMySQLデータベースに保存します。 UUID v4を保存するための最良/最も効率的なMySQLフィールド形式は何ですか?

現在varchar(256)を持っていますが、必要以上に大きいと確信しています。ほとんどの答えをたくさん見つけましたが、それらが参照しているUUIDの形式については一般に曖昧なので、特定の形式を求めています。

17
Stephen R

正確にフィットさせたい場合はVARCHAR(36)として保存します。とにかく同じストレージコストで機能するVARCHAR(255)として保存します。ここでバイトをめちゃくちゃにする理由はありません。

VARCHARフィールドは可変長であるため、ストレージコストは実際にそこにあるデータの量ではなく、実際にそこにあるデータの量に比例します。

BINARYとして保存するのは非常に面倒で、値は印刷できず、クエリの実行時にゴミとして表示される可能性があります。リテラルバイナリ表現を使用する理由はめったにありません。人間が読み取れる値はコピーして貼り付けることができ、簡単に操作できます。

Postgresのような他のプラットフォームには、よりコンパクトな形式で内部的に格納する適切なUUID列がありますが、人間が読める形式で表示するので、両方のアプローチを最大限に活用できます。

35
tadman

各行に常にUUIDがある場合は、CHAR(36)として保存し、VARCHAR(36)を介して行ごとに1バイトを保存できます。

uuid CHAR(36) CHARACTER SET ascii

CHARとは対照的に、VARCHAR値は1バイトまたは2バイトの長さのプレフィックスとデータとして保存されます。長さのプレフィックスは、値のバイト数を示します。値が255バイトを超える必要がない場合、列は1バイトを使用し、値が255バイトを超える場合は2バイトを使用します。 https://dev.mysql.com/doc/refman/5.7/en/char.html

CHARには注意が必要ですが、フィールドが空のままであっても、定義された完全な長さを常に消費します。また、文字セットにはASCIIを使用してください。そうしないと、CHARは最悪のシナリオを計画します(つまり、utf8、4のutf8mb4

[...] MySQLは、CHAR CHARACTER SET utf8mb4カラムの各文字に対して4バイトを予約する必要があります。これは、可能な最大の長さだからです。たとえば、MySQLはCHAR(10)CHARACTER SET utf8mb4カラム用に40バイトを予約する必要があります。 https://dev.mysql.com/doc/refman/5.5/en/charset-unicode-utf8mb4.html

11
Mathieu Rey

質問は、UUIDをMySQLに保存することです。

MySQLバージョン8.0以降では、binary(16)を_UUID_TO_BIN/BIN_TO_UUID_関数を介した自動変換で使用できます。 https://mysqlserverteam.com/mysql-8-0-uuid-support/ =

MySQLには、UUIDを主キーとして生成する高速な方法もあることに注意してください。

VALUES(UUID_TO_BIN(UUID()、true))へのINT INT

10
Karsten R.

最も効率的なのは間違いなくBINARY(16)であり、人間が読める文字を保存する場合は2倍以上の記憶領域を使用し、インデックスが大きくなりルックアップが遅くなります。データが十分に小さく、テキストとして保存してもパフォーマンスが低下しない場合は、退屈な整数キーに対してUUIDは必要ないでしょう。まともなdb管理ツールは、「テキスト」のリテラルバイトではなく、16進数としてオクテットを表示/ダンプするため、rawの保存は他の人が示唆するほど苦痛ではありません。データベースでUUIDを手動で検索する必要はありません。必要な場合は、HEX()およびx'deadbeef01'リテラルはあなたの友達です。これに対処するために、あなたが参照したようなアプリで関数を書くのは簡単です。おそらく、データベースで仮想列およびストアドプロシージャとして実行することもできます。これにより、アプリが生データに煩わされることがなくなります。

UUID生成ロジックを表示ロジックから分離して、既存のデータが変更されず、エラーが検出されるようにします。

function guidv4($prettify = false)
{
    static $native = function_exists('random_bytes');
    $data = $native ? random_bytes(16) : openssl_random_pseudo_bytes(16);

    $data[6] = chr(ord($data[6]) & 0x0f | 0x40); // set version to 0100
    $data[8] = chr(ord($data[8]) & 0x3f | 0x80); // set bits 6-7 to 10

    if ($prettify) {
        return guidv4_pretty($data);
    }
    return $data;
}

function guidv4_pretty($data)
{
    return strlen($data) == 16 ?
        vsprintf('%s%s-%s-%s-%s-%s%s%s', str_split(bin2hex($data), 4)) :
        false;
}

function guidv4_ugly($data)
{
    $data = preg_replace('/[^\\dA-F]+/i', '', $data);
    return strlen($data) == 32 ? hex2bin($data) : false;
}

編集:データベースの読み取り時に列のみが必要な場合は、次のようなステートメントで十分です。

ALTER TABLE test ADD uuid_pretty CHAR(36) GENERATED ALWAYS AS (CONCAT_WS('-', LEFT(HEX(uuid_ugly), 8), SUBSTR(HEX(uuid_ugly), 9, 4), SUBSTR(HEX(uuid_ugly), 13, 4), SUBSTR(HEX(uuid_ugly), 17, 4), RIGHT(HEX(uuid_ugly), 12))) VIRTUAL;
1
Walf

最もスペース効率がよいのは、BINARY(16)または2つのBIGINT UNSIGNED

前者は、手動クエリでは(簡単な方法で)読み取り可能/コピー可能な値が得られないため、頭痛の種になります。後者は、1つの値と2つの列の間をマッピングする必要があるため、頭痛の種になるかもしれません。

これがプライマリキーである場合、すべてのセカンダリインデックスの一部にもなるため、スペースを無駄にすることはありません。つまり、これらのタイプのいずれかを選択します。

パフォーマンスのために、ランダムUUID(つまり、ランダム化されたUUID v4)のランダム性は、深刻な損害を与えます。これは、UUIDが主キーである場合、またはUUIDに対して多くの範囲クエリを実行する場合に適用されます。プライマリインデックスへの挿入は、最後(またはその近く)のすべてではなく、場所全体に行われます。データは一時的な局所性を失います。これはさまざまな場合に役立つプロパティでした。

私の主な改善点は、データの一部としてタイムスタンプを使用するUUID v1に似たものを使用し、タイムスタンプが最上位ビットになるようにすることです。たとえば、UUIDは次のように構成されます。

Timestamp | Machine Identifier | Counter

このようにして、自動インクリメント値に似た局所性を取得します。

1
Timo