前言
这个标题起的过于俗套,前一段时间我还写了一篇总结《Mysql5.7版本中数据表字段可用的类型》来批判这种对比,原因是对比时没有指明数据库,内容写的是char
、varchar
和nvarchar
的对比,结果我测试了半天发现Mysql当前版本根本没有nvarchar
,浪费来了不少时间。
问题起因
真香定律来的总是这么快,这才过了几天,我也来写写Mysql中char
和varchar
究竟有什么区别,起因是看到CSDN好友“铁柱同学”一篇关于innodb主键长度最大为767字节的讲解,里面涉及到一个char
类型最大存储255个字符,按照utf8编码来看最大的字节数应该是255*3=765个字节的知识点。现在来看767的来源好像并不是256*3-1,而是255*3+2,这个2就是存储char
类型字段中实际有多少个字节的。
有点跑题了,实际上是在研究索引长度的过程中,我发现我对char
和varchar
这两个类型一直存在着误解,因为一直做游戏开发的缘故,游戏数据的存储一般使用varbinary
来存,导致我把字符和字节有点弄混了,所以我一直认为在utf8编码下char(9)
可以存储9个英文字符,或者3个中文汉字,实际我做完实验后发现char(9)
也可以正常存储9个汉字。
提到字符和字节,初学者可能会有点蒙,实际上它们两者之间是需要通过编码来转换的,之前做过游戏的多语言版本,所以对这一块还是比较熟的,字节就是计算机中的8个二进制位,而字符是每个语言中的不可分割的单元,字符转换成字节需要依赖编码,实际上编码就是一本大字典,里面对应了每个字符在当前编码下转换成字节是什么样的,ANSI是一本字典,UTF8也是一本字典,编码的类型还有很多,每种编码都记录了各自的转换结果。
举个例子,“中”这个字是汉字中的一个字符,在ANSI这本字典中对应的是2个字节,而在UTF8这本字典中对应的是3个字节,而C
这个字母是英文中的一个字符,在ANSI这本字典中对应的是1个字节,而在UTF8这本字典中对应的同样是1个字节,从这个例子中可以简单理解下字节与字符的关系。
多说一句,不要认为UTF8编码中汉字转换成字节都是3个字节,实际情况是常用字一般都占用了3个字节,但是中国语言博大精深,光语言平面就单独占了好几个,有些汉字转换成UTF8编码可能需要4个字节,5个字节甚至是6个字节,这一点不要形成思维定式,认为汉字在UTF8编码下都是3个字节。
length 和 char_length
今天之前我是不知道Mysql还有一个char_length
函数的,发现这个函数后越发感觉Mysql的强大,这两个函数的区别就是length
用来统计字段中的字节数,char_length
用来统计字段中的字符数,接下来我们用一个例子来看看这两个函数以及char
、varchar
的区别。
测试环境
Windows 10
Welcome to the MySQL monitor. Commands end with ; or \g.
Your MySQL connection id is 10
Server version: 5.7.21-log MySQL Community Server (GPL)
Copyright (c) 2000, 2018, Oracle and/or its affiliates. All rights reserved.
Oracle is a registered trademark of Oracle Corporation and/or its affiliates. Other names may be trademarks of their respective owners.
Type ‘help;’ or ‘\h’ for help. Type ‘\c’ to clear the current input statement.
测试过程
首先创建一个带有
char
和varchar
类型的测试表,查看表结构发现编码为utf81
2
3
4
5
6
7
8
9
10
11
12
13
14mysql> create table diff(id int, s1 char(10), s2 varchar(10));
Query OK, 0 rows affected (0.07 sec)
mysql> show create table diff;
+-------+------------------------------------------------------------------------+
| Table | Create Table |
+-------+------------------------------------------------------------------------+
| diff | CREATE TABLE `diff` (
`id` int(11) DEFAULT NULL,
`s1` char(10) DEFAULT NULL,
`s2` varchar(10) DEFAULT NULL
) ENGINE=InnoDB DEFAULT CHARSET=utf8 |
+-------+------------------------------------------------------------------------+
1 row in set (0.07 sec)插入少于10个字符的测试数据,然后查看结果,发现字节数为10,字符数为6
1
2
3
4
5
6
7
8
9
10mysql> insert into diff values(1, "测试test", "测试test");
Query OK, 1 row affected (0.01 sec)
mysql> select id, s1, length(s1), char_length(s1), s2, length(s2), char_length(s2) from diff;
+----+----------+------------+-----------------+----------+------------+-----------------+
| id | s1 | length(s1) | char_length(s1) | s2 | length(s2) | char_length(s2) |
+----+----------+------------+-----------------+----------+------------+-----------------+
| 1 | 测试test | 10 | 6 | 测试test | 10 | 6 |
+----+----------+------------+-----------------+----------+------------+-----------------+
1 row in set (0.06 sec)增加插入长度后发现,可以超过10个字节,可以完整存储10个汉字
1
2
3
4
5
6
7
8
9
10
11mysql> insert into diff values(2, "测试策划和开发做游戏", "测试策划和开发做游戏");
Query OK, 1 row affected (0.01 sec)
mysql> select id, s1, length(s1), char_length(s1), s2, length(s2), char_length(s2) from diff;
+----+----------------------+------------+-----------------+----------------------+------------+-----------------+
| id | s1 | length(s1) | char_length(s1) | s2 | length(s2) | char_length(s2) |
+----+----------------------+------------+-----------------+----------------------+------------+-----------------+
| 1 | 测试test | 10 | 6 | 测试test | 10 | 6 |
| 2 | 测试策划和开发做游戏 | 30 | 10 | 测试策划和开发做游戏 | 30 | 10 |
+----+----------------------+------------+-----------------+----------------------+------------+-----------------+
2 rows in set (0.10 sec)分别增加s1和s2字段长度后发现,均无法正常插入,Mysql给出报错信息
1
2
3
4
5mysql> insert into diff values(2, "测试策划和开发做游戏OK", "测试策划和开发做游戏");
1406 - Data too long for column 's1' at row 1
mysql> insert into diff values(2, "测试策划和开发做游戏", "测试策划和开发做游戏OK");
1406 - Data too long for column 's2' at row 1
mysql>至此没有看出区别,在插入内容前后都加上空格测试一下
1
2
3
4
5
6
7
8
9mysql> select id, s1, concat('#', s1, '$'), length(s1) as len_s1, char_length(s1) as clen_s1,
s2, concat('#', s2, '$'), length(s2) as len_s2, char_length(s2) as clen_s2 from diff;
+----+----------------------+------------------------+--------+---------+----------------------+------------------------+--------+---------+
| id | s1 | concat('#', s1, '$') | len_s1 | clen_s1 | s2 | concat('#', s2, '$') | len_s2 | clen_s2 |
+----+----------------------+------------------------+--------+---------+----------------------+------------------------+--------+---------+
| 1 | 测试test | #测试test$ | 10 | 6 | 测试test | #测试test$ | 10 | 6 |
| 2 | 测试策划和开发做游戏 | #测试策划和开发做游戏$ | 30 | 10 | 测试策划和开发做游戏 | #测试策划和开发做游戏$ | 30 | 10 |
| 3 | OK | # OK$ | 3 | 3 | OK | # OK $ | 4 | 4 |
+----+----------------------+------------------------+--------+---------+----------------------+------------------------+--------+---------+
这一次出现了区别,char
类型的字段去掉了尾部的空格,而varcahr
了类型的字段原样存储,没有去掉尾部空格,两者对于头部的空格都是存储的,这导致两者显示的字节数和字符数都不相同了。
- 分别使用不带空格、带头部空格,头尾都带空格进行测试
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37mysql> select * from diff where s1 = 'OK ';
Empty set
mysql> select * from diff where s2 = 'OK ';
Empty set
mysql> select * from diff where s1 = ' OK ';
+----+-----+------+
| id | s1 | s2 |
+----+-----+------+
| 3 | OK | OK |
+----+-----+------+
1 row in set (0.05 sec)
mysql> select * from diff where s2 = ' OK ';
+----+-----+------+
| id | s1 | s2 |
+----+-----+------+
| 3 | OK | OK |
+----+-----+------+
1 row in set (0.05 sec)
mysql> select * from diff where s1 = ' OK';
+----+-----+------+
| id | s1 | s2 |
+----+-----+------+
| 3 | OK | OK |
+----+-----+------+
1 row in set (0.05 sec)
mysql> select * from diff where s2 = ' OK';
+----+-----+------+
| id | s1 | s2 |
+----+-----+------+
| 3 | OK | OK |
+----+-----+------+
1 row in set (0.04 sec)
测试结果可能让人出乎意料,虽然s1和s2中存储的内容不同(差一个空格),但是查找时的行为却完全一样,这说明查找时尾部的空格并不会被考虑。
char和varchar区别
做了半天试验发现char和varchar还是没有多大区别,实际上有些区别通过表面数据是测试不出来的,具体区别整理如下:
行为 | char 字段 |
varchar 字段 |
---|---|---|
最大长度 | 255字符 | 65535个字节,所以括号中最大的字符数还得通过编码来算 |
是否定长 | 定长,不足的部分用隐藏空格填充 | 不定长 |
空间使用 | 会有浪费 | 更加节省 |
查找效率 | 高 | 低 |
尾部空格 | 插入时省略 | 插入时不会省略,查找时省略 |
like查找 | 语句中like后的’ ‘不会省 | 语句中like后的’ ‘不会省,字段结尾的空格也不会省 |
总结
char(n)
中的n是字符数,范围是0~255(额外需要1到2个字节来存长度)varchar(n)
中的n也是字符数,但是最大值需要通过编码来算,不能超过65535字节(从中还需要拿出1到2个字节来存长度)- 一般定长的数据选用
char
类型,比如身份证号,手机号,电话等,长度变化很大的可以使用varchar
类型 - 注意尾部空格的匹配,特别是插入时和使用like查找时