SQL 的反模式已经有不少人熟悉,但 NoSQL 是否也有反模式?
在自己的一篇博客文章中,EMC 中国研究院的颜开对 NoSQL 中的文档数据库进行了分析,并提炼出六种反模式。
在文章开头,颜开指出:
好的反模式可以在我们设计 Schema 告诉哪里是陷阱和悬崖。NoSQL 宣传的时候往往宣称是 SchemaLess 的,这会让人误解其不需要设计 Schema。但如果不意识到设计 Schema 的必要,陷阱就在一直在黑暗中等着我们。
接下来,颜开分析了三种主流的 NoSQL 数据库:
- 文档数据库,以 MongoDB 为代表
- 列存数据库,以 HBase 为代表
- 键值数据库,以 Redis 为代表
加上 MySQL,他将这四种开源数据库产品做了比喻和对比:
MySQL——菜刀
尽管其没有什么大的改进,但是新兴的互联网使用的最多的数据库。就像传统的菜刀,结构简单,几百年没有改进。但是不妨碍产生各式各样的刀法,只要有一把,就能胜任厨房里的大部分事务。MySQL 也是一样,核心已经稳定。但是切库,分表,备份,监控,等等手段一应俱全。
MongoDB——瑞士军刀
提供更灵活的 Schema,Capped Collection,异步提交,地理位置索引等五花十色的功能。就像瑞士军刀,不但可以当刀用,还可以开瓶盖,剪指甲。但是他也不比 MySQL 强,因为还缺乏时间的磨砺。一是系统本身的稳定性,二是开发,运维需要更多经验才能流行。
HBase——大象兵
依仗着 Hadoop 的生态环境,可以有很好的扩展性。但是就像象兵一样,使用者需要养一头大象 (Hadoop), 才能驱使它。
Redis——金箍棒
键值存储的代表,功能最简单。提供随机数据存储。就像一根棒子一样,没有多余的构造。但是也正是因此,它的伸缩性特别好。就像悟空手里的金箍棒,大可捅破天,小能成缩成针。
颜开指出 SQL 关系模型的两个弱点:
- 必须支持 Join:因为数据不能够有重复。所以使用范式的关系模型会不可避免的大量 Join。如果参与 Join 的是一张比内存小的表还好。但是如果大表 Join 或者表分布在多台机器上的话,Join 就是性能的噩梦。
- 计算与存储耦合:关系模型作为统一的数据模型既可以用于数据分析,也可以用于在线业务。但这两者一个强调高吞吐,一个强调低延时,已经演化出完全不同的架构。用同一套模型来抽象显然是不合适的。
颜开认为:
针对这两个梦魇。文档数据库如 MongoDB 的主要目的是:提供更丰富的数据结构来抛弃 Join 来适应在线业务。
……文档数据库并不比关系数据库强大,由于对 Join 的弱支持,功能会弱许多。设计关系模型的时候,通常只需要考虑好数据直接的关系,定义数据模型。而设计文档数据库模型的时候,还需要考虑应用如何使用。因此设计好一个的文档数据库 Schema 比设计关系模型更加的困难。除此之外,由于文档数据库事务的支持也是比较弱,一般 NoSQL 只会提供一个行锁。这也给设计 Schema 更加增加了难度。
接下来,颜开从模型设计部分提炼出六种反模式。
反模式一:惯性思维 / 沿用关系模型
由于文档数据库不支持 Join(包括和外键息息相关的外键约束) 等特性,习惯性的沿用关系模型有的时候会出现问题。需要利用起文档数据库提供的丰富的数据模型来应对。
颜开提出一个例子,指出这个例子存在两个问题:
- 存在描述多对多的关系表
- 没有区分"一对多关系"和“多对一关系”
颜开提出一个正确使用的场合:
关系型模型是非常成功的数据模型,合理的沿用是非常好的。但是由于文档数据库的特点,需要适当的调整,这样得出的数据模型,尽管性能不是最优,但是有最好的灵活性。并且也有利于和关系数据库转换。
反模式二:处处引用客户端 Join
这样的坏处是:
- 手动 Join,麻烦且易出错。
- 多次查询。如果引用过多,查询的时候需要多次查询才能查到足够的数据。
- 事务处理繁琐。
解决方法是:
在以下三种场合,适当使用内联数据结构。
- 使用内联可以解决读性能问题,明显减少 Query 的次数的时候。
- 可以简化数据模型,化简表之间的关系,而同时不会影响灵活性的时候。
- 事务可以得到简化为单行事务的时候
正确的场合是:
范式化的使用场景,文档数据库会被多个应用使用。由于数据库设计无法估计多个应用现在及将来的查询情况,需要极大的灵活性。在这个时候,使用引用比内联靠谱。
反模式三:滥用内联后患无穷
有四种主要症状:
- 妨碍到查询的内联
- 无限膨胀的内联
- 无法维护的内联
- 盯死应用的内联
反模式四:在线计算
主要症状:有一些运行时间很长的 Query, 由于有聚合计算,索引也不能解决。随着数据量的增长,逐渐成为性能瓶颈。
坏处有二:
- 影响用户体验。
- 影响数据库性能。
解决方案:要权衡聚合操作是否必要,是否需要实时完成。如果务必要,可采取离线模式。如必须实时,则可以新建一个字段,用“incr”这样的操作,在运行的时候,实时聚合结果。如果逻辑比较复杂,或者觉得大量“incr”操作给数据库系统带来了压力,可以使用 Storm 之类的实时数据处理框架。总之,要慎用长查询。
反模式五:把内联 Map 对象的 Key 当作 ID 用
其症状是:
文档数据库支持内联 Map 类型。将其中 Map 的 Key 当作数据库的主键来用。
颜开指出:“这个反模式很容易犯,因为在编程语言中 Map 数据结构就是这么用的。但是对于数据库模型来说,这是不折不扣的反模式。”
主要坏处有二:
- 无法通过数据库做各种 (><=) 查询。
- 无法通过索引查询。
解决方案:使用数组 +Map 来解决。。Map 类型中的 Key 是属性名,Value 是属性值。这样的用法是文档数据库数据模型的本意,因此其提供的各种功能才能利用上。否则就无法使用。
反模式六:不合理的 ID
症状:使用 String 甚至更复杂数据结构作为的 ID,或者全部使用数据库提供的自生成 ID
两个坏处是:
- ID 混乱。
- 索引庞大,性能低下。
解决方案:
尽量使用有一定意义的字段做 ID,并且不在其他字段中重复出现。不使用复杂的数据类型做 ID,只使用 int,long 或者系统提供的主键类型做 ID。
在文章最后,颜开用一个表格涵盖了所有反模式,读者可点击原文链接查看。
颜开最后说道:
现在关于 NoSQL 数据模型设计模式的讨论才刚刚起步,将来也许会逐渐自成体系。对于列数据库和 Key-Value 的反模式,笔者等到有了足够积累的时候,再和大家分享。
评论