MySQL key_len 大小的計算


背景:

      當用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)

 


免責聲明!

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



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