来自:http://www.cnblogs.com/nankezhishi/archive/2012/02/12/2348091.html#2617778
故事的起源
作为软件工程专业出身的程序员,之前所接受的关于数据库的教育都是基于关系型数据库。对key-value based数据库和document-based数据库的都只是仅仅了解而已。
最近公司要做一个类似电商的系统,我来负责数据库的设计和接口的提供。当然,我们使用的数据库也是传统的关系型数据库sql SERVER 2005,所以我也并没有什么太大压力。
但是当头儿看到我设计的分类系统的数据库表结果时意见很大,一翻沟通下来,我基本上就崩溃了。他的一翻话基本上推翻了我对数据库的基本认识。
我的设计
要实现的分类是书籍分类体系,会至少有4层,总共大约有几百个分类。我很自然地想到了邻接表(Adjacency List)模式。然后设计了的表结构大体如下:
列名 | CategoryId | ParentId | Name |
描述 | 主键 | 父键 | 分类名 |
当然还有别的字段,与问题无关就不列了。
使用CTE递归的方式来获取一个分类下的子分类树。我考虑数据量并不大,所以性能上应该不成什么问题。
和头儿的争论
头儿看到我的设计,直接说,不用这么麻烦了,不是有4层吗?那就用4列,一列表示一级。
我说国图法分类可能有7层以上。他说那就7列好了。
我说这样数据量会有大量冗余,更新起来会很麻烦。
头儿说,我之前做过很多树型结构,一开始也和你一样。但是做着做着就发现查询继承关系好麻烦啊,还不如直接用列存储下来。
我说查询继承的代码我已经写好了,用CTE递归我并不觉得麻烦啊。
头儿说,不要用递归,我们要为读优化,而不是为写优化。
这个我完全赞同,但是这个数据量并不大,但是我还是觉得这个做法很山寨。不符合数据库的基本范式啊。
他说你知道范式,也要了解反范式。而且这个数据库本身不要包含业务逻辑。你看现在出来的Redis和MemeryCached,存的都是名值对儿,没有逻辑。逻辑是给业务处理的。什么是程序?程序就是算法加数据,数据和逻辑是要分开的。
我说从属关系也是逻辑吗?数据一致性也是逻辑吗?
应该算啊,数据库就是负责存储数据的就够了,你自己好好想想吧。
之后我还是坚持了自己的做法,头儿勉强接受,但是要多加一个Path列,存储这个分类的所有父节点。
背景和现状
我进这个项目的时候,这个项目的数据库基本上已经建立完了。现在只是由于业务扩展需要新建数据库表。刚进公司的时候,我就觉得这里的数据库用法很诡异。
- 只有主键,没有外键,也就是数据库基本无从保证数据的一致性。
- 没有使用ORM,需要在C#代码中拼sql来实现业务对象的读取和写入。
- 不允许使用存储过程。这会引入过多的业务逻辑。
- 我也没见过任何View,任何Trigger,任何自定义函数。
现在发现这些现状与头儿的想法其实是比较一致的。但是给人的感觉就是把sql SERVER当作一个Key-Value的Database来实用。自然也就不需要那些sql SERVER的功能。
但是我很奇怪,这样的用法,为什么不直接用No sql方案呢?不用也就算了,更让我困惑的地方就在于,把No sql的理念用在关系型数据库上合适吗?
我在这里也没有看到过任何数据库性能Profiling的代码,对于数据库性能的考量,基本上依赖数据库服务器的cpu占用。(也许有测试人员做过我不知道而已吧。希望是这样。)我们仅有的一名DBA也于年前离职了。
我的想法
我一直觉得数据库设计是一项很重要很复杂的工作,需要了解业务需求,需要了解用户的使用方式,需要能够遇见到基本的可变性,需要对数据库底层机制有深入的了解,等等等等。也听过很多数据库高手改写一段sql,就让数据库查询变得飞快的故事。而且一切优化都应该有实际的数据来说话。也许我们头儿在数据库设计上有相当的功力,很有感觉。但是真的是这样的吗?我有我自己的想法,不知道是不是正确,但希望能和大家讨论一下。
关于反范式和优化
反范式作为一种数据库优化方案,我并觉得和其它的优化有什么根本上的不同。但是从优化的次序上来讲,我个人觉得应该先在查询的sql上做足功夫,不行了再祭出反范式这个以空间换时间的杀手锏。不应该是从数据库设计一开始就惦记着的事情,尤其是这还是在关系型数据库上。当然,资深人员一眼就能感觉出哪里需要这么干当然更好。不过对于上面的情况,我个人觉得不需要。
关于数据库不能有逻辑
这句话首先需要澄清什么属于逻辑。由于这是我们头的观点,我也没有跟他详细讨论过这个问题。我来讲下我的理解吧。数据库在设计之初就是为某个业务设计的,脱离了这个业务,这个设计本身也没有了意义。所以我不认为逻辑从数据库分离能让数据库可以被重用。但是数据库包含了过多的逻辑会增加数据库服务器的负担,所以逻辑还是少放在数据库为好。所以我基本同意这句话。
但是,我认为。数据关系(如外键)不属于逻辑,数据完全性不属于逻辑。而且这些就应该主是由数据库来保证的。Web 服务器可以去检查外键,检查唯一约束,但是即使Web服务器不做这些,数据库也应该做到这些。
最常见的就是数据插入和更新,如果涉及到多张表,那这些sql写入一个存储过程以方便外界调用是很好的。同时,存储过程或是View也可以把数据库表设计与程序之间的依赖解除,在一些简单的情况下实现不修改程序而对数据库表结构进行变更。使用存储过程也许会给数据迁移带来麻烦,但是有谁没事儿换数据库呢?
我们目前这种在程序中拼sql去操作数据库的做法,我没有看到什么优势。纯粹就是为了“数据库不能有‘逻辑’”而拆分出来的东西。
关于数据库系统的选用
我并没有反对Nosql或MemeryCached的理念,毕竟是用于处理不同的情况问题的。
像微博、社交、互动相关的系统。因为数据量大,访问量大,数据结构相对简单,用户访问具有时效性、地域性,使用这种Nosql+MemeryCache方案是合理甚至是唯一的选择。但是任何产品或是理念都有它适用的范围,没有银弹。把 MemoryCache用在员工作业平台或是项目管理系统上我觉得就相当的不靠谱。
但是电商系统似乎是处于两者之间的一种模式,而且我们的业务量在可预见的未来也不会增长到淘宝的那个级别。我和头的争论也就源于这些理念上的差别,他像是在建立大统一理论,把各种先进思想整合在一起,而我又不确定这是创新呢?还是不伦不类的方式?于是有了题目上的疑问。而按头的说法,也许这就是思考问题层次上的差别。
我在数据库设计上并没有什么经验,却又做不到别人说什么我就听什么。上面只是我根据对于数据库自身的理解产生的一些想法,还很不成熟,希望大家能说说自己的见解。如果实在看不下去了,也欢迎吐槽。
posted on 2012-02-12 17:20 南柯之石 阅读(3151) 评论(29) 编辑 收藏
评论
#1楼 2012-02-12 17:55一味
#2楼[楼主] 2012-02-12 18:21南柯之石
这代价是不是有点儿大了。
这样的话,每个平台特有特性是不能用了。我还没有做过迁移平台的事情,但存储过程和触发器这种东西并不是每个平台特有的东西,大家都有。应该不会给迁移带来多大麻烦,即使语法上略有不同,写在存储过程的sql可以很容易用工具进行格式转化,但是写在代码里的sql语句就没有办法了。
#3楼 2012-02-12 18:25Stephen_Liu
#4楼[楼主] 2012-02-12 18:35南柯之石
#5楼 2012-02-12 18:42bluce chen
#6楼 2012-02-12 19:15Stephen_Liu
没有ORM,我们是用C++进行开发的,操作数据库也是直接使用数据库提供的C接口API。sql语句完全在代码中,代码中有相应的class来适配不同的DB。存储过程并不是每一个DB都有的,比如老版本的MysqL、Postgresql和现在的sqlite。因为我们的客户可能已经有了自己的DB,我们的产品不能强制要求客户进行更换,特别是一些日本客户,他们都比较谨慎,所以只能是我们的产品提供这样的支持了。
#7楼 2012-02-12 20:23午后的小睡
#8楼 2012-02-12 23:07pulihe
#9楼 2012-02-13 00:26不若相忘于江湖
在访问量大的系统,不同意使用外键,我认为是性能问题,如果是内部软件,则可以,
2 没有使用ORM,需要在C#代码中拼sql来实现业务对象的读取和写入。
我们现在的做法也是一样,拼sql,不过之前会有相应的安全过滤机制。
3 不允许使用存储过程。这会引入过多的业务逻辑。
我们现在也是不能使用存储过程,理由嘛,有些人写习惯了,会经常把业务逻辑也写到里面去的,维护起来很吃力。
4 我也没见过任何View,任何Trigger,任何自定义函数。
Trigger除非非常有必要,否则坚决不用。
#10楼 2012-02-13 01:47Gu
1 只有主键,没有外键,也就是数据库基本无从保证数据的一致性。
方便扩容和copy数据
2 没有使用ORM,需要在C#代码中拼sql来实现业务对象的读取和写入。
用orm对性能没太大影响,还是应该用的
3 不允许使用存储过程。这会引入过多的业务逻辑。
你只要维护过一堆存储过程的系统,你就知道这个苦啊...
4 我也没见过任何View,任何Trigger,任何自定义函数。
尽量不要用trigger和自定义函数. 但view我还是很喜欢用的.
#11楼 2012-02-13 08:21Kain
2、ORM么,什么都有两面性,ORM牺牲了部分性能来换取开发的效率,就看系统中这种牺牲是否值得了。
3、SP么,现在基本不推荐使用了,能用业务层处理的尽量不用SP干,哪怕效率低上很多,毕竟DB的扩展实在是太难了。
#12楼 2012-02-13 09:07imfunny
#13楼 2012-02-13 14:10睡不醒
1:只有主键,没有外键,也就是数据库基本无从保证数据的一致性。
答:关于外键:仁者见仁、智者见智了,有人喜欢有人讨厌!如果业务要求数据必须保持高度一致性,那必须要外键,否则就无所谓了。
2:没有使用ORM,需要在C#代码中拼sql来实现业务对象的读取和写入。
答:ORM不是万金油,更不是银弹,成也萧何败萧何,如果系统的业务逻辑很简单,而且业务需求也很明确,不会经常修改业务逻辑,那真没必要使用ORM,因为ORM的性能实在是不敢恭维,能用几句sql搞定的真没必要用ORM,而且读的并发达到100万的话,还是用sql吧。
3:不允许使用存储过程。这会引入过多的业务逻辑。
答:如果考虑到跨平台的特性,还是别用存储过程了,sqlSERVER和ORACLE虽然都有存储过程,但是写法不一样的!这个会造成部署的困难,但是把逻辑写到程序中的话,至少很容易保证逻辑一致。当然,如果用户都是微软的合作伙伴的话,还是使用存储过程的好,性能会好些。
4:我也没见过任何View,任何Trigger,任何自定义函数。
答:Trigger太影响性能和稳定性了,自定义函数同存储过程,不再赘述,View?你都是手写sql,自己直接JOIN就是了,VIEW无非就是帮你JOIN了,再加个缓存。而且每次更改表结构都要刷新视图,很讨厌!
#14楼 2012-02-13 21:03fchzzwcsr201
#15楼 2012-02-14 09:28imfunny
其实看需要的。想简单sqllite,想对象化用db4o,普通的使用mssql或者MysqL,想高端用oracle,db2,之外还有很多很多。同时还有MongoDB这样的kv6格式的数据库。随便选择吧。
无所谓那种数据库,其实关键还是程序和算法。数据只是一个成品。比如blogengine人家默认的直接使用xml存储数据。
#16楼 2012-02-14 09:52遗忘海岸
#17楼 2012-02-14 10:07adexbn
从你描述的背景和现状来看,有充分的理由说明应该这样。
#18楼 2012-02-14 22:47Fish Li
#19楼 2012-02-15 10:53luofer
数据库设计要在一致性和性能上进行取舍,如果是可以容忍延迟一致性和一致性失败而是看重高可读,Nosql当然是首选,但在关系数据库上搞这些还是很怪异,还是用Nosql去吧。如果是要保证高可用和一致性那就只能是关系型数据库了。所有的范式和设计规则基本都是为了保证在一致性的前提下保证性能。
反范式首先是要有范式才对吧,否则反范式从何而来?
#20楼 2012-02-15 11:09luofer
至于有人说不用存储过程是怕有人把业务逻辑塞到里面,我想是误会了关系型数据库的用途,毕竟数据库最擅长的还是以集合,批来处理数据,所有在里面大量使用游标,流控制的建议多看看数据库原理,现在还有那种主流的关系型数据库不支持存储过程吗
#21楼 2012-02-17 14:32Ivony...
至于你说的sql优化,君不知主流的关系型数据库都是自行查询优化的。你写的sql并不能决定其实际行为。
#22楼 2012-02-23 23:24听说读写
外键影响性能,在高性能的系统中不应该使用
一般开始可以做范式设计,然后再做反范式设计
2 没有使用ORM,需要在C#代码中拼sql来实现业务对象的读取和写入。
ORM优势和劣势都很明显
开发速度快,处理简单逻辑简单,不过功能和定制性太差了,想搞点复杂功能或者是优化sql性能都非常困难
3 不允许使用存储过程。这会引入过多的业务逻辑。
存储过程的好处是功能和性能稍好,和程序脱离的部署,良好的权限控制和安全机制
坏处是部署麻烦,版本要一致,程序员的控制能力有一点小问题
我很讨厌强制要求使用存储过程的公司
4 我也没见过任何View,任何Trigger,任何自定义函数。
Trigger? 万不得已别用它
View啊....搞搞内部系统用一下还行,平时就不要拿出来玩了
如果是搞小系统,内部系统,或者是学术系统(设计的很漂亮,不实用)
那么爱怎么搞怎么搞
回来说
那个树形结构,网络上有现成的了,随便搞一个吧,看的不爽自己优化也ok,自己写的东西如果没经过验证的复杂东西,老板出于风险控制的目的,八成会砍掉
用不用Nosql或者Memcached主要是架构方面的考虑
#23楼 2012-03-09 10:51Alvin
#24楼 2012-03-11 10:20BFL
#25楼 2012-03-18 22:11通心菜
同意 我们目前开发规范明确规定 不许有外键
2 没有使用ORM,需要在C#代码中拼sql来实现业务对象的读取和写入。
这个可以适当考虑加进去
3 不允许使用存储过程。这会引入过多的业务逻辑。
同意 我们目前开发规范明确规定 不许有存储过程 .迁移过一次带SP的项目痛苦...
4 我也没见过任何View,任何Trigger,任何自定义函数。
同意
#26楼[楼主] 2012-03-18 22:20南柯之石
哥们,我错了,我应该说得更明白些的。我说的太绕了,真是全拧了。汗啊。。。。
#27楼 2012-03-19 17:11Henming
其实数据库越来越倾向于单独存储,对软件越来越透明。
公司的架构师跟你们老大的想法几乎都是吻合的,数据库只用作存储,不做其它附加。
#28楼 2013-02-09 05:36Michael.zh
1,没有任何一个数据是符合数据完整心规范的,使用外键的唯一结果就是大量的数据无法导入;
2,报表基本上都给予“非完整性”数据出来,不精确但完全不影响报表使用者进行决策;
3,在一定规模以后,数据的性能成为几乎是唯一的考量指标,这时候你就会发现具有复杂逻辑的存储过程,trigger 有多么令人绝望;
ORM应该采用,但目的是为了规范编码,防止低性能的sql语句出现,而不是为了以后所谓的迁移,选择数据库平台是首要需要决定的事情,一旦开始使用99%不会考虑迁移。
如果使用了过多的数据库的特性(存储过程,触发器等),会将我们的系统架构局限于该数据库平台,对于项目还好,对于产品来说,可能是一种风险。特别是在将来可以预见到有迁移数据库平台的可能的时候。