首页 > 数据库 >[ MySQL开发高频面试]VARCHAR(50)中的50到底是能存50个字还是50个字节?

[ MySQL开发高频面试]VARCHAR(50)中的50到底是能存50个字还是50个字节?

时间:2023-04-28 11:10:27浏览次数:53  
标签:varchar create MySQL 50 能存 mysql VARCHAR col

本文首发自「慕课网」,想了解更多IT干货内容,程序员圈内热闻,欢迎关注"慕课网"!

作者:李辉 | 慕课网讲师


了解 MySQL 的数据类型是开发人员在使用 MySQL 数据库的时候,必备的基础技能之一。也正因为此,这部分知识也是面试官面试的时候屡屡提及的高频问题,所以尽量不要在这个地方栽跟头。

今天我们就一起探讨下MySQL面试中的高频问题:在数据建模设计时使用非常频繁的字符串类型 – VARCHAR

为了在阅读时不引起歧义,我们先做一个简单的约定:

  1. MySQL 数据库版本 5.7
  2. 使用 InnoDB 存储引擎
  3. 默认隔离级别是 Repeatable Read
  4. 默认使用 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 个字符)。

由于行溢出是另外一个话题,我们今天就不过多赘述,我们只说说大字段和行溢出造成的性能问题。

  1. 大字段会占用较大的内存,使得 MySQL 内存利用率较差
  2. 行溢出的数据在读取时需要多一个 IO,造成 IO 效率下降
  3. 行溢出会使用 uncompress BLOB page,造成 InnoDB 的表空间越来越大
  4. InnoDB 中的大字段在做更新和删除操作时,只能进行悲观操作,这会造成并发性能下降。

另外,因为 InnoDB 的数据页默认是 16K,每个页中至少存放 2 行数据,因此建议 VARCHAR 字段的总长度不要超过 8K。

4. 小结

今天我给你介绍了 MySQL 的 VARCHAR 数据类型,希望你对 VARCHAR 数据类型能有更多的了解。

由于篇幅的限制,诸如行溢出这些知识点就不再做深入的探讨。

不过对于开发人员来说,了解到这里也就基本足够了。如果你对没有展开的那些知识点感兴趣,可以自行搜索研究一番。

最后留一个问题吧:使用 UTF8 字符集时我们最大可以存储 21844 个字符,那么如果是 UTF8MB4 呢?欢迎大家在评论区留言探讨。


欢迎关注「慕课网」帐号,我们会一直坚持内容原创,提供IT圈优质内容,分享干货知识,大家一起共同成长吧!

本文原创发布于慕课网 ,转载请注明出处,谢谢合作

标签:varchar,create,MySQL,50,能存,mysql,VARCHAR,col
From: https://blog.51cto.com/u_15771948/6233556

相关文章

  • NC50428 数星星 Stars
    题目链接题目题目描述天空中有一些星星,这些星星都在不同的位置,每个星星有个坐标。如果一个星星的左下方(包含正左和正下)有k颗星星,就说这颗星星是k级的。例如,上图中星星5是3级的(1,2,4在它左下),星星2,4是1级的。例图中有1个0级,2个1级,1个2级,1个3级的星星。给定星星的位置,输出各级......
  • SQLServer2005 AMD8450,3核CPU装不上sql 2005的解决办法
    中午12点开始,安装SQLServer2005,一直到晚上9点半,把网上的各个文章翻了个遍,依然没有安装上我的SQLServer2005,安装不上的症状跟网上其它人遇到的一样,可是为什么别人的就解决了,我的就不行呢```带着郁闷的心情睡觉了```夜里3点几分,起夜,想到数据库还......
  • CF1814E Chain Chips & CF750E New Year and Old Subsequence - 动态 dp -
    一句话概括动态dp:用来解决带修改/多次区间询问的dp问题。将转移写成矩阵的形式,然后利用线段树求解区间问题/单点修改1814E注意一条边要么选2要么选0次,而且第一条边一定是选了2次。如果有一条边没选,那么这条边两侧的边一定都选了。设\(f_i\)代表考虑到第\(i\)条边,......
  • NC50454 A Simple Problem with Integers
    题目链接题目题目描述给定数列\(a[1],a[2],\dots,a[n]\),你需要依次进行q个操作,操作有两类:1lrx:给定l,r,x,对于所有\(i\in[l,r]\),将a[i]加上x(换言之,将\(a[l],a[l+1],\dots,a[r]\)分别加上x);2lr:给定l,r,求\(\sum_{i=l}^ra[i]\)的值(换言之,求\(a[l]+a[l+1]+\dots+a......
  • CS50363内置MOS可升压16V,高效率升压DC-DC转换器
    CS50363E是一款采用CMOS工艺升压型开关稳压器,其主要包括一个参考电压源,一个振荡电路,一个误差放大器,一个相位补偿电路,通过PWM/PFM切换控制电路。CS50363E内置MOS的设计,只需极简的外围电路,可以最大限度的保证电源模块的可靠性以及避免电源模块设计的复杂化。CS50363E最高可提供16V恒......
  • nginx出现504错误的原因分析及解决
    线上环境使用的是nginx代理到后端应用(java),对应用进行了一次压测发现nginx出现大量的504代码,即网关超时(GatewayTime-out)错误。 原因分析:首先504是网关超时错误,通常是nginx将请求代理到后端应用时,后端应用没有在规定的时间返回数据,需要开发检查下应用那块有什么耗时的操作,比如:......
  • 代码随想录Day38-Leetcode509. 斐波那契数,70. 爬楼梯,746. 使用最小花费爬楼梯
    咳咳,因为找实习+摆导致时间被浪费大半;先从动态规划学起吧,之前的慢慢补。理论基础动态规划的解题步骤1.确定dp数组及对应下标的含义2.确定dp的状态转移方程(递推公式)3.确定dp数组如何初始化4.确定dp遍历顺序5.距离推导dp数组验证509.斐波那契数题目链接:https://le......
  • 年薪50万开发者相亲失败:程序员,别输在不会说话上
    关注“Java后端技术全栈”回复“000”获取大量电子书Ⅰ越来越不会说话的程序员“编程能力很赞,但沟通能力可能有所欠缺”——很多人这么形容程序员。其实,除了他人这么评价,程序员本身也说自己越来越不会说话了。看看一些网友的吐槽:以前还挺会说的。追老婆那会,和她一起走回家,从校门口,......
  • EBS: APP-PER-50022: ORACLE 人力资源管理系统无法检索用户类型配置文件选项的值
    在PO模块设置的人员,点击“人员”功能菜单时,提示: APP-PER-50022。。。。。的错误。路径:PO》》设置》》人员。APP-PER-50022:ORACLE人力资源管理系统无法检索用户类型配置文件选项的值。请确保为您的责任正确设置此值。尝试解决方式(失败了):  PROFILE |     ......
  • leetcode-350-两个数组的交集 II 题解
    题目给定两个数组,编写一个函数来计算它们的交集。示例1:输入:nums1=[1,2,2,1],nums2=[2,2]输出:[2,2]示例2:输入:nums1=[4,9,5],nums2=[9,4,9,8,4]输出:[4,9]说明:输出结果中每个元素出现的次数,应与元素在两个数组中出现次数的最小值一致。我们可以不考虑输出结果......