本文首发自「慕课网」,想理解更多 IT 干货内容,程序员圈内热闻,欢送关注 ” 慕课网 ”!
作者:李辉 | 慕课网讲师
理解 MySQL 的数据类型是开发人员在应用 MySQL 数据库的时候,必备的根底技能之一。也正因为此,这部分常识也是面试官面试的时候每每提及的高频问题,所以尽量不要在这个中央栽跟头。
明天咱们就一起探讨下 MySQL 面试中的高频问题:在数据建模设计时应用十分频繁的字符串类型 – VARCHAR。
为了在浏览时不引起歧义,咱们先做一个简略的约定:
MySQL 数据库版本 5.7
应用 InnoDB 存储引擎
默认隔离级别是 Repeatable Read
默认应用 UTF8 编码
1. VARCHAR (50) 中的 50 到底是能存 50 个字还是 50 个字节?
咱们先做个简略的试验:
mysql> create database imooc_mysql_interview;
Query OK, 1 row affected (0.00 sec)
mysql> use imooc_mysql_interview
Database changed
mysql> create table varchar_test(col_1 varchar(8));
Query OK, 0 rows affected (0.01 sec)
mysql> insert into varchar_test values('ABCDEFGH'),('数一数是不是八个');
Query OK, 2 rows affected (0.01 sec)
Records: 2 Duplicates: 0 Warnings: 0
mysql> select * from varchar_test;
+--------------------------+
| col_1 |
+--------------------------+
| ABCDEFGH |
| 数一数是不是八个 |
+--------------------------+
2 rows in set (0.00 sec)
从上述试验的后果可知,显然是能存 8 个字符而不是 8 个字节,也就是说 VARCHAR 的括号中的数字代表的是字符。如果存的是字节,因为中文、英文和 emoji 在 utf-8 中的字节数都不一样,势必会给编程造成肯定的困扰。
1.1 CHAR (50) 和 VARCHAR (50) 有什么区别?
刚工作不久的同学可能会有这个疑难,为什么大家都喜爱用 VARCHAR,CHAR 却很少见,存的长度不是都一样吗?
首先要阐明的一点,CHAR 和 VARCHAR 在存储形式上存在着差别:CHAR 是定长符,MySQL 数据库会依据建表时定义的长度给它调配相应的存储空间。而 VARCHAR 是可变长度字符的数据类型,在存储时只应用必要的空间。
举个例子,如果一张表上有两列,别离是 CHAR (20) 和 VARCHAR (20),咱们插入一个字符串“abcd”,在数据库中存储时,CHAR 会应用全副的 20 个字符的长度,有余的局部用空格填充,而 VARCHAR 仅仅就只应用 4 个字符的长度。
其次,因为 CHAR 数据类型的这个个性,在将数据写入表中时,如果字符串尾部存在空格,会被主动删除,而 VARCHAR 数据类型会保留这个空格。在一些非凡场景中要留神这个问题。所以举荐你应用 CHAR 数据类型存储一些固定长度的字符串,比方身份证号、手机号、性别等。
最初,CHAR 和 VARCHAR 的存储长度不同。CHAR 数据类型可定义的最大长度是 255 个字符,而 VARCHAR 依据所应用的字符集不同,最大能够应用 65535 个字节。留神我刚说的 VARCHAR 的最大长度不是字符数而是字节数,那么新的问题来了,咱们接着往下看。
2. VARCHAR 能应用的最大长度是多少?
因为 VARCHAR 能存储的最大长度会因为你在表定义中应用的字符集不同而发生变化,上面咱们就以业内应用较多的 UTF8 这个字符集作为前提条件来做个剖析。
咱们再看一个例子:
mysql> create table varchar_test2(col_1 varchar(65535))charset=utf8 engine=innodb;
ERROR 1074 (42000): Column length too big for column 'col_1' (max = 21845); use BLOB or TEXT instead
mysql> create table varchar_test2(col_1 varchar(21845))charset=utf8 engine=innodb;
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
mysql> create table varchar_test2(col_1 varchar(21844))charset=utf8 engine=innodb;
Query OK, 0 rows affected (0.02 sec)
因为 UTF8 字符集中一个汉字占用 3 个字节,因而咱们能创立的最大长度实践上应该是 21845(65535/3=21845)。然而为什么 varchar (21845) 依然报错,而应用 varchar (21844) 却创立胜利?
这是因为涉及了 MySQL 数据库定义的 VARCHAR 的最大行长度限度。
尽管 MySQL 官网定义了最大行长度是 65535 个字节,然而因为还有别的开销,咱们能应用的最大行长度只有 65532。
刚刚的试验中,咱们把 VARCHAR 的字段长度改成 21844 后腾出来 3 个字节 (65535-21844*3=3),因而能够创立胜利。
因而在应用了 UTF-8 的字符集时,VARCHAR 的最大长度为 21844。
另外揭示你留神一下,做表设计时不要肆意的放飞自我,在单表上设计出一堆较大的 VARCHAR 字段,在失去了扩展性时当前可能会哭。因为 MySQL 的最大行长度限度不只是 1 个 VARCHAR 列,而是所有列的长度总和。
咱们再做个试验察看一下:
mysql> create table varchar_test3(id int auto_increment, col_2 varchar(21844), primary key(id))charset=utf8 engine=innodb;
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
mysql> create table varchar_test3(id int auto_increment, col_2 varchar(21843), primary key(id))charset=utf8 engine=innodb;
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
mysql> create table varchar_test3(id int auto_increment, col_2 varchar(21842), primary key(id))charset=utf8 engine=innodb;
Query OK, 0 rows affected (0.02 sec)
mysql> create table varchar_test4(id int auto_increment, col_2 varchar(21842), col_3 smallint, primary key(id))charset=utf8 engine=innodb;
Query OK, 0 rows affected (0.02 sec)
你能够自行计算一下,在下面的这个试验中,所有列的字节数加起来也是不能超过 65532 的,超出时会报错。
你可能还有一个纳闷,在 UTF8 中英文字符不是只占用 1 个字节吗,那 varchar (21844) 应用了 65532 个字节,如果我只存英文字符的话是不是就能存 65532 个?
然而并非如此,VARCHAR (M) 中的 M 依然示意的是 M 个字符,而不是 M 个字节。
只不过它在存储的时候依然是按理论字节数来存的。所以在 UTF8 的字符编码下,咱们能应用的最大长度就只有 21844 个 VARCHAR 字符。
如果要存储更多的字符该怎么办呢?应用 TEXT、BLOB 这样的大对象列类型。因为这些大对象能够把数据寄存到溢出页面上,也就是 DBA 们常说的行溢出。
3. VARCHAR 数据类型优化
上面咱们再聊一聊 VARCHAR 的性能优化相干的一些事件。
3.1 只调配所须要用的 VARCHAR 空间
尽管应用 VARCHAR (50) 和 VARCHAR (1000) 存储‘abcd’的存储空间开销是一样的,然而当你在读取数据时,把这些数据读取到内存的过程中,MySQL 数据库须要调配相应大小的内存空间来存放数据。
所以更大的 VARCHAR 列在读取时要应用更大的内存空间,即便它实际上只存储了一丁点数据。
并且在操作这个表的过程中,如果遇到一些聚合(GROUP BY)或排序(ORDER BY)的操作,须要调用内存长期表或磁盘长期表时,性能会更加蹩脚。
因而,在保留肯定冗余的前提下,只给 VARCHAR 调配恰到好处的空间应用。
3.2 VARCHAR 的字段过长也会导致行溢出
刚刚你不是说了 TEXT 和 BLOB 会溢出吗,VARCHAR 也会溢出?
是的。你在给 MySQL 的数据表加索引时,可能遇到过要在大的 VARCHAR 字段上创立索引却发现只能创立前缀索引的问题。那这个其实是和行溢出无关。
mysql> create index idx_col_2 on varchar_test4(col_2);
ERROR 1071 (42000): Specified key was too long; max key length is 3072 bytes
mysql> create index idx_col_2 on varchar_test4(col_2(3072));
ERROR 1071 (42000): Specified key was too long; max key length is 3072 bytes
mysql> create index idx_col_2 on varchar_test4(col_2(1024));
Query OK, 0 rows affected (0.02 sec)
Records: 0 Duplicates: 0 Warnings: 0
mysql> drop index idx_col_2 on varchar_test4;
Query OK, 0 rows affected (0.01 sec)
Records: 0 Duplicates: 0 Warnings: 0
mysql> create index idx_col_2 on varchar_test4(col_2(1025));
ERROR 1071 (42000): Specified key was too long; max key length is 3072 bytes
下面试验能够看到,最大的索引长度不能超过 3072 字节(在 UTF-8 的字符集中对应的是 1024 个字符)。
因为行溢出是另外一个话题,咱们明天就不过多赘述,咱们只说说大字段和行溢出造成的性能问题。
- 大字段会占用较大的内存,使得 MySQL 内存利用率较差
- 行溢出的数据在读取时须要多一个 IO,造成 IO 效率降落
- 行溢出会应用 uncompress BLOB page,造成 InnoDB 的表空间越来越大
- InnoDB 中的大字段在做更新和删除操作时,只能进行乐观操作,这会造成并发性能降落。
另外,因为 InnoDB 的数据页默认是 16K,每个页中至多寄存 2 行数据,因而倡议 VARCHAR 字段的总长度不要超过 8K。
4. 小结
明天我给你介绍了 MySQL 的 VARCHAR 数据类型,心愿你对 VARCHAR 数据类型能有更多的理解。
因为篇幅的限度,诸如行溢出这些知识点就不再做深刻的探讨。
不过对于开发人员来说,理解到这里也就根本足够了。如果你对没有开展的那些知识点感兴趣,能够自行搜寻钻研一番。
最初留一个问题吧,应用 UTF8 字符集时咱们最大能够存储 21844 个字符,那么如果是 UTF8MB4 呢?
欢送关注「慕课网」帐号,咱们会始终保持内容原创,提供 IT 圈优质内容,分享干货常识,大家一起独特成长吧!
本文原创公布于慕课网,转载请注明出处,谢谢合作