转自:为爱奔跑 的博客
数据库设计的三大范式:为了建立冗余较小、结构合理的数据库,设计数据库时必须遵循一定的规则。在关系型数据库中这种规则就称为范式。范式是符合某一种设计要求的总结。要想设计一个结构合理的关系型数据库,必须满足一定的范式。
在实际开发中最为常见的设计范式有三个:第一范式是最基本的范式。如果数据库表中的所有字段值都是不可分解的原子值,就说明该数据库表满足了第一范式;第二范式在第一范式的基础之上更进一层。第二范式需要确保数据库表中的每一列都和主键相关,而不能只与主键的某一部分相关(主要针对联合主键而言)。也就是说在一个数据库表中,一个表中只能保存一种数据,不可以把多种数据保存在同一张数据库表中;第三范式需要确保数据表中的每一列数据都和主键直接相关,而不能间接相关。总结一下,就是:第一范式(确保每列保持原子性);第二范式(确保表中的每列都和主键相关);第三范式(确保每列都和主键列直接相关,而不是间接相关)。
在目前的企业信息系统中,数据库还是最佳的数据存储方式,虽然已经有很多的书籍在指导我们进行数据库设计,但应该那种方式是设计数据库的表结构的最好方法、设计时应遵从什么样的原则、四个范式如何能够用一种方式达到顺畅的应用等是我一直在思考和总结的问题,下文是我针对这几个问题根据自己的设计经历准备总结的一篇文章的提纲,欢迎大家一块进行探讨,集思广益。其中提到了领域建模的概念,但未作详细解释,希望以后能够有时间我们针对这个命题进行深入探讨。
1.不应该针对整个系统进行数据库设计,而应该根据系统架构中的组件划分,针对每个组件所处理的业务进行组件单元的数据库设计;不同组件间所对应的数据库表之间的关联应尽可能减少,如果不同组件间的表需要外键关联也尽量不要创建外键关联,而只是记录关联表的一个主键,确保组件对应的表之间的独立性,为系统或表结构的重构提供可能性。
2.采用领域模型驱动的方式和自顶向下的思路进行数据库设计,首先分析系统业务,根据职责定义对象。对象要符合封装的特性,确保与职责相关的数据项被定义在一个对象之内,这些数据项能够完整描述该职责,不会出现职责描述缺失。并且一个对象有且只有一项职责,如果一个对象要负责两个或两个以上的职责,应进行分拆。
3.根据建立的领域模型进行数据库表的映射,此时应参考数据库设计第二范式:一个表中的所有非关键字属性都依赖于整个关键字。关键字可以是一个属性,也可以是多个属性的集合,不论那种方式,都应确保关键字能够保证唯一性。在确定关键字时,应保证关键字不会参与业务且不会出现更新异常,这时,最优解决方案为采用一个自增数值型属性或一个随机字符串作为表的关键字。
4.由于第一点所述的领域模型驱动的方式设计数据库表结构,领域模型中的每一个对象只有一项职责,所以对象中的数据项不存在传递依赖,所以,这种思路的数据库表结构设计从一开始即满足第三范式:一个表应满足第二范式,且属性间不存在传递依赖。
5.同样,由于对象职责的单一性以及对象之间的关系反映的是业务逻辑之间的关系,所以在领域模型中的对象存在主对象和从对象之分,从对象是从1-N或N-N的角度进一步完善主对象的业务逻辑,所以从对象及对象关系映射为的表及表关联关系不存在删除和插入异常。
6.在映射后得出的数据库表结构中,应再根据第四范式进行进一步修改,确保不存在多值依赖。这时,应根据反向工程的思路反馈给领域模型。如果表结构中存在多值依赖,则证明领域模型中的对象具有至少两个以上的职责,应根据第一条进行设计修正。第四范式:一个表如果满足BCNF,不应存在多值依赖。
7.在经过分析后确认所有的表都满足二、三、四范式的情况下,表和表之间的关联尽量采用弱关联以便于对表字段和表结构的调整和重构。并且,我认为数据库中的表是用来持久化一个对象实例在特定时间及特定条件下的状态的,只是一个存储介质,所以,表和表之间也不应用强关联来表述业务(数据间的一致性),这一职责应由系统的逻辑层来保证,这种方式也确保了系统对于不正确数据(脏数据)的兼容性。当然,从整个系统的角度来说我们还是要尽最大努力确保系统不会产生脏数据,单从另一个角度来说,脏数据的产生在一定程度上也是不可避免的,我们也要保证系统对这种情况的容错性。这是一个折中的方案。
8.应针对所有表的主键和外键建立索引,有针对性的(针对一些大数据量和常用检索方式)建立组合属性的索引,提高检索效率。虽然建立索引会消耗部分系统资源,但比较起在检索时搜索整张表中的数据尤其时表中的数据量较大时所带来的性能影响,以及无索引时的排序操作所带来的性能影响,这种方式仍然是值得提倡的。
9.尽量少采用存储过程,目前已经有很多技术可以替代存储过程的功能如"对象/关系映射"等,将数据一致性的保证放在数据库中,无论对于版本控制、开发和部署、以及数据库的迁移都会带来很大的影响。但不可否认,存储过程具有性能上的优势,所以,当系统可使用的硬件不会得到提升而性能又是非常重要的质量属性时,可经过平衡考虑选用存储过程。
10.当处理表间的关联约束所付出的代价(常常是使用性上的代价)超过了保证不会出现修改、删除、更改异常所付出的代价,并且数据冗余也不是主要的问题时,表设计可以不符合四个范式。四个范式确保了不会出现异常,但也可能由此导致过于纯洁的设计,使得表结构难于使用,所以在设计时需要进行综合判断,但首先确保符合四个范式,然后再进行精化修正是刚刚进入数据库设计领域时可以采用的最好办法。
11.设计出的表要具有较好的使用性,主要体现在查询时是否需要关联多张表且还需使用复杂的SQL技巧。我感觉遵守的范式越多,就越使SQL复杂,具体情况具体分析。设计出的表要尽可能减少数据冗余,确保数据的准确性,有效的控制冗余有助于提高数据库的性能
因此,考虑了以上条件之后,表设计约定规则如下:
关于MySQL的部分学习笔记总结:
一、事务跟存储引擎
1.四种事务隔离级别:read uncommited, read commited(大多数db默认的),repeatable read(mysql默认), seriazable。
2.mysql是默认的auto commited, 也就是说每次查询默认都是自动提交的(show variables like 'autocommited')。mysql可以通过set transaction isolatioin level命令来设置隔离级别,例如:set session transaction isolation level read commited。
3.mysql中像innodb采用mvcc(多版本并发控制)来处理并发。mvcc只工作在read commited,repeatable read这两种事务隔离级别上。read uncommited隔离级别不兼容mvcc是因为在该级别得下的查询,不读取符合当前事务版本的数据行,而是最新版本的数据行。seriazable隔离级别不兼容MVCC,因为该级别下的读操作会对每个返回行进行加锁。
4.选择存储引擎,并发选用myisam,事务选择innodb,myisam比innodb更容易出错,出错了恢复的时间也比较长。只有myisam支持全文检索。
5.把表从一种存储引擎转到另一种引擎:
二、数据类型
1.尽可能的要把field定义为Not NULL, mysql比较难优化使用了可空列的查询,它会使索引,索引统计更加复杂。可空列需要更多的存储空间,还需要mysql内部进行特殊处理,当可空列被索引时,每条记录都需要一个格外的字节。 即使要在表中存储"没有值"的字段,考虑使用0,特殊字段或者空字符串来代替。
2.datetime与timestamp能保存同样的数据:精确度为秒,但是timestamp使用的空间只有datetime的一半,还能保存时区,拥有特殊的自动更新能力。但是timestamp保存的时间范围要比datetime要小得多。mysql能存储的最细的时间粒度为秒
3.mysql支持很多种别名,如bool,integer,nummeric.
4.float与double类型支持使用标准的浮点运算进行近似计算。 Decimal类型保存精确的小数,在>=mysql5.0,mysql服务器自身进行了decimal的运算,因为CPU不支持直接对它进行运算,所以慢一点。
5.mysql会把text与blob类型的列当成有实体的对象来进行保存。他们有各自的数据类型家族(tinytext,smalltext,text,mediumtext,longtext; blob类似); mysql对blob与text列排序方式和其他类型有所不同,它不会按照字符串的完整长度来排序。而只是按照max_sort_length规定的若干个字节来进行排序。
6.采用enum来代替字符串类型。mysql在内部把每个枚举值都保存为整数。enum在内部是按照数字进行排序的,而不是按照字符串。enum最不好的就是字符串列表是固定的,添加和删除必须使用alter table。
7.ip地址,一般会采用varchar(15)列来保存。事实上,IP地址是个无符号的32位整数,而不是字符串。mysql提供了inet_aton()和inet_nota()函数在证书与ip地址之间进行转换。
三、索引
1.聚集索引不仅仅是一种单独的索引类型,而且是一种存储数据的方式。Innodb引擎的聚集索引实际上在同样的结构中保存了B-Tree索引和数据行。当表有聚集索引时,它的数据行实际上保存在索引的叶子上。注意是存储引擎来实现索引。
2.myisam与innodb数据布局:myisam索引树(无论是主键索引还是非主键索引)叶子节点都是指向的数据行,而innodb中聚集索引,主键索引树叶子节点就带得有数据的内容,而非主键索引树中叶子节点指向主键值,而不是数据的位置。
3.mysql有两种产生排序结果的方式:使用文件排序,或者扫描有序的索引。目前只有myisam支持全文索引。
4.myisam表有表级锁;myisam表不支持事务,实际上,myisam并不保证单条命令完成;myisam只缓存了mysql进程内部的索引,并保存在键缓存区内。OS缓存了表的数据;行被紧密的保存在一起,磁盘上的数据有很小的磁盘占用和快速的全表扫描。
5.innodb支持事务和四种事务隔离级别;在mysql5.0中,只有innodb支持外鍵;支持行级锁与mvcc;所有的innodb表都是按照主键聚集的;所有索引(出开主键)都是按主键引用行;索引没有使用前缀压缩,因此索引可能比myisam大很多;数据转载缓慢;阻塞auto_increment,也就是用表级锁来产生每个auto_increment。
四、MYSQL性能分析
1.mysql提供了一个benchmark(int 循环次数,char* 表达式); 可以分析表达式执行所花时间。 例如:
2.mysql有两种查询日志:普通日志和慢速日志。
五、MYSQL高级特性
1.在mysql中,只有myisam存储引擎支持全文索引。myisam全文索引是一种特殊的具有两层结构的B树。
2.存储引擎事务在存储引擎内部被赋予acid属性,分布式(XA)是一种高层次事务,它可以历哟内部个两段提交的方式将acid属性扩展到存储引擎外部,甚至数据库外部。阶段1:通知所有提交者准备提交 阶段2:通知所有参与者进行真正提交。
3.mysql 的字符集和校对规则有 4 个级别的默认设置:服务器级、数据库级、表级和字段级。Mysql4.1 开始支持 SQL 的子查询。