搞懂 mysql 数据类型那些事(varchar、int、unsigned)
在 mysql 中我们创建表的时候就会声明列的类型,比如 char(12)、varchar(100)、int(10)、bigint(20)。 有的时候可能可能还会看到 unsigned 这个关键字,那么这些有什么猫腻呢,你是不是清楚呢,那今天 我们就一起来探讨一下
1、char、varchar
1.1 char
1.1.1 char 的存储规则
char(M)定义的列的长度为固定的,M取值可以为0~255之间,当保存CHAR值时,在它们的右边填充空格以达到指定的长度。 当检 索到CHAR值时,尾部的空格被删除掉。在存储或检索过程中不进行大小写转换。 CHAR存储定长数据很方便,CHAR字段上的索引效率级高,比如定义 char(10),那么不论你存储的数据是否达到了10个字节,都要占去10个字节的空间,不足的自动用空格填充。
1.2 varchar
1.2.1 varchar的存储规则
4.0版本以下,varchar(20),指的是20字节,如果存放 UTF8 汉字时,只能存6个(每个汉字3字节)。 5.0版本以上,varchar(20),指的是20字符,无论存放的是数字、字母还是UTF8汉字(每个汉字3字 节),都可以存放20个,最大大小是65532字节。 varchar 字段是将实际内容单独存储在聚簇索引之外,内容开头用1到2个字节表示实际长度。
官方是这么说的: Values in VARCHAR columns are variable-length strings. The length can be specified as a value from 0 to 255 before MySQL 5.0.3, and 0 to 65,535 in 5.0.3 and later versions. In contrast to CHAR, VARCHAR values are stored as a one-byte or two-byte length prefix plus data. The length prefix indicates the number of bytes in the value. A column uses one length byte if values require no more than 255 bytes, two length bytes if values may require more than 255 bytes.
1.2.2 varchar的控制位
MySQL 中的 varchar 字符类型还保留了1个字节来留其它控制信息。
1.2.2.1 示例 1
示例 1 :若一张表中只有一个字段 varchar (N)类型,utf8编码,则N最大值为多少?
create table tb_test1(
a varchar(N)
) default charset=utf8,
减 1 的原因是实际行存储从第 2 个字节开始。 减 2 的原因是 varchar 头部的 2 个字节表示长度。 除 3 的原因是字符编码是 utf8
sql测试:
create table tb_test1(a varchar(21844)) default charset=utf8;
Query OK, 0 rows affected (0.38 sec)
drop table tb_test1;
Query OK, 0 rows affected (0.00 sec)
create table tb_test1(a varchar(21845)) default charset=utf8;
ERROR 1118 (42000): Row size too large. The maximum row size for
the used table type, not counting BLOBs, is 65535. You have to
change some columns
1.2.2.2 示例 2
示例 2 :若一张表中有一个字段 varchar(N) 类型,并且有其它的字段类型,utf8 编码,则N的最大值为多少?
create table tb_test2(
a int,
b char(20),
c varchar(N)
) default charset=utf8;
减 1 的原因是实际行存储从第 2 个字节开始。 减 2 的原因是 varchar 头部的 2 个字节表示长度。 减 4 的原因是 a 字段的 int 类型占 4 个字节。 减 20 * 3 的原因是 char(20) 占用 60 个字节,编码是 utf8。
sql测试:
create table tb_test2(
a int,
b char(20),
c varchar(21822)
) default charset=utf8;
Query OK, 0 rows affected (0.28 sec)
drop table tb_test2;
Query OK, 0 rows affected (0.20 sec)
create table tb_test2(
a int,
b char(20),
c varchar(21823)
) default charset=utf8;
ERROR 1118 (42000): Row size too large. The maximum row size for the used table type,
not counting BLOBs, is 65535. You have to change some columns to TEXT or BLOBs
1.2.2.3 示例 3
示例 3:若一张表中有多字段 varhcar(N) 类型,并且有其它的字段类型,gbk 编码,则 N 的最大值为多少?
create table tb_test3(
a int,
b char(20),
c varchar(50),
d varchar(N)
) default charset=gbk;
第 1 个减 1 的原因是实际行存储从第 2 个字节开始。 第 2 个减 1 表示第二个 varchar(50) 头部一个 1 个字节表示长度(小于255)。 减 2 的原因是 varchar 头部的 2 个字节表示长度。 减 4 的原因是 int 类型占 4 个字节。 减 20 * 2 的原因是 char(20) 占用40个字节,编码是 gbk。 减 50 * 2 的原因是 varchar(50) 占用100个字节,编码是 gbk。
sql 测试:
create table tb_test3(
a int,
b char(20),
c varchar(50),
d varchar(32694)
) default charset=gbk;
ERROR 1118 (42000): Row size too large. The maximum row size for the used table type,
not counting BLOBs, is 65535. You have to change some columns to TEXT or BLOBs
create table tb_test3(
a int,
b char(20),
c varchar(50),
d varchar(32693)
) default charset=gbk;
Query OK, 0 rows affected (0.18 sec)
最后我们自己看一下例子:
DROP TABLE if EXISTS tb_test4;
create table tb_test4(
a int,
b char(20),
c varchar(50),
e varchar(50),
d varchar(N)
) default charset=gbk;
DROP TABLE if EXISTS tb_test4;
create table tb_test4(
a int,
b char(20),
c varchar(50),
e varchar(50),
d varchar(32644)
) default charset=gbk;
DROP TABLE if EXISTS tb_test4
> OK
> Time: 0.004s
create table tb_test4(
a int,
b char(20),
c varchar(50),
e varchar(50),
d varchar(32644)
) default charset=gbk
> 1118 - 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
> Time: 0s
1.3 varchar和char 的区别
1.3.1 定长和变长
char 表示定长,长度固定 varchar表示变长,即长度可变。 当所插入的字符串超出它们的长度时,视情况来处理,如果是严格模式,则会拒绝插入并提示错误信息,如果是宽松模式,则会截取然后插入。
如果插入的字符串长度小于定义长度时,则会以不同的方式来处理, 如 char(10),表示存储的是10个字符,无论你插入的是多少,都是10个,如果少于10个,则用空格填满。 而 varchar(10),小于10个的话,则插入多少个字符就存多少个。
varchar 怎么知道所存储字符串的长度呢?实际上,对于 varchar 字段来说,需要使用一个(如果字符串长度小于255)或两个字节(长度大于255)来存储字符串的长度。但是因为他需要有一个prefix来表示他具体bytes数是多少(因为varchar是变长的,没有这个长度值他不知道如何读取数据)。
1.3.2 存储的容量不同
对 char 来说,最多能存放的字符个数 255,和编码无关。 而 varchar 呢,最多能存放 65532 个字符。varchar 的最大有效长度由最大行大小和使用的字符集确定。整体最大长度是 65,532字节
varchar 的编码长度限制 字符类型若为 gbk,则个字符最多占 2 个字节,最大长度不能超过 32766; 字符类型若为utf8,则每个字符最多占 3 个字节,最大长度不能超过 21845。 字符类型若为utf8mb4,则每个字符最多占 4 个字节,最大长度不能超过 16383。 若定义的时候超过上述限制,则 varchar 字段会被强行转为 text 类型,并产生 warning。
行长度限制 导致实际应用中 varchar 长度限制的是一个行定义的长度。 MySQL 要求一个行的定义长度不能超过 65535。若定义的表长度超过这个值,则提示 :
ERROR 1118 (42000): Row size too large.The maximum row size for the used table type,
not counting BLOBs, is 65535. You have to change some columns to TEXT or BLOBs。
这就是说,比如创建一个表,表结构中有两个 varhcar 类型字段,那么这两个字段的总长度不能超过65535。官方说明如下:
Every table has a maximum row size of 65,535 bytes.
This maximum applies to all storage engines, but a given engine might have
additional constraints that result in a lower effective maximum row size.
具体的限制,可以参考官方文档:
表列数和行大小的限制
1.4 超过长度后的存储现象(mysql 版本 5.7.36)
1.4.1 char
CREATE TABLE tb_test1 (
id bigint(20) not null auto_increment primary key ,
name char(5) not null default ''
);
insert into tb_test1 (name) values('bamboo1'),('jessica');
SELECT * FROM tb_test1;
然后插入小于 5 个字符再执行插入语句 :
insert into tb_test1 (name) values('jack'),('allen');
SELECT * FROM tb_test1;
1.4.2 varchar
DROP TABLE IF EXISTS tb_test2;
CREATE TABLE tb_test2 (
id bigint(20) not null auto_increment primary key ,
name varchar(5) not null default ''
);
insert into tb_test2 (name) values('bamboo1'),('jessica');
insert into tb_test2 (name) values('bamboo1'),('jessica')
> 1406 - Data too long for column 'name' at row 1
> Time: 0.002s
SELECT * FROM tb_test2;
insert into tb_test2 (name) values('jack'),('allen');
insert into tb_test2 (name) values('jack'),('allen')
> Affected rows: 2
> Time: 0.002s
SELECT * FROM tb_test2;
2、int
2.1 int(1)、int (10) 有啥区别?
这 2 个声明并没有多大区别,数据表示的范围都是一样的。 int(M): M (对应于上面的 1 和 10) indicates the maximum display width for integer types.1 在 integer 数据类型中,M 表示最大显示宽度。
在 int(M) 中,M 的值跟 int(M) 所占多少存储空间并无任何关系。 int(3)、int(4)、int(8) 在磁盘上都是占用 4 btyes 的存储空间。 说白了,除了显示给用户的方式有点不同外,int(M) 跟 int 数据类型是相同的。 另外,int(M) 只有跟 zerofill 结合起来,才能使我们清楚的看到不同之处。
测试一下
mysql> drop table if exists t;
mysql> create table t(id int zerofill);
mysql> insert into t(id) values(10);
mysql> select * from t;
+
| id |
+
| 0000000010 |
+
mysql> alter table t change column id id int(3) zerofill;
mysql> select * from t;
+
| id |
+
| 010 |
+
mysql>
mysql> alter table t change column id id int(4) zerofill;
mysql> select * from t;
+
| id |
+
| 0010 |
+
mysql>
mysql> insert into t(id) values(1000000);
mysql> select * from t;
+
| id |
+
| 0010 |
| 1000000 |
+
从上面的测试可以看出,“(M)”指定了 int 型数值显示的宽度,如果字段数据类型是 int(4),则:当显示数值 10 时,在左边要补上 “00”; 当显示数值 100 是,在左边要补上“0”;当显示数值 1000000 时,已经超过了指定宽度“(4)”, 因此按原样输出。 在使用 MySQL 数据类型中的整数类型(tinyint、smallint、 mediumint、 int/integer、bigint)时,非特殊需求下,在数据类型后加个“(M)”,是没有任何意义的。
int(1)和int(11)在实际使用中,如果不使用 zerofill 是没有任何区别的,而且int型最大只能存储4294967295这个整数,我们可以发现其实只有10位。
综上所述,当我们需要用整形来存储一个字段类型的时候,应该尽量估算出该字段所需要的实际长度, 比如tinyint可存储无符号最大值是255(1个字节长度,即2的8次方), smallint可存储无符号最大值是65535(2个字节长度,即2的16次方), mediumint可存储无符号最大值是16777215(3个字节长度,即2的24次方), 而int型最大可存储4294967295(3个字节长度,即2的32次方)
2.2 unsigned 的含义
默认的 int 类型,取值范围是 -2147483648-2147483647 之间, 而 unsigned 的取值范围是 0-4294967295 之间。
默认的 int 类型,允许插入负数,unsigned 设置后,无法插入负数。
测试一下:
2.2.1 不声明 unsigned 的表
CREATE TABLE `tb_test_int` (
`id` int(10) NOT NULL AUTO_INCREMENT,
PRIMARY KEY (`id`)
);
insert into `tb_test_int` values(2147483647);
insert into `tb_test_int` values(-2147483648);
select * from tb_test_int;
insert into `tb_test_int` values(2147483648);
insert into `tb_test_int` values(-2147483649);
2.2.2 声明 unsigned 的表
DROP TABLE if EXISTS tb_test_int_un;
CREATE TABLE `tb_test_int_un` (
`id` int unsigned NOT NULL AUTO_INCREMENT,
PRIMARY KEY (`id`)
);
SHOW CREATE TABLE tb_test_int_un;
CREATE TABLE `tb_test_int_un` (
`id` int(10) unsigned NOT NULL AUTO_INCREMENT,
PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
insert into `tb_test_int_un` values(2147483647);
insert into `tb_test_int_un` values(-2147483648);
insert into `tb_test_int_un` values(-1);
select * from tb_test_int_un;
insert into `tb_test_int_un` values(2147483648);
|