Explain之key_len長度計算


   以前我很好奇通過執行計划Explain去分析SQL的時候看到的key_len值有時很小,有時看到很大,那時也不知道它是怎么算出來的,現在終於搞懂了,嘻。因為網上對key_len的長度的計算的資料也很少,官網也如此。我相信還有很多人不關心key_len的值是怎么來的,或者key_len有什么用的。key_len表示索引使用的字節數,根據這個值,就可以判斷索引使用情況,特別是在組合索引的時候,判斷所有的索引字段是否都被查詢用到。好啦,廢話不多說,我們通過例子來說明吧!

 

在說key_len長度計算長度之前,先和大家溫習字符類型的知識:

   char和varchar是日常使用最多的字符類型。char(N)用於保存固定長度的字符串,長度最大為255,比指定長度大的值將被截短,而比指定長度小的值將會用空格進行填補。

   varchar(N)用於保存可以變長的字符串,長度最大為65535,只存儲字符串實際實際需要的長度(它會增加一個額外字節來存儲字符串本身的長度),varchar使用額外的1~2字節來存儲值的的長度,如果列的最大長度小於或者等於255,則用1字節,否則用2字節。

   char和varchar跟字符編碼也有密切的聯系,latin1占用1個字節,gbk占用2個字節,utf8占用3個字節。(不同字符編碼占用的存儲空間不同)

Latinl如下:

Gbk如下:

Utf8如下:

 

一、字符串類型的key_len計算

測試表的表結構如下:

mysql> show create table t1\G
*************************** 1. row ***************************
       Table: t1
Create Table: CREATE TABLE `t1` (
  `id` int(11) NOT NULL AUTO_INCREMENT,
  `name` char(10) DEFAULT NULL,
  `addr` varchar(20) DEFAULT NULL,
  PRIMARY KEY (`id`),
  KEY `name` (`name`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8
1 row in set (0.00 sec)
》

1、索引字段為char類型的key_len計算:

(1)允許為Null時

mysql> explain select * from t1 where name='xuanzhi';
+----+-------------+-------+------+---------------+------+---------+-------+------+-----------------------+
| id | select_type | table | type | possible_keys | key  | key_len | ref   | rows | Extra                 |
+----+-------------+-------+------+---------------+------+---------+-------+------+-----------------------+
|  1 | SIMPLE      | t1    | ref  | name          | name | 31      | const |    1 | Using index condition |
+----+-------------+-------+------+---------------+------+---------+-------+------+-----------------------+
1 row in set (0.00 sec)

mysql> 

可以看到key_len=31,這個31是字節長度,它是怎么算出來的呢?讓我們一起來分析下:
從表結構可以看到字符集是utf8,那就一個字符3個字節,那么char(10)代表的是10個字符相當30個字節,Null 占1個字節,char類型不需要額外的字節來存儲值的的長度,所以得到:key_len:10x3+1=31,可以看到跟上面的結果一致的。

(2)不允許為Null時

mysql> alter table t1 change name name char(10) not null;
Query OK, 2 rows affected (0.06 sec)
Records: 2  Duplicates: 0  Warnings: 0

mysql> explain select * from t1 where name='xuanzhi';
+----+-------------+-------+------+---------------+------+---------+-------+------+-----------------------+
| id | select_type | table | type | possible_keys | key  | key_len | ref   | rows | Extra                 |
+----+-------------+-------+------+---------------+------+---------+-------+------+-----------------------+
|  1 | SIMPLE      | t1    | ref  | name          | name | 30      | const |    1 | Using index condition |
+----+-------------+-------+------+---------------+------+---------+-------+------+-----------------------+
1 row in set (0.01 sec)

mysql> 

算法和上面差不多,只是字段不允許為Null,所以比上面的例子少了一個字節,key_len=10x3=30

2、索引字段為varchar類型且允許為Null時的key_len計算:

(1)允許為Null時

mysql> alter table t1 change name name varchar(10);
Query OK, 2 rows affected (0.06 sec)
Records: 2  Duplicates: 0  Warnings: 0

mysql> explain select * from t1 where name='xuanzhi';
+----+-------------+-------+------+---------------+------+---------+-------+------+-----------------------+
| id | select_type | table | type | possible_keys | key  | key_len | ref   | rows | Extra                 |
+----+-------------+-------+------+---------------+------+---------+-------+------+-----------------------+
|  1 | SIMPLE      | t1    | ref  | name          | name | 33      | const |    1 | Using index condition |
+----+-------------+-------+------+---------------+------+---------+-------+------+-----------------------+
1 row in set (0.02 sec)

mysql> 

還是utf8的字符集,所以還是一個字符3個字節,那么varchar(10)就是10個字符30個字節,Null占一個字節,由於varchar類型需要額外的1~2字節來存儲值的的長度:所以key_len:10x3+1+2=33

(2)不允許為Null時

mysql> alter table t1 change name name varchar(10) not null;
Query OK, 2 rows affected (0.07 sec)
Records: 2  Duplicates: 0  Warnings: 0

mysql> explain select * from t1 where name='xuanzhi';
+----+-------------+-------+------+---------------+------+---------+-------+------+-----------------------+
| id | select_type | table | type | possible_keys | key  | key_len | ref   | rows | Extra                 |
+----+-------------+-------+------+---------------+------+---------+-------+------+-----------------------+
|  1 | SIMPLE      | t1    | ref  | name          | name | 32      | const |    1 | Using index condition |
+----+-------------+-------+------+---------------+------+---------+-------+------+-----------------------+
1 row in set (0.00 sec)

mysql> 

相信大家都會算了吧,哈哈,不允許為Null就少了一個字符,所以Key_len:10x3+2=32

 

 二、數值類型的key_len計算,先來回顧數值類型的一些根本知識:

    所有整數類型可以有一個可選(非標准)屬性UNSIGNED。當你想要在列內只允許非負數和該列需要較大的上限數值范圍時可以使用無符號值。如果設置了ZEROFILL擴展屬性試,默認就有了無符號屬性(UNSIGNED),所以INT(1)與INT(11)后的括號中的字符表示顯示寬度,整數列的顯示寬度與MySQL需要用多少個字符來顯示該列數值與該整數需要的存儲空間的大小都沒有關系,INT類型的字段能存儲的數據上限還是2147483647(有符號型)和4294967295(無符號型)。其實當我們在選擇使用INT的類型的時候,不論是INT(1)還是INT(11),它在數據庫里面存儲的都是4個字節的長度

 

(1)int型允許為Null

mysql> alter table t1 add age int(3);
Query OK, 0 rows affected (0.09 sec)
Records: 0  Duplicates: 0  Warnings: 0


mysql> alter table t1 add key (age);
Query OK, 0 rows affected (0.07 sec)
Records: 0  Duplicates: 0  Warnings: 0

mysql> explain select * from t1 where age=20;
+----+-------------+-------+------+---------------+------+---------+-------+------+-------+
| id | select_type | table | type | possible_keys | key  | key_len | ref   | rows | Extra |
+----+-------------+-------+------+---------------+------+---------+-------+------+-------+
|  1 | SIMPLE      | t1    | ref  | age           | age  | 5       | const |    1 | NULL  |
+----+-------------+-------+------+---------------+------+---------+-------+------+-------+
1 row in set (0.00 sec)

mysql> 

分析:int(3),就是4個字節,上面已經提及到,int(N)都是4個字節長度,允許為Null占一個字節,所以key_len:4+1=5

(2)不允許Null時(不能通過alter table t1 change age age int(3) not null,要drop掉才能添加為not null

mysql> alter table t1 change age age int(3) not null;
ERROR 1138 (22004): Invalid use of NULL value

mysql> alter table t1 drop age; Query OK, 0 rows affected (0.07 sec) Records: 0 Duplicates: 0 Warnings: 0 mysql> alter table t1 add age int(3) not null; Query OK, 0 rows affected (0.03 sec) Records: 0 Duplicates: 0 Warnings: 0 mysql> alter table t1 add key (age); Query OK, 0 rows affected (0.05 sec) Records: 0 Duplicates: 0 Warnings: 0 mysql> explain select * from t1 where age=20; +----+-------------+-------+------+---------------+------+---------+-------+------+-------+ | id | select_type | table | type | possible_keys | key | key_len | ref | rows | Extra | +----+-------------+-------+------+---------------+------+---------+-------+------+-------+ | 1 | SIMPLE | t1 | ref | age | age | 4 | const | 1 | NULL | +----+-------------+-------+------+---------------+------+---------+-------+------+-------+ 1 row in set (0.00 sec) mysql>

如果是Not null的話,int(N)的key_len都是4個字節
當結合可選擴展屬性ZEROFILL使用時, 默認補充的空格用零代替。例如,對於聲明為INT(5) ZEROFILL的列,值4檢索為00004,看例子:

mysql> desc aa;
+--------+--------------------------+------+-----+---------+-------+
| Field  | Type                     | Null | Key | Default | Extra |
+--------+--------------------------+------+-----+---------+-------+
| id     | int(3)                   | YES  |     | NULL    |       |
| number | int(5) unsigned zerofill | YES  |     | NULL    |       |
+--------+--------------------------+------+-----+---------+-------+
2 rows in set (0.00 sec)

mysql> 
mysql> insert into aa (id,number) values (11,4);
Query OK, 1 row affected (0.00 sec)

mysql> select * from aa;
+------+--------+
| id   | number |
+------+--------+
|   11 |  00004 |
+------+--------+
1 rows in set (0.00 sec)

其它數值類型也是同理的,相信大家找到共同點了,這里我就不作測試。

 

三、日期時間型的Key_len計算,先來回顧一下日期時間型的基本知知識:

 

Datetime類型key_len計算(針對MySQL5.5版本之前):

1、允許為Null時:

mysql> desc t1;
+-------+----------+------+-----+---------+-------+
| Field | Type     | Null | Key | Default | Extra |
+-------+----------+------+-----+---------+-------+
| id    | int(11)  | YES  |     | NULL    |       |
| date  | datetime | YES  | MUL | NULL    |       |
+-------+----------+------+-----+---------+-------+
2 rows in set (0.00 sec)

mysql> explain select * from t1 where date='2015-05-03 12:10:10';
+----+-------------+-------+------+---------------+------+---------+-------+------+-------------+
| id | select_type | table | type | possible_keys | key  | key_len | ref   | rows | Extra       |
+----+-------------+-------+------+---------------+------+---------+-------+------+-------------+
|  1 | SIMPLE      | t1    | ref  | date          | date | 9       | const |    1 | Using where |
+----+-------------+-------+------+---------------+------+---------+-------+------+-------------+
1 row in set (0.00 sec)

mysql> 

分析:因為datetime類型存儲8個節點,允許為Null,所以多占一個字節,所以key_len: 8+1=9

2、不允許為null時:

mysql> desc t1;
+-------+----------+------+-----+---------+-------+
| Field | Type     | Null | Key | Default | Extra |
+-------+----------+------+-----+---------+-------+
| id    | int(11)  | YES  |     | NULL    |       |
| date  | datetime | NO   | MUL | NULL    |       |
+-------+----------+------+-----+---------+-------+
2 rows in set (0.00 sec)

mysql>  explain select * from t1 where date='2015-05-03 12:10:10';
+----+-------------+-------+------+---------------+------+---------+-------+------+-------+
| id | select_type | table | type | possible_keys | key  | key_len | ref   | rows | Extra |
+----+-------------+-------+------+---------------+------+---------+-------+------+-------+
|  1 | SIMPLE      | t1    | ref  | date          | date | 8       | const |    1 |       |
+----+-------------+-------+------+---------------+------+---------+-------+------+-------+
1 row in set (0.00 sec)

mysql> 

分析:當不為空時,datetime都是存儲8個字節,所以key_len=8。

MySQL5.6 datetime的key_len計算

1、允許為空時:

mysql> select version();
+------------+
| version()  |
+------------+
| 5.6.10-log |
+------------+
1 row in set (0.00 sec)

mysql> desc t1;
+-------+----------+------+-----+---------+-------+
| Field | Type     | Null | Key | Default | Extra |
+-------+----------+------+-----+---------+-------+
| id    | int(11)  | YES  |     | NULL    |       |
| date  | datetime | YES  | MUL | NULL    |       |
+-------+----------+------+-----+---------+-------+
2 rows in set (0.00 sec)

mysql> explain select * from t1 where date='2015-05-03 12:10:10';
+----+-------------+-------+------+---------------+------+---------+-------+------+-------+
| id | select_type | table | type | possible_keys | key  | key_len | ref   | rows | Extra |
+----+-------------+-------+------+---------------+------+---------+-------+------+-------+
|  1 | SIMPLE      | t1    | ref  | date          | date | 6       | const |    1 | NULL  |
+----+-------------+-------+------+---------------+------+---------+-------+------+-------+
1 row in set (0.00 sec)

分析:mysql5.6的datetime已經不是存儲8個字節了,應該存儲5個字節了,允許為Null,所以加一個字節,所以key_len:5+1

2、不允許Null時:

mysql> alter table t1 modify date datetime not null;
Query OK, 0 rows affected (0.06 sec)
Records: 0  Duplicates: 0  Warnings: 0

mysql> explain select * from t1 where date='2015-05-03 12:10:10';
+----+-------------+-------+------+---------------+------+---------+-------+------+-------+
| id | select_type | table | type | possible_keys | key  | key_len | ref   | rows | Extra |
+----+-------------+-------+------+---------------+------+---------+-------+------+-------+
|  1 | SIMPLE      | t1    | ref  | date          | date | 5       | const |    1 | NULL  |
+----+-------------+-------+------+---------------+------+---------+-------+------+-------+
1 row in set (0.00 sec)

mysql> 

分析:不為Null時,則存儲自身的字節大小,所以key_len=5

 

四、聯合索引的key_len計算,在說聯合索引計算之前,我們先回顧一個索引的限制

InnoDB: INNODB的索引會限制單獨Key的最大長度為767字節,超過這個長度必須建立小於等於767字節的前綴索引。

MyISAM: MyISAM存儲引擎的表,索引前綴的長度可以達到1000字節長。

前綴索引能提高索引建立速度和檢索速度,但是無法使用:索引覆蓋掃描和通過索引的排序

 

mysql> show create table xuanzhi\G
*************************** 1. row ***************************
       Table: xuanzhi
Create Table: CREATE TABLE `xuanzhi` (
  `id` int(11) NOT NULL AUTO_INCREMENT,
  `name` char(20) DEFAULT NULL,
  `addr` varchar(20) DEFAULT NULL,
  PRIMARY KEY (`id`),
  KEY `name` (`name`,`addr`)
) ENGINE=InnoDB AUTO_INCREMENT=2 DEFAULT CHARSET=utf8
1 row in set (0.00 sec)

mysql> explain select * from xuanzhi where name='xuanzhi' and addr='shanghai';
+----+-------------+---------+------+---------------+------+---------+-------------+------+--------------------------+
| id | select_type | table   | type | possible_keys | key  | key_len | ref         | rows | Extra                    |
+----+-------------+---------+------+---------------+------+---------+-------------+------+--------------------------+
|  1 | SIMPLE      | xuanzhi | ref  | name          | name | 124     | const,const |    1 | Using where; Using index |
+----+-------------+---------+------+---------------+------+---------+-------------+------+--------------------------+
1 row in set (0.00 sec)

mysql> 

可以看到表結構里有一個聯合索引name,那么上面的key_len是怎么算出來的呢,相信到現在,同學們都有計算的思路,好吧,我們算一下:
name的key_len計算:utf8:char(20)x3+null:1=61

addr的key_len計算: utf8:varchar(20)x3+null:1+2=63 (如果不明白為什么+2往前面再看一次)

聯合索引name('name','addr') key_len:61+63=124

嘻嘻,我想到現在沒多少人不會算了吧,通過key_len可以讓我們知道它是否有充分利用索引

 

還有一些類型沒有說到的,希望同學們自己測試一下,下面我總結一下計算公式:

char和varchar類型key_len計算公式:
varchr(N)變長字段且允許NULL   =  N * ( character set:utf8=3,gbk=2,latin1=1)+1(NULL)+2(變長字段)
varchr(N)變長字段且不允許NULL =  N * ( character set:utf8=3,gbk=2,latin1=1)+2(變長字段)

char
(N)固定字段且允許NULL = N * ( character set:utf8=3,gbk=2,latin1=1)+1(NULL) char(N)固定字段且允許NULL = N * ( character set:utf8=3,gbk=2,latin1=1) 數值數據的key_len計算公式: TINYINT允許NULL = 1 + 1(NULL) TINYINT不允許NULL = 1 SMALLINT允許為NULL = 2+1(NULL) SMALLINT不允許為NULL = 2 INT允許為NULL = 4+1(NULL) INT不允許為NULL = 4 日期時間型的key_len計算:(針對mysql5.5及之前版本) DATETIME允許為NULL = 8 + 1(NULL) DATETIME不允許為NULL = 8 TIMESTAMP允許為NULL = 4 + 1(NULL) TIMESTAMP不允許為NULL = 4

還有一些沒寫出來,相信大家對key_len有一定的認識了,所以這里就不把所有的都寫出來了。

 

總結:

   一、INT型如果不結合可選擴展屬性ZEROFILL使用,INT(1)和INT(N),它在數據庫里面存儲的都是4個字節的長度,當然N最大的上限

   二、從上面的例子可以看到,定義表結構時,如果字段允許為NULL,會有額外的開銷,所以建議字段盡量不要使用允許NULL,提高索引的使用效率

   三、INNODB的索引會限制單獨Key的最大長度為767字節,MyISAM索引前綴的長度可以達到1000字節長,如果order by也使用了索引則key_len不計算在內

 

 參考資料:

     http://www.cnblogs.com/gomysql/p/3616366.html

    http://www.cnblogs.com/LMySQL/p/4525867.html

     <<深入淺出MySQL>>

 

 

作者:陸炫志

出處:xuanzhi的博客 http://www.cnblogs.com/xuanzhi201111

您的支持是對博主最大的鼓勵,感謝您的認真閱讀。本文版權歸作者所有,歡迎轉載,但請保留該聲明。

 

 
 

 

 
 

 


免責聲明!

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



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