VARCHAR の長さは適切ですか?
著者: Aikesheng DBA チームのメンバーである Guan Yongqiang は、MySQL の運用とメンテナンスのスキルに優れています。新しい知識を学ぶのが大好きで、ゲームも大好きなオタクです。
著者: Li Fuqiang 氏、Aikesheng DBA チームのメンバーで、MySQL、TiDB、OceanBase およびその他のデータベースに精通しています。正しいことをしっかりやり続ければ、また違ったご褒美が得られると信じています。
Aikeson オープンソース コミュニティによって作成されています。オリジナルのコンテンツを許可なく使用することはできません。転載する場合は編集者に連絡し、出典を明記してください。
この記事は約 2,200 ワードで、読むのに 8 分かかると予想されます。
背景の説明
一部の顧客は、 VARCHAR型フィールドの長さを拡張したと報告しました。初回はすぐに変更できますが、2 回目は実行に時間がかかります。テーブル内のデータ量はほぼ同じなので、から に調整するのは速いのに、から に調整するのに時間がかかるのはなぜですか?VARCHAR(20)
VARCHAR(50)
VARCHAR(50)
VARCHAR(100)
そこで状況を再現し、問題分析を実施しました。
環境情報
今回の検証に対象となる製品とバージョン情報は以下のとおりです。
製品 | バージョン |
---|---|
MySQL | 5.7.25-ログ MySQL コミュニティ サーバー (GPL) |
システムベンチ | システムベンチ 1.0.17 |
シーンの繰り返し
3.1 データの準備
mysql> show create table test.sbtest1;
+---------+----------------------------------------+
| Table | Create Table |
+---------+----------------------------------------+
| sbtest1 | CREATE TABLE `sbtest1` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`k` int(11) NOT NULL DEFAULT '0',
`c` varchar(20) COLLATE utf8mb4_bin NOT NULL DEFAULT '',
`pad` varchar(20) COLLATE utf8mb4_bin NOT NULL DEFAULT '',
PRIMARY KEY (`id`),
KEY `k_1` (`k`)
) ENGINE=InnoDB AUTO_INCREMENT=1000001 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_bin |
+---------+----------------------------------------+
1 row in set (0.00 sec)
mysql> select count(*) from test.sbtest1;
+----------+
| count(*) |
+----------+
| 1000000 |
+----------+
1 row in set (0.10 sec)
3.2 問題点の検証
顧客の説明をシミュレートするために、c
フィールドを変更し、VARCHAR(20)
に変更しVARCHAR(50)
、次に に変更しVARCHAR(100)
、その実行に必要な時間を観察します。関連する操作コマンドと実行結果は次のとおりです。
mysql> ALTER TABLE test.sbtest1 MODIFY c VARCHAR(50);
Query OK, 0 rows affected (0.01 sec)
Records: 0 Duplicates: 0 Warnings: 0
mysql> show create table test.sbtest1;
+---------+-------------------------------+
| Table | Create Table |
+---------+-------------------------------+
| sbtest1 | CREATE TABLE `sbtest1` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`k` int(11) NOT NULL DEFAULT '0',
`c` varchar(50) COLLATE utf8mb4_bin DEFAULT NULL,
`pad` varchar(20) COLLATE utf8mb4_bin NOT NULL DEFAULT '',
PRIMARY KEY (`id`),
KEY `k_1` (`k`)
) ENGINE=InnoDB AUTO_INCREMENT=1000001 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_bin |
+---------+--------------------------------------------------------+
1 row in set (0.00 sec)
mysql> ALTER TABLE test.sbtest1 MODIFY c VARCHAR(100);
Query OK, 1000000 rows affected (4.80 sec)
Records: 1000000 Duplicates: 0 Warnings: 0
mysql> show create table test.sbtest1;
+---------+---------------------------+
| Table | Create Table |
+---------+---------------------------+
| sbtest1 | CREATE TABLE `sbtest1` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`k` int(11) NOT NULL DEFAULT '0',
`c` varchar(100) COLLATE utf8mb4_bin DEFAULT NULL,
`pad` varchar(20) COLLATE utf8mb4_bin NOT NULL DEFAULT '',
PRIMARY KEY (`id`),
KEY `k_1` (`k`)
) ENGINE=InnoDB AUTO_INCREMENT=1000001 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_bin |
+---------+------------------------------------------------------------------------+
1 row in set (0.00 sec)
VARCHAR(63)
検証の結果、安定して再発することがわかったので、引き続き修正を試みましたが、最終的にに修正するのに時間がかかることが判明しましたVARCHAR(64)
が、64以降も長さを拡張し続けたところ、 になることがわかりました。すぐに完了することができました。
mysql> ALTER TABLE test.sbtest1 MODIFY c VARCHAR(63);
Query OK, 0 rows affected (0.01 sec)
Records: 0 Duplicates: 0 Warnings: 0
mysql> ALTER TABLE test.sbtest1 MODIFY c VARCHAR(64);
Query OK, 1000000 rows affected (4.87 sec)
Records: 1000000 Duplicates: 0 Warnings: 0
mysql> show create table test.sbtest1;
+---------+---------------+
| Table | Create Table |
+---------+---------------+
| sbtest1 | CREATE TABLE `sbtest1` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`k` int(11) NOT NULL DEFAULT '0',
`c` varchar(64) COLLATE utf8mb4_bin DEFAULT NULL,
`pad` varchar(20) COLLATE utf8mb4_bin NOT NULL DEFAULT '',
PRIMARY KEY (`id`),
KEY `k_1` (`k`)
) ENGINE=InnoDB AUTO_INCREMENT=1000001 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_bin |
+---------+------------------------------------------------------------------------+
1 row in set (0.00 sec)
mysql> ALTER TABLE test.sbtest1 MODIFY c VARCHAR(65);
Query OK, 0 rows affected (0.01 sec)
Records: 0 Duplicates: 0 Warnings: 0
mysql> ALTER TABLE test.sbtest1 MODIFY c VARCHAR(66);
Query OK, 0 rows affected (0.01 sec)
Records: 0 Duplicates: 0 Warnings: 0
3.3 問題分析
VARCHAR(63)
修正にVARCHAR(64)
時間がかかる状況を分析します。公式ドキュメントを参照すると、VARCHAR
バイト長が1の場合に格納できる文字の種類は0~255であることがわかりました。現在の文字セット タイプは UTF8MB4 です。UTF8MB4 は 4 バイトのエンコード文字セットであるため、1 バイト長で 63.75 (255/4) 文字を格納できるため、これを にVARCHAR(63)
変更する場合は、データ用のバイトを追加する必要があります。VARCHAR(64)
ストレージの場合は、一時テーブルを作成してこの長さの拡張を完了する必要があるため、時間がかかります。
拡張検証
4.1 データの準備
mysql> show create table test_utf8.sbtest1;
+---------+----------------------------------------+
| Table | Create Table |
+---------+----------------------------------------+
| sbtest1 | CREATE TABLE `sbtest1` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`k` int(11) NOT NULL DEFAULT '0',
`c` varchar(20) NOT NULL DEFAULT '',
`pad` varchar(20) NOT NULL DEFAULT '',
PRIMARY KEY (`id`),
KEY `k_1` (`k`)
) ENGINE=InnoDB AUTO_INCREMENT=1000001 DEFAULT CHARSET=utf8 |
+---------+------------------+
1 row in set (0.00 sec)
mysql> select count(*) from test_utf8.sbtest1;
+----------+
| count(*) |
+----------+
| 1000000 |
+----------+
1 row in set (0.10 sec)
4.2 UTF8シーン検証
UTF8 は 3 バイトのエンコーディング文字セットであるため、1 バイトに 85 (255/3=85) 文字を格納できます。
この変更の順序は、VARCHAR(20)→VARCHAR(50)→VARCHAR(85)となり、その実行にかかる時間を観察します。関連する操作コマンドと実行結果は次のとおりです。
mysql> ALTER TABLE test_utf8.sbtest1 MODIFY c VARCHAR(50) ,algorithm=inplace,lock=none;
Query OK, 0 rows affected (0.01 sec)
Records: 0 Duplicates: 0 Warnings: 0
mysql> ALTER TABLE test_utf8.sbtest1 MODIFY c VARCHAR(85) ,algorithm=inplace,lock=none;
Query OK, 0 rows affected (0.00 sec)
Records: 0 Duplicates: 0 Warnings: 0
mysql> show create table test_utf8.sbtest1;
+---------+-------------------------------+
| Table | Create Table |
+---------+-------------------------------+
| sbtest1 | CREATE TABLE `sbtest1` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`k` int(11) NOT NULL DEFAULT '0',
`c` varchar(85) DEFAULT NULL,
`pad` varchar(20) NOT NULL DEFAULT '',
PRIMARY KEY (`id`),
KEY `k_1` (`k`)
) ENGINE=InnoDB AUTO_INCREMENT=1000001 DEFAULT CHARSET=utf8 |
+---------+--------------------------------------------------+
1 row in set (0.00 sec)
変更順序: VARCHAR(85)→VARCHAR(86)→VARCHAR(100) このとき、実行された SQL ステートメントが直接エラーを返すことがわかります。したがって、これら 2 つのパラメータを削除するとalgorithm=inplace ,lock=none
、この SQL で一時テーブルを作成し、ターゲット テーブルをロックしてから、SQL を再実行できるようになります。関連する操作コマンドと実行結果は次のとおりです。
mysql> ALTER TABLE test_utf8.sbtest1 MODIFY c VARCHAR(86) ,algorithm=inplace,lock=none;
ERROR 1846 (0A000): ALGORITHM=INPLACE is not supported. Reason: Cannot change column type INPLACE. Try ALGORITHM=COPY.
mysql> ALTER TABLE test_utf8.sbtest1 MODIFY c VARCHAR(86);
Query OK, 1000000 rows affected (4.94 sec)
Records: 1000000 Duplicates: 0 Warnings: 0
mysql> show create table test_utf8.sbtest1;
+---------+-------------------------------+
| Table | Create Table |
+---------+-------------------------------+
| sbtest1 | CREATE TABLE `sbtest1` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`k` int(11) NOT NULL DEFAULT '0',
`c` varchar(86) DEFAULT NULL,
`pad` varchar(20) NOT NULL DEFAULT '',
PRIMARY KEY (`id`),
KEY `k_1` (`k`)
) ENGINE=InnoDB AUTO_INCREMENT=1000001 DEFAULT CHARSET=utf8 |
+---------+--------------------------------------------------+
1 row in set (0.00 sec)
mysql> ALTER TABLE test_utf8.sbtest1 MODIFY c VARCHAR(100) ,algorithm=inplace,lock=none;
Query OK, 0 rows affected (0.00 sec)
Records: 0 Duplicates: 0 Warnings: 0
4.3 UTF8MB4シナリオの検証
UTF8MB4 は 4 バイトのエンコーディング文字セットであるため、1 バイトに 63 (255/4=63.75) 文字を格納できます。
この変更の順序は、VARCHAR(20)→VARCHAR(50)→VARCHAR(63)となり、その実行にかかる時間を観察します。関連する操作コマンドと実行結果は次のとおりです。
mysql> ALTER TABLE test.sbtest1 MODIFY c VARCHAR(50) ,algorithm=inplace,lock=none;
Query OK, 0 rows affected (0.00 sec)
Records: 0 Duplicates: 0 Warnings: 0
mysql> ALTER TABLE test.sbtest1 MODIFY c VARCHAR(63) ,algorithm=inplace,lock=none;
Query OK, 0 rows affected (0.00 sec)
Records: 0 Duplicates: 0 Warnings: 0
mysql> show create table test.sbtest1;
+---------+-------------------------+
| Table | Create Table |
+---------+-------------------------+
| sbtest1 | CREATE TABLE `sbtest1` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`k` int(11) NOT NULL DEFAULT '0',
`c` varchar(63) COLLATE utf8mb4_bin DEFAULT NULL,
`pad` varchar(20) COLLATE utf8mb4_bin NOT NULL DEFAULT '',
PRIMARY KEY (`id`),
KEY `k_1` (`k`)
) ENGINE=InnoDB AUTO_INCREMENT=1000001 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_bin |
+---------+-------------------------------------------------------------------------+
1 row in set (0.00 sec)
この変更の順序は、VARCHAR(63)→VARCHAR(64)→VARCHAR(100) になります。このとき、実行された SQL ステートメントが直接エラーを返すことがわかります。したがって、これら 2 つのパラメータを削除するとalgorithm=inplace, lock=none
、この SQL で一時テーブルを作成し、ターゲット テーブルをロックしてから、SQL を再実行できるようになります。関連する操作コマンドと実行結果は次のとおりです。
mysql> ALTER TABLE test.sbtest1 MODIFY c VARCHAR(64) ,algorithm=inplace,lock=none;
ERROR 1846 (0A000): ALGORITHM=INPLACE is not supported. Reason: Cannot change column type INPLACE. Try ALGORITHM=COPY.
mysql> ALTER TABLE test.sbtest1 MODIFY c VARCHAR(64) ;
Query OK, 1000000 rows affected (4.93 sec)
Records: 1000000 Duplicates: 0 Warnings: 0
mysql> show create table test.sbtest1;
+---------+--------------------------+
| Table | Create Table |
+---------+--------------------------+
| sbtest1 | CREATE TABLE `sbtest1` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`k` int(11) NOT NULL DEFAULT '0',
`c` varchar(64) COLLATE utf8mb4_bin DEFAULT NULL,
`pad` varchar(20) COLLATE utf8mb4_bin NOT NULL DEFAULT '',
PRIMARY KEY (`id`),
KEY `k_1` (`k`)
) ENGINE=InnoDB AUTO_INCREMENT=1000001 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_bin |
+---------+-------------------------------------------------------------------------+
1 row in set (0.00 sec)
mysql> ALTER TABLE test.sbtest1 MODIFY c VARCHAR(100) ,algorithm=inplace,lock=none;
Query OK, 0 rows affected (0.00 sec)
Records: 0 Duplicates: 0 Warnings: 0
4.4 比較分析
文字長変更 | UTF8(MB3) | UTF8MB4 |
---|---|---|
20->50 | オンライン DDL (インプレース) | オンライン DDL (インプレース) |
50->100 | オンライン DDL (コピー) | オンライン DDL (コピー) |
X->Y | Y*3<256の場合、インプレース <br> X*3>=256の場合、インプレース | Y*4<256の場合、インプレース <br> X*4>=256の場合、インプレース |
述べる | 1文字は最大3バイトを占有します | 1文字は最大4バイトを占有します |
結論は
フィールドの最大バイト長が 256 文字以上の場合、フィールド長を表すために 2 バイトが必要です。
UTF8MB4を使用した例:
- フィールドの最大バイト長が 256 文字以内に変化する場合 (つまり、x*4<256 および Y*4<256)、オンライン ddl は非常に効率的なインプレース モードを使用します。
- 最大バイト長が 256 文字を超えるフィールド (つまり、x*4>=256 および Y*4>=256) の場合、オンライン DDL は非常に効率的なインプレース モードを使用します。
- それ以外の場合、オンライン DDL はコピー モードを使用するため、非効率的になります。
- UTF8(MB3)でも同様です。
提案
後のフィールド長の拡張を避けるために、オンライン DDL は非効率的なコピー モードを採用することをお勧めします。
- UTF8(MB3)文字タイプの場合:
- 文字数が50未満です。文字長はVARCHAR(50)以下に設定することを推奨します。
- 文字数は 84 (256/3=83.33) に近い値です。varchar(84) またはそれ以上の文字数に設定することをお勧めします。
- UTF8MB4 文字タイプの場合:
- 文字数が 50 未満の場合は、VARCHAR(50) またはそれより短い文字長に設定することをお勧めします。
- 文字数は 64 に近い (256/4=64)、VARCHAR(64) 以上の文字数に設定することをお勧めします。
この検証結果は参考用です。本番環境で運用する必要がある場合は、経済的損失を避けるために、実際の状況に基づいて VARCHAR の長さを合理的に定義してください。
さらに技術的な記事については、https: //opensource.actionsky.com/をご覧ください。
SQLEについて
SQLE は、開発環境から運用環境までの SQL 監査と管理をカバーする包括的な SQL 品質管理プラットフォームです。主流のオープンソース、商用および国内データベースをサポートし、開発、運用および保守のためのプロセス自動化機能を提供し、オンライン効率を向上させ、データ品質を向上させます。
SQL取得
タイプ | 住所 |
---|---|
リポジトリ | https://github.com/actiontech/sqle |
書類 | https://actiontech.github.io/sqle-docs/ |
リリースニュース | https://github.com/actiontech/sqle/releases |
データ監査プラグイン開発ドキュメント | https://actiontech.github.io/sqle-docs/docs/dev-manual/plugins/howtouse |