[轉]MySQL 經典案例分析:Specified key was too long索引字段長度過長


原文地址:https://segmentfault.com/p/1210000010686697/read

一、 問題是這樣來的

BG內部要進行數據庫的容災演習,需要模擬線上實際的環境進行測試,這就需要copy一份線上的數據庫到測試庫中,其實也就是重建一個線上數據庫。要完成這個任務其實有N種不同的做法,但是我當時一想:“好久沒有測試一下數據備份是否正常了。”就決定利用備份數據進行重建測試庫,於是乎就導出了一份XXXX.sql文件,直接往測試庫里面導入,由於線上庫的實際數據量還不小,所以當我按完“Enter”后就決定下班回家,估計等我到家了就差不多完成了。

結果竟然出問題了,屏幕上出現:

ERROR 1071 (42000): Specified key was too long; max key length is 767 bytes

二、 苦逼的探索

對於報錯信息“ERROR 1071 (42000): Specified key was too long; max key length is 767 bytes”,其實意思就是“索引字段長度太長,超過了767bytes”。

mysql的varchar主鍵只支持不超過767個字節或者768/2=384個雙字節 或者767/3=255個三字節的字段 而GBK是雙字節的,UTF8是三字節的。

那么問題來了,為什么線上業務庫中正常的數據直接移植到另一個庫,竟然會報設置表中字段不合法的錯!

(1)字符集設置不同?

第一個想法是查看兩個庫的字符集設置是不是不一樣,果然線上庫的字符集character_set_database=lantin1,而測試庫的字符集character_set_database= utf8。這時候,我很天真的以為可能是因為建表的時候沒有指定字符集,所以在導入測試庫的時候默認使用utf-8字符集,導致Specified key was too long。所以就更改了測試庫中默認的字符集,但是導入數據的時候依然還是報同樣的錯誤。而且我在建表的語句中發現了,其實原來就已經指定了字符集,都是UTF8(CHARSET=UTF8),所以字符集設置不同並不是問題的症結所在。

(2)更改索引字段長度?

定位到出錯的建表語句,果然是使用UTF8字符集,而且長度是256(如下圖所示)。

為了驗證索引字段的字符集設置為utf8時,varchar(256)確實是超長了,做了如下的測試:

那么問題簡單了,更改tagvarchar(256)為tagvarchar(255),然后再進行導入數據,竟然成功了。但是依然后兩個困惑在我腦海里,

① 為什么線上庫可以設置tagvarchar(256)?

② 要是tag字段剛好有256個字節的數據,那么轉存的時候,數據不就丟失了?

(3)存儲引擎惹的禍?

當我去檢查剛剛那張被我手動修改過的表的狀態時,意想不到的發現了一個問題,為什么建表的語句明明寫的是“ENGINE=MYISAM”,但是導入后的表變成了“ENGINE=InnoDB”。(如下圖所示)

再仔細一回想,剛剛竟然只記得索引字段長度不能超過768,但是忘了innodb和myisam的區別了。

① innodb存儲引擎,多列索引的長度限制如下:

每個列的長度不能大於767 bytes;所有組成索引列的長度和不能大於3072 bytes

② myisam存儲引擎,多列索引長度限制如下:

每個列的長度不能大於1000 bytes,所有組成索引列的長度和不能大於1000 bytes

原來是兩張表的存儲引擎不同,這樣就解釋了剛剛的兩個疑問,但是又一個疑問就出現了,為什么建表語句中明明寫的是MYISAM表,怎么導入之后就變成了INNODB。(經過對比,發現所有的MYISAM表都被轉換成了INNODB表)

這個問題暫且先擱置,緊急的是要怎么規避。先分別查了下兩個數據庫的默認存儲引擎,果然發現在線上庫中default_storage_engine和storage_engine都是MYISAM,而測試庫卻均是INNODB。(如下圖所示)

通過修改默認的存儲引擎:

1)在my.cnf中的 [mysqld] 下加入default-storage-engine=INNODB ,保存(需重啟)。

2)set global default_storage_engine="InnoDB"

通過這種方法終於保證了不修改源數據文件的情況下,能正確的重建線上數據至測試庫,並且核對了數據和存儲引擎,均與線上庫一致。

(4)InnoDB存儲優化選項?

問題雖然是解決了,但是難道InnoDB中單個索引字段的長度真的只能小於767?

又經過一番探索,發現在InnoDB中,可以啟用啟用innodb_large_prefix參數,來使得單個索引字段的長度突破767。

注意:

①啟用innodb_large_prefix參數能夠取消對於索引中每列長度的限制(但是無法取消對於索引總長度的限制)

②啟用innodb_large_prefix必須同時指定innodb_file_format=barracuda,innodb_file_per_table=true,並且建表的時候指定表的row_format為dynamic或者compressed(mysql 5.6中row_format默認值為compact)

具體的操作如下:

① 查看innodb_large_prefix,innodb_file_format參數

mysql> show variables like 'innodb_large_prefix';

+-------------------------+---------------+

| Variable_name | Value |

+--------------------------+--------------+

| innodb_large_prefix | OFF |

+--------------------------+--------------+

② 查看mysql> show variables like 'innodb_file_format';

+-------------------------+---------------+

| Variable_name | Value |

+-------------------------+---------------+

| innodb_file_format | Antelope |

+-------------------------+---------------+

③ 建索引測試(innodb_large_prefix,innodb_file_format都為默認值的情況下)

mysql> create table test (id varchar(256),key (id));

mysql>create table test (id varchar(255),name varchar(255),name1 varchar(255),name2 varchar(255),name3 varchar(5),key (id,name,name1,name2,name3));

##索引列大於767 bytes時報錯,組成索引列總長度大於3072 bytes時報錯

④ 修改innodb_large_prefix,innodb_file_format參數

mysql> set global innodb_large_prefix=1;

mysql> set global innodb_file_format=BARRACUDA;

⑤ 對row_format為dynamic格式表創建索引測試

mysql>create table test(id varchar(256),key (id)) row_format=dynamic;

mysql>create table test(id varchar(255),name varchar(255),name1 varchar(255),name2 varchar(255),name3 varchar(5),key (id,name,name1,name2,name3)) row_format=dynamic;

##innodb_large_prefix=1並且innodb_file_format=BARRACUDA時,對於row_format為dynamic的表可以指定索引列長度大於767 bytes。但是索引列總長度的不能大於3072 bytes的限制仍然存在。

三、 有待深思的細節

相信大家一定還記得上面我們跳過的一個問題:為什么建表語句中明明寫的是MYISAM表,怎么導入之后就變成了INNODB。於是乎我做個2組簡單的測試:

① 設置數據庫default_storage_engine="InnoDB",將含有InnoDB和MYISAM的庫導入。

② 設置數據庫default_storage_engine=”MYISAM",將含有InnoDB和MYISAM的庫導入。

實驗結果是,default_storage_engine設置成InnoDB時,不論建表語句指定的是InnoDB或MYISAM,最終生成的表均為InnoDB;設置成MYISAM時,最終生成的表與其建表語句中設置的一致。


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM