MySQL 数据表优化设计(三):CHAR 和 VARCHAR 怎么选?
VARCHAR 和 CHAR 是两种主要的字符串类型,用于存储字符。不幸的是,由于实现的方式依赖于存储引擎,因此很难解释这些字符串在磁盘和内存中如何存储,除了除了常用的 InnoDB 和 MyISAM 外,假设你使用了其他存储引擎,应当仔细阅读存储引擎的文档。
VARCHAR 存储可变长度的字符串,也是最常用的字符数据类型。相比固定长度的类型,VARCHAR 所需的存储空间更小,它会尽可能少地使用存储空间(例如,短的字符串占据的空间)。对于 MyISAM 来说,如果创建表的时候指定了 ROW_FORMAT=FIXED 的话,那么会使用固定的空间存储字段而导致空间浪费。VARCHAR 使用1-2个额外的字节存储字符串的长度:当最大长度低于255字节的时候使用1个字节,如果更多的话就使用2个字节。因此,拉丁字符集的 VARCHAr(10)会使用11个字节的存储空间,而 VARCHAr(1000)则会使用1002个字节的存储空间。
VARCHAR 由于能够节省空间,因此可以改善性能。但是,由于长度可变,当更新数据表的时候数据行的存储空间会变化,这一定程度上会带来额外的开销。如果数据行的长度导致原有的存储位置无法存放,那么不同的存储引擎会做不同的处理。例如 MyISAM 可能产生数据行的碎片,而 InnoDB 需要进行磁盘分页来存放更新后的数据行。
通常,如果最大的列长度远远高于平均长度的话(例如可选的备注字段),使用 VARCHAR 是划算的,同时如果更新的频次很低,那么碎片化也不会是一个问题。需要注意的是,如果使用的是 UTF-8字符集,则实际存储的字节长度是根据字符定的。对于中文,推荐的存储字符集是 utf8mb4。
CHAR 类型的长度是固定的,MySQL 会对每个字段分配足够的存储空间。 存储CHAR 类型值的时候,MySQL 会移除后面多出来的空字符 。值是使用空字符进行对齐以便进行比较。对于短的字符串来说,使用 CHAR 更有优势,而如果所有的值的长度几乎一致的话,就可以使用 CHAR。例如存储用户密码的MD5值时使用 CHAR 就更合适,这是因为 MD5的长度总是固定的。同时,对于字段值经常改变的数据类型来说,CHAR 相比 VARCHAR 也更有优势,因为 CHAR 不会产生碎片。对于很短的数据列,使用 CHAR 比 VARCHAR更高效,例如使用CHAr(1)存储逻辑值的 Y 和 N,这种情况下只需要1个字节,而 VARCHAR 需要2个字节。
对于移除空字符这个特性会感觉奇怪,我们举个例子:
按上面的结果插入数据表后,string2中的前置空格不会移除,但使用 CHAR 类型存储时,string3尾随空格会被移除,使用 SQL 查询结果来检验一下:
得出来的结果如下,可以看到 CHAR 类型的 string3后面的空格被移除了,而 VARCHAR类型的没有。这种情况大多数时候不会有什么问题,实际在应用中也经常会使用 trim 函数移除两端的空字符,但是如果确实需要存储空格的时候,那就需要注意不要选择使用 CHAR 类型:
数据如何存储是由存储引擎决定的,而且存储引擎处理固定长度和可变长度的数据的方式并不相同。Memory 引擎使用固定大小的行,因此它需要分配最大可能的存储空间——即便数据长度是可变的。但是,对于字符串的对齐和空字符截断是由 MySQL 服务端完成的,因此所有存储引擎都是一样的。
与 CHAR 和 VARCHAR 相似的是 BINARY和 VARBINARY,用于存储二进制字节字符,BINARY 的对齐使用字符0的字节值来对齐,并且再获取值的时候不会截断。如果需要使用字符的字节值而不是字符的话,使用 BINARY 会更高效,这是因为比较时,一方面不需要考虑大小写,另一方面是MySQL一次只比较一个字节。
1.建立用户信息表
create table userinfo(id int(4) not null primary key, name varchar(20) not null unique key)engine=innodb default charset=utf8
2.建立好友关系表
create table friend(uid int(4) not null, foreign key(uid) references
userinfo(id),fid int(4) not null, foreign key(fid) references
userinfo(id),unique key(uid,fid))engine=innodb default charset=utf8
3.追加测试数据(满足uid<fid条件)
insert userinfo values(1111---9999,'namea---namei’)
insert friend values(1111,4444---6666)
insert friend values(5555,6666---9999)
4.查询好友(5555的好友)
select * from friend where uid=5555 or fid=5555
+-------+------+
| uid | fid|
+-------+------+
| 1111 | 5555 |
| 5555 | 6666 |
| 5555 | 7777 |
| 5555 | 8888 |
| 5555 | 9999 |
+-------+--------+
5.问题:
5.1.userinfo中的id和name不为null,且不可重复:table设计可以做到
5.2.friend中的uid和fid均不为null,且都来自于userinfo的id:table设计可以实现
5.3.(uid,fid)组合不可重复:table设计可以完成
5.4.好友关系的表达时,(1111,5555)和(5555,1111)有冗余,也会出现(1111,1111)这样的数据:这个在table设计实现比较麻烦,需要在程序层面实现,也即增加限制条件uid<fid即可
6.结果:
table设计达不到要求,或者较难达到要求时,可以在程序层面予以弥补。
也可以左键点击某个表(即选中某表),在上面辅助菜单栏里有“打开表”、“设计表”、“新建表”等按钮可点击,点击“设计表”按钮即可。
进入后,会弹出新的操作窗口,新窗口的菜单栏里有常用的修改表结构的按钮,右键点击某列字段也可以弹出相应的修改表的操作按钮。
其它摸索着看提示操作即可,还是很简单的。
修改好表后点击菜单栏上的“保存”按钮即可。
注:若要查看修改表的sql语句,必须在“保存”之前点击“SQL预览”按钮。
这个问题问的好,要弄一个表很容易,关键是表设计出来是否合理!
如果表设计的好,则会相当清晰,易于理解,后续开发上事半功倍,维护也方便;如果设计的不好,则难以理解,维护困难,代价大。
表与表之间的关系有三种:1.一对一,2.一对多,3.多对多
一对一的表,两表的属性实际上完全可以合并成一个表,共用一个主键即可;
一对多的表,可以设中间关联表,也可以将关联表并入“多”这头;若设独立关联表,则可引入“多”这头的主键作为其主键,也可另立主键,并将“一”和“多”两表的主键作为关联表的外键;
多对多的表,则必须设中间关联表,关联表设独立主键,并引入两个“多”头的表的主键作为关联表的外键。
这是上述三种关系表在键处理上的基本原则。
范式还是要遵循的,这套理论还是科学合理的。不要相信反范式设计,反范式设计在规模庞大时,数据冗余多,编码及维护会变得困难,万一考虑漏掉的将导致数据不一致,甚至酿成灾难。严格按照范式理论来设计数据库,将使你编码及维护时少操很多心。
一般来说,先进行需求分析,然后画出数据流图,然后再根据数据流图画出ER图,然后再根据ER图创建各种表。表是根据ER图来创建的,表设计的合不合理,关键是ER图抽像的合不合理。在抽像ER图时,一般遵循这样的原则:
能用1对1的,就不用1对多;能用1对多的,就不用多对多,往简单化方向靠;
能当属性处理的,尽量当属性,而不是当实体处理去另立新表,这样可使问题简化。
把意义相近联系紧密的属性放在一张表内,而不是拆在多张表中。
看了一下你上述几张表,我认为不合理,户主是人,家庭成员也是人,把他们分在户主表和家庭成员表中不合理,他们是同一类的,宜合在一张家庭成员表中,并增加一个标志性字段,以指明哪个人是户主。另外,宜建立一张地址表,以取代户主表,地址表中宜指明乡场镇、村巷道、几区、门牌号等与地址关系紧密的属性,把户籍、联系方式、户主等字段拿走,他们不是地址属性,这几个宜放在成员关系表中,户籍是人的属性,并非地址的属性,联系方式就更明显了,要联系的是人,而不是地址。
很明显,地址和家庭成员是一对多关系,一个地址同时可以住着多个成员,而一个成员同时只能住一个地址,这样,设计成地址表和家庭成员表之后,要在家庭成员表中再加一个地址外键字段,把地址表的主键当作家庭成员表的外键填入,这样,成员表中的每个人都可以通过地址外键字段到地址表中找到其所住地址。另外,成员表中也指明了哪个人是户主,也指明了每个人的户籍和联系方式,这些信息你都可以找得到。
2.通过表的关系,来帮助我们怎样建表,建几张表。
一对一
一张表的一条记录一定只能与另外一张表的一条记录进行对应,反之亦然。
学生表:姓名,性别,年龄,身高,体重,籍贯,家庭住址,紧急联系人
其中姓名、性别、年龄、身高,体重属于常用数据,但是籍贯、住址和联系人为不常用数据
如果每次查询都是查询所有数据,不常用的数据就会影响效率,实际又不用
常用信息表:ID(P),姓名,性别,年龄,身高,体重
不常用信息表:ID(P),籍贯,家庭住址,紧急联系人
解决方案:将常用的和不常用的信息分享存储,分成两张表
不常用信息表和常用信息表,保证不常用信息表与常用信息表能够对应上:找一个具有唯一性的
字段来共同连接两张表。
一个常用表中的一条记录永远只能在一张不常用表中匹配一条记录,反之亦然。
一对多
一张表中有一条记录可以对应另外一张表中的多条记录;但是反过来,另外一张表的一条记录
只能对应第一张表的一条记录,这种关系就是一对多或多对一
母亲与孩子的关系:母亲,孩子两个实体
母亲表:ID(P),名字,年龄,性别
孩子表:ID(P),名字,年龄,性别
以上关系:一个妈妈可以在孩子表中找到多条记录(也可能是一条),但是一个孩子只能找到一个妈妈
是一种典型的一对多的关系。
但是以上设计:解决了实体的设计表问题,但是没有解决关系问题,孩子找不到母亲,母亲也找不到孩子
解决方案:在某一张表中增加一个字段,能够找到另外一张表中的记录:在孩子表中增加一个字段
指向母亲表,因为孩子表的记录只能匹配到一条母亲表的记录。
母亲表:ID(P),名字,年龄,性别
孩子表:ID(P),名字,年龄,性别,母亲表ID(母亲表主键)
多对多
一对表中(A)的一条记录能够对应另外一张表(B)中的多条记录;同时B表中的一条记录
也能对应A表中的多条记录
老师和学生
老师表 T_ID(P),姓名,性别
学生表 S_ID(P),姓名,性别
以上设计方案:实现了实体的设计,但是没有维护实体的关系
一个老师教过多个学生,一个学生也被多个老师教过
解决方案:增加一张中间关系表
老师与学生的关系表:ID(P),T_ID,S_ID
老师表与中间表形成一对多的关系,而中间表是多表;维护了能够唯一找到一表的关系;
同样的学生表与中间表也是一个一对多的关系
学生找老师:找出学生ID--->中间表寻找匹配记录(多条)--->老师表匹配(一条)
老师找学生:找出老师ID--->中间表寻找匹配记录(多条)--->学生表匹配(一条)
非持久化统计信息
统计信息没有保存在磁盘上,而是频繁的实时计算统计信息;
每次对表的访问都会重新计算其统计信息;
假设针对一张大表的频繁查询,那么每次都要重新计算统计信息,很耗费资源。
持久化统计信息
把一张表在某一时刻的统计信息值保存在磁盘上;
避免每次查询时重新计算;
如果表更新不是很频繁,或者没有达到 MySQL 必须重新计算统计信息的临界值,可直接从磁盘上获取;
即使 MySQL 服务重启,也可以快速的获取统计信息值;
统计信息的持久化可以针对全局设置也可以针对单表设置。
接下来,详细说 MySQL 统计信息如何计算,何时计算,效果评估等问题。在 MySQL Server 层来控制是否自动计算统计信息的分布,并且来决策是持久化还是非持久化。
数据查询语言(凡是带有 select 关键字的都是查询语句)
select...
数据操作语言(凡是对表中的 数据 进行增删改的都是 DML)
insert 增 delete 删 update 改
数据定义语言(凡是带有 create、drop、alter 的都是 DDL)
主要操作的是 表的结构 ,不是表的数据
事务控制语言(包括:事务提交 commit、事务回滚 rollback)
数据控制语言(授权 grant、撤销权限 revoke)
select 字段 from 表名 where 条件
in(具体值,具体值,......) 不是区间
一个输入对应一个输出,和其对应的是多行处理函数(多个输入,对应一个输出)
输入多行,最终输出一行
如果你 没有对数据进行分组,整张表默认为一组 。
在实际的应用中,可能需要先进行分组,然后对每一组的数据进行操作
案例: 查询每个员工所在部门的名称,显示员工名和部门名?
emp e 和 dept d 表进行连接。条件是:e.deptno = d.deptno
SQL92语法:(结构不够清晰,表的连接条件和后期进一步筛选的条件,都放到了 where 子句中)
SQL99语法:(表连接的条件是独立的,连接之后,如果还需要进一步筛选,再往后继续添加 where 子句)
技巧: 把一张表看成两张表
思考: 外连接的查询结果条数 >= 内连接的查询结果条数
select 语句中 嵌套 select 语句,被嵌套的 select 语句称为 子查询。
将查询结果集的一部分取出来。(通常使用在分页查询当中)
将字符串 varchar 类型转换成 date 类型
将日期转换成字符串
可以获取当前系统的时间,并且获取的时间是 datetime 类型的
注意:若没有条件限制将会导致所有数据全部更新。
注意:若没有条件,会删除整张表的数据。
constraint
not null 约束的字段 不能为 NULL (只有列级约束)
unique 约束的字段 不能重复 ,但是可以为 NULL
primary key
foreign key
transaction
实现原理 :缩小扫描的范围(形成树),避免全表扫描
Database Administrator 数据库管理员
数据库表的设计依据。教你怎么进行数据库表的设计。
免费领取有关于java面试题材料和讲解!
试题类别:(类别 ID,类别名称,类别描述)
试题表:(试题 ID,试题类别 ID,试题描述,试题难度,试题答案)
答题表:(答题 ID,用户 ID,试题 ID,答题答案,答题评分)
大概就这样