数据库设计
- 问题1:为什么一定要设一个主键?
- 问题2:主键是用自增还是UUID?
- 问题3:主键为什么不推荐有业务含义?
- 问题4:货币字段用什么类型?
- 问题5 drop、truncate和delete的区别
- 问题6:时间字段用什么类型?
- 问题7:为什么不直接存储图片、音频、视频等大容量内容?
- 问题8:char、verChar和text区别?
- 问题9:字段为什么要定义为NOT NULL?
- 问题10:MySQL中的锁
- 问题11:字段允许适当冗余
- 个人课设中的数据库设计失误
呜呜呜。。。前天接某司电话面试,问到数据库的东西答的一塌糊涂,赶紧CSDN一波
问题1:为什么一定要设一个主键?
数据库中一个表的主键指的是一个列或多列的组合,可唯一标识一行数据。
主键的作用:
- 保证实体的完整性;
- 加快数据库的操作速度。
- 在表中添加新记录时,DBMS会自动检查新记录的主键值,不允许该值与其他记录的主键值重复。
- DBMS自动按主键值的顺序显示表中的记录。如果没有定义主键,则按输入记录的顺序显示表中的记录。
问题2:主键是用自增还是UUID?
使用自增长做主键的优点:
1、很小的数据存储空间
2、性能最好
3、容易记忆
使用自增长做主键的缺点:
1、如果存在大量的数据,可能会超出自增长的取值范围
2、很难(并不是不能)处理分布式存储的数据表,尤其是需要合并表的情况下
3、安全性低,因为是有规律的,容易被非法获取数据
使用UUID做主键的优点:
1、它是独一无二的,出现重复的机会少
2、适合大量数据中的插入和更新操作,尤其是在高并发和分布式环境下
3、跨服务器数据合并非常方便
4、安全性较高
使用UUID做主键的缺点:
1、存储空间大(16 byte),因此它将会占用更多的磁盘空间
2、会降低性能
3、很难记忆
个人理解:
- 如果在工程量较小、数据量不大,对速度效率要求不高的情况下,用UUID;当然为了省事用自增id也行。。。
- 如果项目是分布式的,尤其在会发生表合并的情况下,就要使用UUID;自增id很难对表进行合并操作。
- 在数据量大,对速度效率要求高的情况下,建议使用自增id,只是要注意保证数据的安全性。(其实我也没接触过百万级数据量的工程。。。)
UUID:
目前生成uuid常用SnowFlake算法,其结果是一个64bit大小的整数
其实在实际项目中生成的uuid并不一定是64位,也并不一定是全数字的整形;
我参与的项目中uuid就是32位的verchar类型,应该只是算法略有不同,原理类似吧。
当然需要考虑的问题还有:如果自增id用完了怎么办?
–>自增id用完了怎么办?
问题3:主键为什么不推荐有业务含义?
其实我第一眼看到这个问题的时候没多想,可能就是为了防止具体业务对数据库设计的干扰吧。
回头一想,我的课设数据库设计中不就违背了这个规则吗,比如考虑用学号作为学生表的主键。。。
为什么不推荐主键包含业务含义有如下两个原因:
- 因为任何有业务含义的列都有改变的可能性,主键一旦带上了业务含义,那么主键就有可能发生变更。主键一旦发生变更,该数据在磁盘上的存储位置就会发生变更,有可能会引发页分裂,产生空间碎片。
- 带有业务含义的主键,不一定是顺序自增的。那么就会导致数据的插入顺序,并不能保证后面插入数据的主键一定比前面的数据大。如果出现了,后面插入数据的主键比前面的小,就有可能引发页分裂,产生空间碎片。
- 再一点,主键一旦包含了业务含义,业务发生改变时主键随之需要变更;可能引发需要修改从前端到sql语句的大范围代码修改,工程可维护性差。详见:主键包含业务含义对工程可维护性的影响
问题4:货币字段用什么类型?
回答:如果货币存储单位是分,可以用Int类型。如果坚持用元,用Decimal。
MySQL中DECIMAL数据类型用于在数据库中存储精确的数值。
例如语法:column_name DECIMAL(P,D):其中P表示的是有效数字的位数,D表示是小数的位数,D小于或等于P。
理解可看下面代码:
create table decimal_test(
id int auto_increment PRIMARY key,
score decimal(5,2) -- 取值范围是 -999.99 到 999.99
);
!!!千万不要答float和double,因为float和double是以二进制存储的,所以有一定的误差。 打个比方,你建一个列如下
CREATE TABLE `t` (
`price` float(10,2) DEFAULT NULL,
) ENGINE=InnoDB DEFAULT CHARSET=utf8
然后insert给price列一个数据为1234567.23,你会发现显示出来的数据变为1234567.25,精度失准!
问题5 drop、truncate和delete的区别
delete:属于DML数据操纵语言(CRUD)
drop 和 truncate:属于DDL数据定义语言
- Delete:
delete删除一行数据,并且同时将该行的删除操作作为事务记录在日志中保存以便进行进行回滚操作;
delete可根据条件删除表中满足条件的数据;若无where子句则删除所有行;
delete不影响表或索引占用的空间。 - truncate
1、truncate TABLE 一次性地从表中删除所有的数据(与不带where的delete子句类似),且不把单独的删除操作记录记入日志保存,删除行是不能恢复的。不可回滚,不触发触发器。执行速度快;
2、truncate TABLE 后表和索引所占用的空间会恢复到初始大小;
3、对于外键(foreignkey )约束引用的表,不能使用 truncate table,(没有为什么,语法规定),而应使用不带 where 子句的 delete 语句;
4、被索引视图引用过的表,也不能使用truncate。(索引视图是啥???忘记了。。。) - Drop
drop Table是删掉一张表的结构和所有数据,将其所占用的空间全部释放;
drop是DDL,会隐式提交,操作立刻生效;不可回滚,不触发触发器。
总:
1、速度上一般来说:drop> truncate > delete
2、慎用drop和truncate!!!(反正我是没有用过。。。)
3、delete删除部分数据,注意where子句;drop删除整表;truncate保留表删除所有数据。
相关链接:drop、truncate和delete的区别详解
问题6:时间字段用什么类型?
本人参与项目中的时间日期字段为verchar类型,最大长度为14。
但是在后端存储时间的时候并不是简单的直接将时间对应的字符串存在数据库里;如2018-08-11存储为20180811。为什么不直接这样存储呢?看起来更直接不是。
数据库中有专门用来存储时间的格式datetime,如果像上面那样存储时间不觉得很zz嘛。有人给出如理由:
下划红线的观点我觉得不是重要原因,只要都用两位表示月和日就行了,比如01月11日。
后面的观点比较认同,这种写法在不同格式日期作比较的时候神烦。。。
那么到底数据库里存储的是什么格式的日期呢?
在新增一行数据的sql语句中对时间字段插入是这样写的:
<sql id="insertValues">
UNIX_TIMESTAMP()
</sql>
使用了unix时间戳(Unix timestamp): UNIX_TIMESTAMP()
获取当前时间的时间戳。
至于unix时间戳怎么用,两个内容差不多的链接:用法1、用法2
为什么要将日期时间转换再使用这种东西存储日期呢?
UNIX时间戳:Unix时间戳(英文为Unix epoch, Unix time, POSIX time 或 Unix timestamp)
是从1970年1月1日(UTC/GMT的午夜)开始所经过的秒数,不考虑闰秒。
我们知道(其实我不知道),在不同的数据库系统中对时间日期的解释是不同的,不可以直接进行兼容的转换。
而直接获取系统时间可以避免对各种时间格式转换的问题(个人理解)。而且使用这种方法可以解决跨时区问题。
唯一的缺点就是:2038年以后的时间,是无法用timestamp类型存储的。
很常见的时间表示格式是datetime:
datetime储存占用8个字节,它存储的时间范围为1000-01-01 00:00:00 ~ 9999-12-31 23:59:59。显然,存储时间范围更大。但是,它存储的是时间绝对值,不带时区信息。如果改变数据库的时区,该项的值不会自己发生变更!
问题7:为什么不直接存储图片、音频、视频等大容量内容?
之前也思考过这个问题,大量的媒体文件如果以二进制存在数据库里,可能会死。。。
数据库中存的应该是URL或路径;根据URL获取本地或是服务器上的文件信息。
看看具体设计中数据库是对这些媒体信息如何存储的:
学员表中有两个字段是对学员照片进行存储的,类型verchar,最大长度500
表中存储的也正是照片的URL:
那在前端怎么展示呢?看了代码就知道很简单:前后端将URL以字符串形式传递,前端以img标签展示即可
问题8:char、verChar和text区别?
以前也知道前两个有区别,具体是啥不太清楚,一直以为是长度不一样。。。
查了一些资料以后发现,感觉char和verChar与String和StringBuffer之间的关系还有点像,嚯嚯~
VERCHAR:
1、varchar(M)定义的列的长度是可变长度字符串,在MySQL5.0以上的版本中,varchar的数据类型长度支持到了65535,因为起始位和结束位占去了3个字节,所以其整体最大长度为65532字节(varchar的最大有效长度由最大行大小和使用的字符集确定)。
2、varchar值保存时只保存需要的字符数,另加一个字节来记录长度(长度超过255时需要2个字节)。
CHAR:
1、char(M)定义的列的长度为固定的,M的取值可以0-255之间;
2、保存char值时,在它们的右边填充空格以达到指定的长度。当检索到char值时,尾部的空格被删除掉;
3、在存储或检索过程中不进行大小写转换。char存储定长数据很方便,char字段上的索引效率很高。
因为在定义verChar时,系统会根据其实际长度分配存储空间,因此一般认为verChar能比char占用更少的空间,可节约磁盘时间。!!!但是,正因为verChar长度可变,在某些数据更新情况下会需要系统进行额外操作。
text:
存储可变长度的非Unicode数据,最大长度为2^31-1个字符。text列不能有默认值,存储或检索过程中,不存在大小写转换,后面如果指定长度,不会报错误,但是这个长度是不起作用的,意思就是你插入数据的时候,超过你指定的长度还是可以正常插入。
最后注意,阿里Java开发手册中明确限制:
varchar 是可变长字符串,不预先分配存储空间,长度不要超过 5000,如果存储长度大于此值,定义字段类型为 text,独立出来一张表,用主键来对应,避免影响其它字段索引效率。
问题9:字段为什么要定义为NOT NULL?
在前两天联测的时候也发现了一个因为not null而出现的bug:
这是数据库设计,允许suggestions为空
下面是前端代码,当suggestions为空时,suggestions.length
自然获取不到,报空指针异常。
要进行额外的判空,才能保证程序不出现异常:
但是如果在数据库设计时将相关字段设计为not null
就可以避免前端繁琐的判空和可能出现的异常,同时提高检索效率,那么何乐而不为呢?
问题10:MySQL中的锁
问题11:字段允许适当冗余
字段允许适当冗余,以提高查询性能,但必须考虑数据一致。冗余字段应遵循:
1)不是频繁修改的字段。
2)不是 varchar 超长字段,更不能是 text 字段。
正例:商品类目名称使用频率高,字段长度短,名称基本一成不变,可在相关联的表中冗余存储类目名称,避免关联查询。
个人课设中的数据库设计失误
考虑将有业务含义赋予主键:学号
将金额设置为float类型
设置了大量外键
。。。