1. 程式人生 > >漫談MySQL索引與欄位兒長度的關係

漫談MySQL索引與欄位兒長度的關係

在資料庫開發中,索引是優化查詢效能的重要手段,本文是對使用MySQL 5.6.28進行索引優化時遇到的問題一個總結,目的是為了加深對索引長度和欄位長度的理解,理清這兩個長度的關係,本檔案均使用InnoDB儲存引擎、utf8字符集、索引方法為btree。

在使用MySQL時常用的資料型別有int、bigint、char、varchar、date、datetime、timestamp、float、double,每種型別和長度見下表:

常用資料型別和長度
資料型別 長度(byte)
int 4
bigint 8
char(n) n表示字元數,位元組數為n*3,n<=255
varchar(n) n表示字元數,位元組數為n*3,n<=21845
date 3
datetime 8
timestamp 4
float 4
double 8

在MySQL中為表建立索引時經常會遇到“Error : Specified key was too long; max key length is 767 bytes”和“Error : Duplicate key name ind_composite_3072”錯誤,767和3072的由來參考官方文件或《關於InnoDB索引長度限制的tips》。這兩個錯誤都和MySQL對索引長度的預設設定有關,可通過設定innodb_large_prefix,修改767為3072。由於MySQL使用字首索引,所謂字首索引,指的是隻用欄位的前n個字元建立索引。除text、blob在建索引時必須要指定字首長度外,大部分型別是不需要指定長度的,指定字首的方法見後面的示例。

下面通過具體示例來說明欄位長度與索引長度的關係,為了測試建立"index_test"表,SQL如下:

CREATE TABLE `index_test` (
	`id` int NOT NULL,
	`num` bigint,
	`achar` char(50),
	`avarchar` varchar(2000),
	`adate` date,
	`adatetime` datetime,
	PRIMARY KEY (`id`)
) ENGINE INNODB DEFAULT CHARACTER SET utf8 COMMENT='測試索引長度與欄位長度的關係';

接下來為表“index_test”在“avarchar”欄位上增加一個索引,建立索引時不指定索引字首的長度,SQL如下:

ALTER TABLE `index_test` ADD INDEX `ind_single_varchar` (avarchar) COMMENT '單一varchar欄位的索引,不指定索引字首的長度';

執行上面的SQL時,MySQL給出了錯誤提示“Error : Specified key was too long; max key length is 767 bytes”,這句話的意思是索引的最大長度為767位元組(byte),而“index_test”的“avarchar”的長度為2000字元,由於使用的是utf8字條集,一個字元佔3個位元組,長度為2000*3=6000位元組,所以建立失敗。接下來我們為索引指寫字首長度,索引字首的長度單位為字元,SQL如下:

ALTER TABLE `index_test` ADD INDEX `ind_single_varchar_pre` (avarchar(255)) COMMENT '單一varchar欄位的索引,指定索引字首長度為255字元';

 “avarchar(255)”是MySQL建立索引語法的一部分,形式為“欄位名(索引字首長度)”,單位為字元,255*3=765<767,所以上面的語句能夠正常執行。實際使用中text、blob使用的不多,所以“Error : Specified key was too long; max key length is 767 bytes”通常在對varchar型別欄位建索引出現。

在對單個欄位建立索引時,主要的限制是欄位長度,如果欄位長度不超過767位元組,不會出現問題。但複合索引的情況與單欄位索引有所不同。複合索引中除了單欄位長度不能超過767位元組外,索引中所有欄位長度的總合不能超過3072位元組。

為了進一步測試複合索引與欄位長度的關係,讓我們增加幾個varchar型別的欄位,SQL如下:

ALTER TABLE `index_test` ADD COLUMN `bvarchar` VARCHAR(255),
ADD COLUMN `cvarchar` VARCHAR(255),
ADD COLUMN `dvarchar` VARCHAR(255),
ADD COLUMN `evarchar` VARCHAR(255);

接下來為“index_test”增加一個複合索引,建立索引時不指定索引字首的長度,SQL如下:

ALTER TABLE `index_test` ADD INDEX `ind_composite` (`id`,`num`,`achar`,`adate`,`adatetime`,`avarchar`) 
COMMENT '不指定字首長度的複合索引';

由於“avarchar”的長度為2000字元,2000*3>767,這違反了MySQL的約束,如期給出錯誤提示“Error : Specified key was too long; max key length is 767 bytes”。接下來為“avarchar”指定索引的字首長度,SQL如下:

ALTER TABLE `index_test` ADD INDEX `ind_composite_pre` (`id`,`num`,`achar`,`adate`,`adatetime`,`avarchar`(255)) 
COMMENT '指定avarchar前255個字元用於索引';

由於指定了前255個字元用於索引,所以上面的語句得以正常執行。

通過上面的例子我們驗證了在複合索引中,對單個欄位的約束條件,這一點和單欄位索引是一樣的。下面的例子將用於驗證MySQL對複合索引總長度為3072位元組的限制。用下面的SQL為“index_test”表建立複合索引,如下:

ALTER TABLE `index_test` ADD INDEX `ind_composite_3072` (`bvarchar`,`cvarchar`,`dvarchar`,`evarchar`,`num`,`adatetime`)
COMMENT '複合索引,總長度不能超過<=3072位元組';

由於“bvarchar”、“cvarchar”、“dvarchar”、“evarchar”長度均為255字元,即255*3=3060位元組,“num”、“adatetime”為8位元組,所以這個索引的總長度為3060+8+8=3076位元組>3072,執行上面語句時MySQL會給出錯誤提示“Error : Duplicate key name ind_composite_3072”。

通過上面一系列的試驗,我們明確知道MySQL建立索引時,單欄位索引的欄位長度不能超過767位元組,超過時需要指定索引字首;建立複合索引時,單欄位長度不能超過767位元組,且索引中所有欄位的總長度不能超過3072位元組,違反這些約束時需要刪減欄位或是為長度較大的欄位指定索引字首。在MySQL 5.6.28中,字元型別的長度指的是字元數,而不是位元組數,每個字元佔用的位元組數和使用的字符集相關。