39

次のようなテーブルがあります。

CREATE TABLE `products` (
  `id` int(11) unsigned NOT NULL AUTO_INCREMENT,
  `name` varchar(512) NOT NULL,
  `description` text,
  PRIMARY KEY (`id`),
) ENGINE=InnoDB AUTO_INCREMENT=38 DEFAULT CHARSET=utf8;

そしてこのようなもの:

CREATE TABLE `product_variants` (
  `id` int(11) unsigned NOT NULL AUTO_INCREMENT,
  `product_id` int(11) unsigned NOT NULL,
  `product_code` varchar(255) NOT NULL,
  PRIMARY KEY (`id`),
  UNIQUE KEY `product_code` (`product_code`),
  KEY `product_variant_product_fk` (`product_id`),
  CONSTRAINT `product_variant_product_fk` FOREIGN KEY (`product_id`) REFERENCES `products` (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=1037 DEFAULT CHARSET=utf8;

そして、このようなSQL文

SELECT p.id AS id, p.name AS name, p.description AS description, pv.id AS product_variant_id, pv.product_code AS product_code
FROM products p
INNER JOIN product_variants pv ON pv.product_id = p.id
ORDER BY p.name ASC
LIMIT 300 OFFSET 0;

説明すると、次のようになります。

+----+-------------+-------+------+----------------------------+----------------------------+---------+---------+--------+----------------+
| id | select_type | table | type | possible_keys              | key                        | key_len | ref     | rows   | Extra          |
+----+-------------+-------+------+----------------------------+----------------------------+---------+---------+--------+----------------+
|  1 | SIMPLE      | p     | ALL  | PRIMARY                    | NULL                       | NULL    | NULL    | 993658 | Using filesort |
|  1 | SIMPLE      | pv    | ref  | product_variant_product_fk | product_variant_product_fk | 4       | db.p.id |      1 |                |
+----+-------------+-------+------+----------------------------+----------------------------+---------+---------+--------+----------------+
2 rows in set (0.00 sec)

百万行の場合、これはかなり遅いです。私はproducts.nameにインデックスを追加しようとしました:

ALTER TABLE products ADD INDEX `product_name_idx` (name(512));

これにより、次のようになります。

mysql> show indexes from products;
+----------+------------+------------------+--------------+-----------------+-----------+-------------+----------+--------+------+------------+---------+---------------+
| Table    | Non_unique | Key_name         | Seq_in_index | Column_name     | Collation | Cardinality | Sub_part | Packed | Null | Index_type | Comment | Index_comment |
+----------+------------+------------------+--------------+-----------------+-----------+-------------+----------+--------+------+------------+---------+---------------+
| products |          0 | PRIMARY          |            1 | id              | A         |      993658 |     NULL | NULL   |      | BTREE      |         |               |
| products |          1 | product_manf_fk  |            1 | manufacturer_id | A         |          18 |     NULL | NULL   | YES  | BTREE      |         |               |
| products |          1 | product_name_idx |            1 | name            | A         |         201 |      255 | NULL   |      | BTREE      |         |               |
+----------+------------+------------------+--------------+-----------------+-----------+-------------+----------+--------+------+------------+---------+---------------+
3 rows in set (0.00 sec)

Sub_part 列は、このページで説明されているように、インデックス化されたプレフィックス (バイト単位) を示していると思います。

クエリを再説明すると、次のようになります。

+----+-------------+-------+------+----------------------------+----------------------------+---------+---------+--------+----------------+
| id | select_type | table | type | possible_keys              | key                        | key_len | ref     | rows   | Extra          |
+----+-------------+-------+------+----------------------------+----------------------------+---------+---------+--------+----------------+
|  1 | SIMPLE      | p     | ALL  | PRIMARY                    | NULL                       | NULL    | NULL    | 993658 | Using filesort |
|  1 | SIMPLE      | pv    | ref  | product_variant_product_fk | product_variant_product_fk | 4       | db.p.id |      1 |                |
+----+-------------+-------+------+----------------------------+----------------------------+---------+---------+--------+----------------+
2 rows in set (0.00 sec)

新しいインデックスが使用されていないようです。このページで説明されて いるように、プレフィックス インデックスの場合、インデックスは並べ替えに使用されません。実際、次のようにデータを切り捨てると:

alter table products modify `name`  varchar(255) not null;

説明は次のとおりです。

+----+-------------+-------+-------+----------------------------+----------------------------+---------+----------------------------------------------+------+-------+
| id | select_type | table | type  | possible_keys              | key                        | key_len | ref                                          | rows | Extra |
+----+-------------+-------+-------+----------------------------+----------------------------+---------+----------------------------------------------+------+-------+
|  1 | SIMPLE      | p     | index | PRIMARY                    | product_name_idx           | 767     | NULL                                         |  300 |       |
|  1 | SIMPLE      | pv    | ref   | product_variant_product_fk | product_variant_product_fk | 4       | oh_2c98c233_69fe_4f06_ad0d_fe6f85a5beac.p.id |    1 |       |
+----+-------------+-------+-------+----------------------------+----------------------------+---------+----------------------------------------------+------+-------+

それを裏付けるものだと思います。ただし、このページでは、 InnoDB テーブルには最大 767 バイトのインデックスを含めることができると記載されています。長さがバイト単位の場合、255 を超える長さを拒否するのはなぜですか? 文字単位の場合、各 UTF-8 文字の長さはどのように決定されますか? 3を仮定しているだけですか?

また、このバージョンの MySQL を使用しています。

mysql> select version();
+------------+
| version()  |
+------------+
| 5.5.27-log |
+------------+
1 row in set (0.00 sec)
4

2 に答える 2

75

2021 年 9 月の編集: 私は数年前から MySQL 8.0 を使用しているため、更新された情報をいくつか紹介します。

MySQL のマニュアルには、 (現在は とも呼ばれる) との間の変換に関する非常に有益なページがあります。廃止され、最終的に削除されます。削除すると、現在のエイリアスが代わりに参照されます。utf8mb3utf8utf8mb4utf8mb3 utf8utf8mb4

deprecatedでは、インデックスに最大utf8mb3255 文字を格納できます。utf8mb4COMPACTREDUNDANT

COMPRESSEDまたは行形式のDYNAMIC場合、インデックス キーのプレフィックスは最大 3072 バイトです。これらを使用すると、 の場合は最大 1024 文字、 の場合は最大utf8mb3768 文字のインデックスを作成できますutf8mb4

以下は私の以前の回答です。これは、インデックスを作成できる文字数とバイトの背後にあるロジックの一部を説明しています。


調査のため、回答を修正する必要があります。私はもともとこれを投稿しました(自分自身を引用):

答えは、文字が何バイトになるかわからないため、インデックスに含まれる文字数がわからないということだと思います(マルチバイト文字を除外するために何かをしない限り)。

確かではありませんが、それでも正しいかもしれませんが、私が考えていた方法とはまったく異なります.

正解は次のとおりです。

MySQL は、utf8 文字ごとに 3 バイトを想定しています。列ごとに指定できる最大インデックス サイズは 255 文字です。これは、256x3=768 であり、767 バイトの制限を超えているためです。

インデックス サイズを指定しない場合、MySQL は最大サイズ (つまり、列ごとに 255) を選択します。一意のインデックスにはセル値全体が含まれている必要があるため、長さが 255 を超える utf8 列に UNIQUE 制約を設定することはできません。ただし、通常のインデックスを使用することもできます。最初の 255 文字 (または最初の 767 バイト?) にインデックスを付けるだけです。そして、それは私にとってまだいくつかの謎があるところです。

The MySTERY: 安全のために、MySQL が 1 文字あたり 3 バイトを想定している理由がわかりました。しかし、ドキュメントは、インデックスが実際には文字ではなくバイト単位でサイズ設定されていることを示唆しているようです。したがって、25 5文字 (765 バイト) のインデックスを varchar(25 6 ) 列に配置するとします。格納する文字がすべて ASCII の 1 バイト文字 (AZ、az、0 ~ 9 など) の場合、列全体を 767 バイトのインデックスに収めることができます。そして、それが実際に起こることのようです。

以下は、文字、バイトなどに関する私の元の回答からの詳細です。


wikipediaによると、UTF-8 文字の長さは 1、2、3、または 4 バイトです。ただし、この mysql ドキュメントによると、最大文字サイズは 3 バイトであるため、255 文字を超える列インデックス インデックスはそのバイト制限に達する可能性があります。しかし、私が理解しているように、そうではないかもしれません。ほとんどの文字が ASCII 範囲内にある場合、平均文字サイズは 1 バイトに近くなります。たとえば、平均文字サイズが 1.3 バイト (ほとんどが 1 バイトですが、2 ~ 3 バイト文字がかなりの数) の場合、767/1.3 のインデックスを指定できます。

したがって、ほとんどが 1 バイト文字を格納している場合、実際の文字制限は 767 / 1.3 = 590 のようになります。しかし、それはうまくいかないことがわかりました。255文字が限界です。

この MySQL ドキュメントに記載されているように、

プレフィックスの制限はバイト単位で測定されますが、CREATE INDEX ステートメントのプレフィックスの長さは、非バイナリ データ型 (CHAR、VARCHAR、TEXT) の文字数として解釈されます。マルチバイト文字セットを使用する列のプレフィックス長を指定する場合は、この点を考慮してください。

MySQL は、varchar カラムのキー サイズを決定するために、私が行ったように計算/推測を行うよう人々にアドバイスしているようです。ただし、実際には、utf8 列に 255 を超えるインデックスを指定することはできません。

最後に、私の 2 番目のリンクをもう一度参照すると、これもあります。

innodb_large_prefix 構成オプションが有効になっている場合、DYNAMIC および COMPRESSED 行フォーマットを使用する InnoDB テーブルの場合、この長さ制限は 3072 バイトに引き上げられます。

そのため、必要に応じて、少し調整するだけで、はるかに大きなインデックスを取得できるようです。行フォーマットが DYNAMIC または COMPRESSED であることを確認してください。その場合、おそらく 1023 文字または 1024 文字のインデックスを指定できます。


ちなみに、[utf8mb4文字セット][4]を使えば4バイト文字を格納できることがわかりました。utf8 文字セットには ["plane 0" 文字][5] のみが格納されているようです。

編集:

tinyint(1) 列を持つ varchar(511) 列に複合インデックスを作成しようとしたところ、最大インデックス サイズが 767 バイトであるというエラー メッセージが表示されました。これにより、MySQL は utf8 文字セットの列に 1 文字あたり 3 バイト (最大) が含まれ、最大 255 文字を使用できると想定していると思われます。しかし、おそらくそれは複合インデックスのみです。詳細がわかり次第、回答を更新します。しかし、今のところ、これは編集として残しています。

于 2013-05-10T02:02:41.330 に答える
3

InnoDB テーブルの制限

警告

mysql データベースの MySQL システム テーブルを MyISAM から InnoDB テーブルに変換しないでください。これはサポートされていない操作です。これを行うと、古いシステム テーブルをバックアップから復元するか、mysql_install_db プログラムでそれらを再生成するまで、MySQL は再起動しません。

警告

NFS ボリューム上のデータ ファイルまたはログ ファイルを使用するように InnoDB を構成することはお勧めできません。そうしないと、ファイルが他のプロセスによってロックされ、MySQL で使用できなくなる可能性があります。

最大値と最小値

  1. テーブルには、最大 1000 列を含めることができます。
  2. テーブルには、最大 64 個のセカンダリ インデックスを含めることができます。
  3. デフォルトでは、単一列インデックスのインデックス キーは最大 767 バイトです。同じ長さの制限が、すべてのインデックス キー プレフィックスに適用されます。たとえば、UTF-8 文字セットと各文字の最大 3 バイトを想定すると、TEXT または VARCHAR 列で 255 文字を超える列プレフィックス インデックスでこの制限に達する可能性があります。innodb_large_prefix 構成オプションが有効になっている場合、DYNAMIC および COMPRESSED 行フォーマットを使用する InnoDB テーブルの場合、この長さ制限は 3072 バイトに引き上げられます。
  4. 許可されている最大値よりも長いインデックス プレフィックス長を指定すると、長さは自動的に最大長に短縮されます。MySQL 5.6 以降では、最大長を超えるインデックス プレフィックス長を指定すると、エラーが発生します。

innodb_large_prefix が有効になっている場合、REDUNDANT または COMPACT テーブルのキー長が 3072 を超えるインデックス プレフィックスを作成しようとすると、ER_INDEX_COLUMN_TOO_LONG エラーが発生します。

InnoDB 内部の最大キー長は 3500 バイトですが、MySQL 自体はこれを 3072 バイトに制限しています。この制限は、複数列のインデックスの結合されたインデックス キーの長さに適用されます。

可変長列 (VARBINARY、VARCHAR、BLOB、および TEXT) を除いて、行の最大長は、データベース ページの半分よりわずかに小さくなります。つまり、行の最大長は約 8000 バイトです。LONGBLOB および LONGTEXT 列は 4GB 未満である必要があり、BLOB および TEXT 列を含む行の合計の長さは 4GB 未満である必要があります。

参考: InnoDBの制限事項

于 2015-06-15T06:38:19.700 に答える