MYSQL的CHAR和VARCHAR注意事项 以及binary和varbinary存储方式

本文涉及的产品
RDS MySQL Serverless 基础系列,0.5-2RCU 50GB
云数据库 RDS MySQL,集群系列 2核4GB
推荐场景:
搭建个人博客
云数据库 RDS MySQL,高可用系列 2核4GB
简介: 版本使用5.7 存储引擎INNODB 行格式Dynamic 从概念上讲他们用于存放字符型数据,其允许的范围: 1、char 0-255 bytes,固定长度,不够的长度用ASCII 0X20进行补足。
版本使用5.7 存储引擎INNODB 行格式Dynamic

从概念上讲他们用于存放字符型数据,其允许的范围:
1、char 0-255 bytes,固定长度,不够的长度用ASCII 0X20进行补足。
2、varchar 0-65535(2^8-1)bytes,注意是整个表所有的varchar字段的长度,所谓 可变长度,就是按需分配空间。

下面就几个问题进行讨论:

第一个问题:varchar的可变长度
      那么这里引出了第一个问题,可变长度,在INNODB(Dynamic 行格式),在行头使用,1-2个字节
来存储这个可变长度及:
 variable field lengths  (1-2 bytes* var )
(具体参考http://blog.itpub.net/7728585/viewspace-2071787/)
2个字节也刚好是65535的长度,这是INNODB对MYSQL的一个实现方法,同时如果使用5.7 INNODB 
online  DDL进行modify varchar column的长度,在1-255 和 256-65535之间都可以迅速完成,但是
如果跨越比如改变一个varchar 字段的长度从250 到 300 注意是字节,就会出现需要使用
inpace或者copy等方法,那就非常慢了,原因也在这里因为涉及到行头的扩张了,以前是一
个字节来存储长度,而改变后需要二个字节,当然也就需要重新组织表,而如果不跨越就不
会改变表的组织方式,也就值需要修改数据字典和frm文件而已,当然瞬间完成,下面来做
一个测试。对于UTF8字符集,它的这个点是255/3=85。
注意使用版本5.7 引擎为innodb 行格式为Dynamic,并且这一列不能有索引,如果有索引
索引会带入而外的操作,也是比较慢的
mysql> select count(*) from testshared3;
+----------+
| count(*) |
+----------+
|  1048576 |
+----------+
1 row in set (0.35 sec)

mysql> show create table testshared3;
+-------------+----------------------------------------------------------------------------------------------------------------------------------+
| Table       | Create Table                                                                                                                     |
+-------------+----------------------------------------------------------------------------------------------------------------------------------+
| testshared3 | CREATE TABLE `testshared3` (
  `id` int(11) DEFAULT NULL,
  `name` varchar(13) DEFAULT NULL
) ENGINE=InnoDB DEFAULT CHARSET=utf8 |
+-------------+----------------------------------------------------------------------------------------------------------------------------------+
1 row in set (0.01 sec)

mysql> alter table testshared3  change  name name varchar(85) ;
Query OK, 0 rows affected (0.07 sec)
Records: 0  Duplicates: 0  Warnings: 0

可以看到虽然有1048576行的数据但是modify还是瞬间完成了。但是如果从85改到86如何呢?
mysql> alter table testshared3 ALGORITHM=INPLACE, change  name name varchar(86) ;
ERROR 1846 (0A000): ALGORITHM=INPLACE is not supported. Reason: Cannot change column type INPLACE. Try ALGORITHM=COPY.
mysql> alter table testshared3  change  name name varchar(86) ;
Query OK, 1048576 rows affected (15.68 sec)
Records: 1048576  Duplicates: 0  Warnings: 0
可以看到使用了15秒多,而且ALGORITHM=COPY。

第二个问题:关于char和varchar 左空格存储以及显示的不同

mysql> create table testvc(name1 varchar(5),name2 char(5));
Query OK, 0 rows affected (0.08 sec)

mysql> insert into testvc values('gao ','gao ');
Query OK, 1 row affected (0.01 sec)
mysql> select concat(name1,')'),concat(name2,')') from  testvc;
+-------------------+-------------------+
| concat(name1,')') | concat(name2,')') |
+-------------------+-------------------+
| gao )             | gao)              |
+-------------------+-------------------+
1 row in set (0.06 sec)
可以看到varchar可以正常显示gao后面的空格,而char却不行,那么他们内部到底是如何存储的,我们需要
用二进制方式查看一下:
(下面是我解析好的,具体的方法参考http://blog.itpub.net/7728585/viewspace-2071787/需要用到我自己
写的几个小工具)
04           --varchar 长度
00           --NULL位图
00
0010
0024
00000089a25
40000002e0c1bd9
000001640110
67616f20      --varchar(5)  'gao '
67616f2020  --char(5)       'gao '


这里我们可以明显看到varchar的长度为4,当存储varchar的'gao '的时候存储的是0X67616f20也就是ASCII的'gao ',当存储char类型的'gao '
的时候为0X67616f2020,可以看到他后面有两个0X20,也就是ASCII的空格那么我们可以知道char(5)会对不够的字节全部补上ASCII 0X20,这也就是
为什么输出的时候空格不在了,因为了char字段中存储的时候尾部的0X20作为了补足的字符,而VARCHAR中却不是这样0X20作为了实际的字符,也就
是空格那么输出就有了。

第三个问题:比较和varchar以及char尾部的空格。
在MYSQL文档中描述:
This means that all CHAR, VARCHAR, and TEXT values in MySQL are compared without regard to any trailing spaces
 “Comparison”in this context does not include the LIKEpattern-matching operator
For those cases where trailing pad characters are stripped or comparisons ignore them, if a column has
an index that requires unique values, inserting into the column values that differ only in number of trailing
pad characters will result in a duplicate-key error. 

也就是除了LIKE的比较操作,都会忽略尾部空格不管是VARCHAR CHAR 还是TEXT,并且如果字段是唯一键,唯一性判断的时候
也会忽略空格。

还是刚才的表我们在varchar 的name1上加上唯一索引。
mysql> alter table testvc add unique key(name1);
Query OK, 0 rows affected (0.07 sec)
Records: 0  Duplicates: 0  Warnings: 0


我们刚才插入的数据为
insert into testvc values('gao ','gao ');

mysql> select * from testvc  where name1='gao ';
+-------+-------+
| name1 | name2 |
+-------+-------+
| gao   | gao   |
+-------+-------+
1 row in set (0.00 sec)

mysql> select * from testvc  where name1='gao';
+-------+-------+
| name1 | name2 |
+-------+-------+
| gao   | gao   |
+-------+-------+
1 row in set (0.00 sec)

可以看到不管存储有没有0X20空格,也不管条件=后面是否有0X20空格
都会查询出来,我们来测试一下插入

mysql> insert into testvc values('gao','gao10');
ERROR 1062 (23000): Duplicate entry 'gao' for key 'name1'
mysql> insert into testvc values('gao  ','gao10');
ERROR 1062 (23000): Duplicate entry 'gao  ' for key 'name1'

不管我插入的是'gao'还是'gao  '都是重复的值,证明的文档的说法,另外
这个问题在ORACLE中是不存在,MYSQL也比较奇怪。很多ORACLE的概念在MYSQL
中需要打一个问号。
ORACLE:
SQL> create table testui1(name varchar2(20));
Table created
SQL> create unique index testuiq_IDX on testui1(name);
Index created
SQL> insert into testui1 values('gao');
1 row inserted
SQL> insert into testui1 values('gao ');
1 row inserted
SQL> insert into testui1 values('gao  ');
1 row inserted
SQL> commit;
Commit complete

接下来看看LIKE:
varchar:
mysql> select * from testvc  where name1 like 'gao %';
+-------+-------+
| name1 | name2 |
+-------+-------+
| gao   | gao   |
+-------+-------+
1 row in set (0.00 sec)

mysql> select * from testvc  where name1='gao  ';
+-------+-------+
| name1 | name2 |
+-------+-------+
| gao   | gao   |
+-------+-------+
1 row in set (0.00 sec)

char:
mysql> select * from testvc  where name2 like 'gao %';
Empty set (0.00 sec)

mysql>  select * from testvc  where name2='gao  ';
+-------+-------+
| name1 | name2 |
+-------+-------+
| gao   | gao   |
+-------+-------+
1 row in set (0.00 sec)

这里 char name2 like 'gao %' 没有出来数据,而varchar name1 like 'gao %'
出来了数据也正是证明了我们对存储格式的剖析,因为char对尾部的0X20空格在
存储的时候已经去掉了,但是VARCHAR没有,只要LIKE是严格匹配就会出来这样的
效果。

最后来看看MYSQL的binary和varbinary格式,这种格式就是说明其存储和比较都使用二进制格式,也就是按照一个
字节一个字节的比较ASCII值,官方描述如下:
they contain byte strings rather than character strings. This
means that they have no character set, and sorting and comparison are based on the numeric values of
the bytes in the values.
他们的意义和char和varchar相似,但是有一点,其比较方法和存储方法不太一样
binary 使用0X00也就是\0补足不够的字节,而其比较也是严格和存储中的格式进行
匹配不存在char和varchar那样对空格的处理


mysql> insert into testbin3 values('a ','a ');
Query OK, 1 row affected (0.03 sec)


mysql> desc testbin3;
+-------+---------------+------+-----+---------+-------+
| Field | Type          | Null | Key | Default | Extra |
+-------+---------------+------+-----+---------+-------+
| name1 | varbinary(10) | YES  |     | NULL    |       |
| name2 | binary(10)    | YES  |     | NULL    |       |
+-------+---------------+------+-----+---------+-------+
2 rows in set (0.00 sec)


mysql> select * from testbin3 where name1='a ';
+-------+------------+
| name1 | name2      |
+-------+------------+
| a     | a          |
+-------+------------+
1 row in set (0.00 sec)


mysql> select * from testbin3 where name2='a ';
Empty set (0.00 sec)


mysql> select * from testbin3 where name2='a \0\0\0\0\0\0\0\0';
+-------+------------+
| name1 | name2      |
+-------+------------+
| a     | a          |
+-------+------------+
1 row in set (0.00 sec)


可以看到varbinary使用'a '可以查询到记录但是binary使用'a '不能查到,为什么呢?
我们看看他的内部存储
00000089a25f
0000002e0c66bc
0000012a0110


6120                --binary    'a '
612000000000000000  --varbinary 'a '


可以看到varbinary使用8个0X00进行补足,既然他严格按照而进行进行匹配那么我们这样可以
查出数据:


mysql> select * from testbin3 where name2='a \0\0\0\0\0\0\0\0';
+-------+------------+
| name1 | name2      |
+-------+------------+
| a     | a          |
+-------+------------+
1 row in set (0.00 sec)
当然unique也是严格按照而进行进行比较
增加一个unique key 在binary 上
mysql> alter table testbin3 add unique key(name2);
mysql> insert into testbin3 values('a ','a \0\0\0\0\0\0\0\0');
ERROR 1062 (23000): Duplicate entry 'a ' for key 'name2'
可以看到重复的行

相关实践学习
如何在云端创建MySQL数据库
开始实验后,系统会自动创建一台自建MySQL的 源数据库 ECS 实例和一台 目标数据库 RDS。
全面了解阿里云能为你做什么
阿里云在全球各地部署高效节能的绿色数据中心,利用清洁计算为万物互联的新世界提供源源不断的能源动力,目前开服的区域包括中国(华北、华东、华南、香港)、新加坡、美国(美东、美西)、欧洲、中东、澳大利亚、日本。目前阿里云的产品涵盖弹性计算、数据库、存储与CDN、分析与搜索、云通信、网络、管理与监控、应用服务、互联网中间件、移动服务、视频服务等。通过本课程,来了解阿里云能够为你的业务带来哪些帮助     相关的阿里云产品:云服务器ECS 云服务器 ECS(Elastic Compute Service)是一种弹性可伸缩的计算服务,助您降低 IT 成本,提升运维效率,使您更专注于核心业务创新。产品详情: https://www.aliyun.com/product/ecs
相关文章
|
1月前
|
存储 关系型数据库 MySQL
MySQL 字段类型探究:深入理解 Varchar(50) 与 Varchar(500)
在MySQL数据库中,`VARCHAR`类型是一种常用的字符串存储类型,它允许定义一个可变长度的字符串。然而,`VARCHAR(50)`和`VARCHAR(500)`之间的差异不仅仅是长度的不同,它们在存储和性能方面也有显著的区别。本文将深入探讨这两种字段类型的区别,以及它们在实际应用中的选择。
98 3
|
1月前
|
存储 关系型数据库 MySQL
MySQL 字段类型深度解析:VARCHAR(50) 与 VARCHAR(500) 的差异
在MySQL数据库中,`VARCHAR`类型是一种非常灵活的字符串存储类型,它允许存储可变长度的字符串。然而,`VARCHAR(50)`和`VARCHAR(500)`之间的差异不仅仅是长度的不同,它们在存储效率、性能和使用场景上也有所不同。本文将深入探讨这两种字段类型的区别及其对数据库设计的影响。
45 2
|
4月前
|
存储 数据管理 数据库
|
4月前
|
存储 关系型数据库 MySQL
在 MySQL 中使用 CHAR
【8月更文挑战第11天】
88 0
|
7月前
|
存储 关系型数据库 MySQL
MySQL字段的字符类型该如何选择?千万数据下varchar和char性能竟然相差30%🚀
本篇文章来讨论MySQL字段的字符类型选择并深入实践char与varchar类型的区别以及在千万数据下的性能测试
MySQL字段的字符类型该如何选择?千万数据下varchar和char性能竟然相差30%🚀
|
7月前
|
存储 关系型数据库 MySQL
|
7月前
|
存储 数据库
5. CHAR和VARCHAR的区别?
`CHAR`和`VARCHAR`在数据库中有所不同:`CHAR`长度固定,用空格填充,存储效率高,英文占1字节,汉字占2字节;而`VARCHAR`长度可变,节省空间,英文和汉字都占2字节。
86 0
|
8天前
|
关系型数据库 MySQL 数据库
Python处理数据库:MySQL与SQLite详解 | python小知识
本文详细介绍了如何使用Python操作MySQL和SQLite数据库,包括安装必要的库、连接数据库、执行增删改查等基本操作,适合初学者快速上手。
71 15
|
2天前
|
SQL 关系型数据库 MySQL
数据库数据恢复—Mysql数据库表记录丢失的数据恢复方案
Mysql数据库故障: Mysql数据库表记录丢失。 Mysql数据库故障表现: 1、Mysql数据库表中无任何数据或只有部分数据。 2、客户端无法查询到完整的信息。
|
9天前
|
关系型数据库 MySQL 数据库
数据库数据恢复—MYSQL数据库文件损坏的数据恢复案例
mysql数据库文件ibdata1、MYI、MYD损坏。 故障表现:1、数据库无法进行查询等操作;2、使用mysqlcheck和myisamchk无法修复数据库。