1. 程式人生 > >MySQL 問題分析:ERROR 1071 (42000) : Specified key was too long; max key length is 767 bytes

MySQL 問題分析:ERROR 1071 (42000) : Specified key was too long; max key length is 767 bytes

今天在MySQL 5.6版本的資料庫中修改InnoDB表字段長度時遇到了”ERROR 1071 (42000): Specified key was too long; max key length is 767 bytes”錯誤,第一次遇到這個錯誤,遂花了點學習、研究過、總結這個問題。

我們先來建立一個測試表,構造這樣的錯誤。

mysql> use MyDB;
Reading table information for completion of table and column names
You can turn off this feature to get a quicker startup with -A
 
Database changed
mysql> CREATE TABLE `TEST` (
    ->   `CODE_NAME` varchar(100) NOT NULL DEFAULT '',
    ->   `CODE_SEQ` smallint(6) NOT NULL DEFAULT '1',
    ->   `ACTIVE` char(1) DEFAULT 'Y',
    ->   `CODE_VALUE1` varchar(250) DEFAULT NULL,
    ->   PRIMARY KEY (`CODE_NAME`,`CODE_SEQ`),
    ->   KEY `IDX_GEN_CODE` (`CODE_NAME`,`CODE_VALUE1`)
    -> ) ENGINE=InnoDB DEFAULT CHARSET=utf8;
Query OK, 0 rows affected (0.02 sec)
 
 
mysql> ALTER TABLE TEST MODIFY CODE_VALUE1 VARCHAR(350);
ERROR 1071 (42000): Specified key was too long; max key length is 767 bytes
mysql>

其實這個“ERROR 1071 (42000): Specified key was too long; max key length is 767 bytes”錯誤是指超出索引位元組的限制,並不是指欄位長度限制。在官方文件“Limits on InnoDB Tables”有關於這方面的介紹、描述(詳情請見參考資料):

MySQL 5.6文件內容如下

By default, the index key prefix length limit is 767 bytes. See Section 13.1.13, “CREATE INDEX Syntax”. For example, you might hit this limit with a column prefix index of more than 255 characters on a TEXT or VARCHAR column, assuming a utf8mb3 character set and the maximum of 3 bytes for each character. When the innodb_large_prefix configuration option is enabled, the index key prefix length limit is raised to 3072 bytes for InnoDB tables that use DYNAMIC or COMPRESSED row format.

 

Attempting to use an index key prefix length that exceeds the limit returns an error. To avoid such errors in replication configurations, avoid enablinginnodb_large_prefix on the master if it cannot also be enabled on slaves.

The limits that apply to index key prefixes also apply to full-column index keys.

MySQL 5.7文件內容如下:

If innodb_large_prefix is enabled (the default), the index key prefix limit is 3072 bytes for InnoDB tables that use DYNAMIC or COMPRESSED row format. If innodb_large_prefix is disabled, the index key prefix limit is 767 bytes for tables of any row format.

innodb_large_prefix is deprecated and will be removed in a future release. innodb_large_prefix was introduced in MySQL 5.5 to disable large index key prefixes for compatibility with earlier versions of InnoDB that do not support large index key prefixes.

The index key prefix length limit is 767 bytes for InnoDB tables that use the REDUNDANT or COMPACT row format. For example, you might hit this limit with a column prefix index of more than 255 characters on a TEXT or VARCHAR column, assuming a utf8mb3 character set and the maximum of 3 bytes for each character.

Attempting to use an index key prefix length that exceeds the limit returns an error. To avoid such errors in replication configurations, avoid enablinginnodb_large_prefix on the master if it cannot also be enabled on slaves.

The limits that apply to index key prefixes also apply to full-column index keys.

如果啟用了系統變數innodb_large_prefix(預設啟用,注意實驗版本為MySQL 5.6.41,預設是關閉的,MySQL 5.7預設開啟),則對於使用DYNAMIC或COMPRESSED行格式的InnoDB表,索引鍵字首限制為3072位元組。如果禁用innodb_large_prefix,則對於任何行格式的表,索引鍵字首限制為767位元組。

innodb_large_prefix將在以後的版本中刪除、棄用。在MySQL 5.5中引入了innodb_large_prefix,用來禁用大型字首索引,以便與不支援大索引鍵字首的早期版本的InnoDB相容。

對於使用REDUNDANT或COMPACT行格式的InnoDB表,索引鍵字首長度限制為767位元組。例如,您可能會在TEXT或VARCHAR列上使用超過255個字元的列字首索引達到此限制,假設為utf8mb3字符集,並且每個字元最多包含3個位元組。

嘗試使用超出限制的索引鍵字首長度會返回錯誤。要避免複製配置中出現此類錯誤,請避免在主伺服器上啟用enableinnodb_large_prefix(如果無法在從伺服器上啟用)。

適用於索引鍵字首的限制也適用於全列索引鍵。

注意:上面是767個位元組,而不是字元,具體到字元數量,這就跟字符集有關。GBK是雙位元組的,UTF-8是三位元組的

解決方案:

1:啟用系統變數innodb_large_prefix

注意:光有這個系統變數開啟是不夠的。必須滿足下面幾個條件:

  • 系統變數innodb_large_prefix為ON
  • 系統變數innodb_file_format為Barracuda
  • ROW_FORMAT為DYNAMIC或COMPRESSED

如下測試所示:

mysql> show variables like '%innodb_large_prefix%';
+---------------------+-------+
| Variable_name       | Value |
+---------------------+-------+
| innodb_large_prefix | OFF   |
+---------------------+-------+
1 row in set (0.00 sec)
 
mysql> set global innodb_large_prefix=on;
Query OK, 0 rows affected (0.00 sec)
 
mysql> ALTER TABLE TEST MODIFY CODE_VALUE1 VARCHAR(350);
ERROR 1709 (HY000): Index column size too large. The maximum column size is 767 bytes.
mysql> 
mysql> show variables like '%innodb_file_format%';
+--------------------------+-----------+
| Variable_name            | Value     |
+--------------------------+-----------+
| innodb_file_format       | Antelope  |
| innodb_file_format_check | ON        |
| innodb_file_format_max   | Barracuda |
+--------------------------+-----------+
3 rows in set (0.01 sec)
 
mysql> set global innodb_file_format=Barracuda;
Query OK, 0 rows affected (0.00 sec)
 
mysql> ALTER TABLE TEST MODIFY CODE_VALUE1 VARCHAR(350);
ERROR 1709 (HY000): Index column size too large. The maximum column size is 767 bytes.
mysql> 
 
mysql> 
mysql> show table status from MyDB where name='TEST'\G;
*************************** 1. row ***************************
           Name: TEST
         Engine: InnoDB
        Version: 10
     Row_format: Compact
           Rows: 0
 Avg_row_length: 0
    Data_length: 16384
Max_data_length: 0
   Index_length: 16384
      Data_free: 0
 Auto_increment: NULL
    Create_time: 2018-09-20 13:53:49
    Update_time: NULL
     Check_time: NULL
      Collation: utf8_general_ci
       Checksum: NULL
 Create_options: 
        Comment: 
 
mysql>  ALTER TABLE TEST ROW_FORMAT=DYNAMIC;
Query OK, 0 rows affected (0.05 sec)
Records: 0  Duplicates: 0  Warnings: 0
 
mysql> show table status from MyDB where name='TEST'\G;
*************************** 1. row ***************************
           Name: TEST
         Engine: InnoDB
        Version: 10
     Row_format: Dynamic
           Rows: 0
 Avg_row_length: 0
    Data_length: 16384
Max_data_length: 0
   Index_length: 16384
      Data_free: 0
 Auto_increment: NULL
    Create_time: 2018-09-20 14:04:05
    Update_time: NULL
     Check_time: NULL
      Collation: utf8_general_ci
       Checksum: NULL
 Create_options: row_format=DYNAMIC
        Comment: 
1 row in set (0.00 sec)
 
ERROR: 
No query specified
 
mysql> ALTER TABLE TEST MODIFY CODE_VALUE1 VARCHAR(350);
Query OK, 0 rows affected (0.02 sec)
Records: 0  Duplicates: 0  Warnings: 0

2:使用字首索引解決這個問題

之所以要限制索引鍵值的大小,是因為效能問題,而字首索引能很好的解決這個問題。不需要修改任何系統變數。

mysql> show index from TEST;
..................................
 
mysql> ALTER TABLE TEST DROP INDEX IDX_GEN_CODE;
Query OK, 0 rows affected (0.00 sec)
Records: 0  Duplicates: 0  Warnings: 0
 
mysql> CREATE IDX_GEN_CODE TEST ON TEST (CODE_NAME, CODE_VALUE1(12));
Query OK, 0 rows affected (0.01 sec)
Records: 0  Duplicates: 0  Warnings: 0
 
mysql> ALTER TABLE TEST MODIFY CODE_VALUE1 VARCHAR(350);
Query OK, 1064 rows affected (0.08 sec)
Records: 1064  Duplicates: 0  Warnings: 0

問題延伸: 為什麼InnoDB的索引位元組數限制為767位元組? 而不是800位元組呢? 這樣限制又是出於什麼具體效能的考慮呢? 暫時還沒有弄清楚這些細節問題!

參考資料:

  • https://dev.mysql.com/doc/refman/5.6/en/innodb-restrictions.html
  • https://dev.mysql.com/doc/refman/5.7/en/innodb-restrictions.html