背景:
當用Explain查看SQL的執行計划時,里面有列顯示了 key_len 的值,根據這個值可以判斷索引的長度,在組合索引里面可以更清楚的了解到了哪部分字段使用到了索引。
環境:
CREATE TABLE `tmp_0612` ( `id` int(11) NOT NULL, `name` varchar(10) DEFAULT NULL, `age` int(11) DEFAULT NULL, `address` varchar(100) DEFAULT NULL, PRIMARY KEY (`id`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8
插入數據:
insert into tmp_0612 values(1,'a',11,'hz'),(2,'b',22,'gz'),(3,'c',33,'aa');
創建索引:
alter table tmp_0612 add index idx_name(name); alter table tmp_0612 add index idx_age(age);
測試:
explain select * from tmp_0612 where name ='a'; +----+-------------+----------+------+---------------+----------+---------+-------+------+-----------------------+ | id | select_type | table | type | possible_keys | key | key_len | ref | rows | Extra | +----+-------------+----------+------+---------------+----------+---------+-------+------+-----------------------+ | 1 | SIMPLE | tmp_0612 | ref | idx_name | idx_name | 33 | const | 1 | Using index condition | +----+-------------+----------+------+---------------+----------+---------+-------+------+-----------------------+
從上面的執行計划可知,索引的長度是33。比預想的30(10*3(utf8))要高出3字節,為什么呢?進一步測試:
修改name 成 not null
alter table tmp_0612 modify name varchar(10) not null;
再看執行計划:
explain select * from tmp_0612 where name ='a'; +----+-------------+----------+------+---------------+----------+---------+-------+------+-----------------------+ | id | select_type | table | type | possible_keys | key | key_len | ref | rows | Extra | +----+-------------+----------+------+---------------+----------+---------+-------+------+-----------------------+ | 1 | SIMPLE | tmp_0612 | ref | idx_name | idx_name | 32 | const | 1 | Using index condition | +----+-------------+----------+------+---------------+----------+---------+-------+------+-----------------------+
發現上面的執行計划和第一次的有區別(key_len),經過多次測試,發現字段允許NULL的會多出一個字節。想到了之前的一篇文章,NULL是需要一個標志位的,占用1個字符。那還有2個字節怎么算?這里想到的是會不會和 多字節字符集 相關?那改字符集試試:
alter table tmp_0612 convert to charset latin1; explain select * from tmp_0612 where name ='a'; +----+-------------+----------+------+---------------+----------+---------+-------+------+-----------------------+ | id | select_type | table | type | possible_keys | key | key_len | ref | rows | Extra | +----+-------------+----------+------+---------------+----------+---------+-------+------+-----------------------+ | 1 | SIMPLE | tmp_0612 | ref | idx_name | idx_name | 12 | const | 1 | Using index condition | +----+-------------+----------+------+---------------+----------+---------+-------+------+-----------------------+
發現還是多了2個字節,看來和多字節字符集沒有關系了。那會不會和 變長字段 有關系?再試試:
alter table tmp_0612 convert to charset utf8; alter table tmp_0612 modify name char(10) not null; explain select * from tmp_0612 where name ='a'; +----+-------------+----------+------+---------------+----------+---------+-------+------+-----------------------+ | id | select_type | table | type | possible_keys | key | key_len | ref | rows | Extra | +----+-------------+----------+------+---------------+----------+---------+-------+------+-----------------------+ | 1 | SIMPLE | tmp_0612 | ref | idx_name | idx_name | 30 | const | 1 | Using index condition | +----+-------------+----------+------+---------------+----------+---------+-------+------+-----------------------+
和預料的一樣了,是30=10*3。到這里相信大家都已經很清楚了,要是還比較模糊就看反推到33字節。
改成允許NULL,應該會變成31。
alter table tmp_0612 modify name char(10); explain select * from tmp_0612 where name ='a'; +----+-------------+----------+------+---------------+----------+---------+-------+------+-----------------------+ | id | select_type | table | type | possible_keys | key | key_len | ref | rows | Extra | +----+-------------+----------+------+---------------+----------+---------+-------+------+-----------------------+ | 1 | SIMPLE | tmp_0612 | ref | idx_name | idx_name | 31 | const | 1 | Using index condition | +----+-------------+----------+------+---------------+----------+---------+-------+------+-----------------------+
改成變長字段類型,應該會變成33。
alter table tmp_0612 modify name varchar(10); explain select * from tmp_0612 where name ='a'; +----+-------------+----------+------+---------------+----------+---------+-------+------+-----------------------+ | id | select_type | table | type | possible_keys | key | key_len | ref | rows | Extra | +----+-------------+----------+------+---------------+----------+---------+-------+------+-----------------------+ | 1 | SIMPLE | tmp_0612 | ref | idx_name | idx_name | 33 | const | 1 | Using index condition | +----+-------------+----------+------+---------------+----------+---------+-------+------+-----------------------+
改成單字節字符集,還是還是需要額外的3字節(1:null ;變長字段:2),和字符集無關。
alter table tmp_0612 convert to charset latin1; explain select * from tmp_0612 where name ='a'; +----+-------------+----------+------+---------------+----------+---------+-------+------+-----------------------+ | id | select_type | table | type | possible_keys | key | key_len | ref | rows | Extra | +----+-------------+----------+------+---------------+----------+---------+-------+------+-----------------------+ | 1 | SIMPLE | tmp_0612 | ref | idx_name | idx_name | 13 | const | 1 | Using index condition | +----+-------------+----------+------+---------------+----------+---------+-------+------+-----------------------+
反推上去都和預測的一樣。
其他測試:
explain select * from tmp_0612 where age = 11; +----+-------------+----------+------+---------------+---------+---------+-------+------+-------+ | id | select_type | table | type | possible_keys | key | key_len | ref | rows | Extra | +----+-------------+----------+------+---------------+---------+---------+-------+------+-------+ | 1 | SIMPLE | tmp_0612 | ref | idx_age | idx_age | 5 | const | 1 | NULL | +----+-------------+----------+------+---------------+---------+---------+-------+------+-------+ alter table tmp_0612 modify age int not null; explain select * from tmp_0612 where age = 11; +----+-------------+----------+------+---------------+---------+---------+-------+------+-------+ | id | select_type | table | type | possible_keys | key | key_len | ref | rows | Extra | +----+-------------+----------+------+---------------+---------+---------+-------+------+-------+ | 1 | SIMPLE | tmp_0612 | ref | idx_age | idx_age | 4 | const | 1 | NULL | +----+-------------+----------+------+---------------+---------+---------+-------+------+-------+
int 是占4個字符的,上面key_len的也是符合預期。關於組合索引的,可以自己去測試玩。
總結:
變長字段需要額外的2個字節,固定長度字段不需要額外的字節。而null都需要1個字節的額外空間,所以以前有個說法:索引字段最好不要為NULL,因為NULL讓統計更加復雜,並且需要額外一個字節的存儲空間。
key_len的長度計算公式:
varchr(10)變長字段且允許NULL : 10*(Character Set:utf8=3,gbk=2,latin1=1)+1(NULL)+2(變長字段)
varchr(10)變長字段且不允許NULL : 10*(Character Set:utf8=3,gbk=2,latin1=1)+2(變長字段)
char(10)固定字段且允許NULL : 10*(Character Set:utf8=3,gbk=2,latin1=1)+1(NULL)
char(10)固定字段且不允許NULL : 10*(Character Set:utf8=3,gbk=2,latin1=1)