一、前言
当我们用MySQL创建表的时候,我们需要设计字段名以及字段的类型,这时你可能会发现网上大部分表在设计的时候,数值类型一般用INT(11),而字符串类型一般用VARCHAR(255),这背后究竟暗藏什么玄机呢,且听我一一道来。
二、为什么是int(11)
首先让我们从int(11)讲起,MySQL数值类型一共可以分为四类,分别是BIT、BOOL、INTEGER和DECIMAL,其他三种类型本次暂且不表,本次单纯讨论下INTEGER类型。
INTEGER表示的是整数类型,它还可以继续细分,分为INYINT / SMALLINT / INT / MEDIUMINT / BIGINT。
不同INTEGER类型,存储的使用字节数也不同:
- TINYINT = 1 byte (8 bit)
- SMALLINT = 2 bytes (16 bit)
- MEDIUMINT = 3 bytes (24 bit)
- INT = 4 bytes (32 bit)
- BIGINT = 8 bytes (64 bit)
由上面可以得到,int(11)存储使用的字节数为4字节(即32位),所以存储范围为:-2^(32-1) ~ 2^(32-1)-1 = -2147483648 ~ 2147483647。这个数值范围其实已经能涵盖大部分的使用场景,所以使用INT类型即不会出现太小,亦或者太大的情况(正常使用下一般不会出问题),这也是其频频出镜的原因之一。
那INT(11)后面的数字表示的是什么含义呢?是不是INT(1)存储1位数字(即0-9),而INT(11)存储11位数字呢?其实不是的,后面的数字表示的是展示长度,跟实际存储无关,也就是说INT(1)的存储范围也是-2147483648 ~ 2147483647。
展示长度的不同只有在使用了ZEROFILL属性才能体现出来,加了ZEROFILL属性,当长度不够时会自动补0,其他情况下都是一样的,只不过我们默认写法就是INT(11)
下面我会通过一个小栗子来说明ZEROFILL属性下的不同:
# 创建表(必须加上zerofill,才能看到有没有补零)
create table test_int (
`id` int not null primary key,
`int3` int(3) not null default 0,
`int11` int(11) not null default 0,
`zerofill3` int(3) ZEROFILL NOT NULL,
`zerofill11` int(11) ZEROFILL NOT NULL
);
# 插入测试数据
INSERT INTO test_int(id, `int3`, `int11`, `zerofill3`, `zerofill11`) VALUES (1, 100000, 100000, 20, 20);
# 可以看到如果添加了 ZEROFILL 属性,会在前面自动补零,补零的个数就是INT后面的数字有关
mysql> SELECT * FROM test_int;
+----+--------+--------+-----------+-------------+
| id | int3 | int11 | zerofill3 | zerofill11 |
+----+--------+--------+-----------+-------------+
| 1 | 100000 | 100000 | 020 | 00000000020 |
+----+--------+--------+-----------+-------------+
1 row in set (0.00 sec)
三、为什么是varchar(255)
讲完了INT(11),我们再来看看VARCHAR(255)。VARCHAR属于字符串类型的一种,除了VARCHAR类型外,MySQL的字符串类型还包含: CHAR, BLOB和TEXT,其他类型暂且不表,本次只讨论 VARCHAR。
VARCHAR准确的表现形式应该是VARCHAR(M),该类型存储的是非定长字符串,即有可能用1字节,2字节或者3字节等存储一个字符(如果是utf8,最大是3字节;如果是utf8mb4,最大是4字节)。
MySQL的utf8字符集并不等同广义的UTF-8字符集,utf8mb4才是
与上述数值类型不同,VARCHAR后面的数字,即M,表示的是存储的最大字符数,M的范围是0-65535。对于utf8字符集来说,一个字母和一个汉字都算是一个字符。
既然M的范围是0-65535,那为什么平常我们在设计的时候,使用的大部分是VARCHAR(255) 呢?而且有的时候,M并没有超过65535,却还会报错呢?
对于第一个问题,得从MySQL的索引说起,因为我们平常使用的存储引擎大都是InnoDB,而InnoDB索引的前缀长度最长是767字节(bytes),当你使用utf8编码时,折算下来就是 767 / 3 = 255.7,也就是说,如果你想建索引的话,varchar(255)恰恰是能建索引的最大值。
对于第二个问题,我们需要知道,MySQL表的每一行其实也是有大小限制的,这个值是65535字节。
所以,如果我们使用的utf8字符集,则每一行存储的最大字符数为 65535/3=21845,如果使用的是utf8mb4字符集,则最大字符数为 65535/4=16383
但上面的计算方式其实比较理想化,实际计算还要减去一些额外的字节,如果该表包含主键ID,且类型为INT,则最大字符数为:(65535-1-2-4)/3 = 21843 字符;如果该表不包含主键ID,则最大字符数为:(65535-1-2)/3 = 21844 字符。
注:减1字节的原因是实际行的存储从第2个字节开始,减2字节的原因是VARCHAR头部的2个字节用来表示长度。
下面通过一个小栗子来说明下:
# 没有其他字段,所以最大可以到 21844
create table varchar_length1 (
varchar1 varchar(21844) not null default ''
) charset utf8;
Query OK, 0 rows affected (0.07 sec)
# 有主键ID字段,如果为21844会超出限制
create table varchar_length2 (
id int(11) not null default 0,
varchar1 varchar(21844) not null default ''
) charset utf8;
ERROR 1118 (42000): Row size too large. The maximum row size for the used table type, not counting BLOBs, is 65535.
This includes storage overhead, check the manual. You have to change some columns to TEXT or BLOBs
# 减少到21843就不会超出限制
create table varchar_length3 (
id int(11) not null default 0,
varchar1 varchar(21843) not null default ''
) charset utf8;
Query OK, 0 rows affected (0.05 sec)