之前的两篇笔记中谈到了从单库扩展到多库以承载更多的请求量以及单库(表)拆分成多库(表),打破单库的性能瓶颈。
这都是为了应对大数据量下的措施。
然而,除却数据量外,还有一个极其影响单库性能的因素——数据的组织方式。
对于关系型数据库,我们可以尝试在一定程度上改变数据的组织方式,即反范式化(Denormalization)
关于范式
可以参考以前做的笔记:
《MySQL——数据表设计三大范式》
设计范式相当于数据层的设计模式,对数据表进行解耦,使单表信息更加内聚,彼此边界分明,依赖关系更加清晰.
试想,如果相同的信息在多行中重复出现,不相干的信息也凑在同一张表中,就很容易出现一些异常情况:
- 更新异常:只更新单行,就会出现逻辑上的不一致
- 插入异常:无法只插入部分信息,除非让其它列先留空
- 删除异常:删除部分信息的同时,可能会波及其它无关信息
范式化的弊端
在这些设计范式的约束下,相关联的信息被存储到了不同的逻辑表中,以致于经常需要多表联查(join操作),关系越复杂,连表查询越慢。例如分成客户表和订单表和商户表,当我们要执行的操作与这张表的部分信息都有关时,就需要进行多表join。
那么,有办法能改善查询性能吗?
- 允许 DBMS 存储额外的冗余信息,例如索引视图(indexed views)、物化视图(materialized views),但仍遵从设计范式
- 增加冗余数据,减少join操作,打破设计范式(即反范式化)
反范式化
所谓反范式化,是一种针对遵从设计范式的数据库(关系模式)的性能优化策略。
反范式化不等于非范式化(Unnormalized form),反范式化一定发生在满足范式设计的基础之上。前者相当于先遵守所有规则,再进行局部调整,故意打破一些规则,而后者全然不顾规则。
反范式化通过增加冗余数据或对数据进行分组,牺牲一部分写入性能,换取更高的读取性能:
在设计范式的约束下,数据表中没有冗余信息(某个数据只存放在某张表的某个单元格中),为了得到某个数据可能需要一系列的跨表查询,因而读操作性能不佳,但写操作很快,因为更新数据时只需要修改一处。
反范式化就是要打破这种约束,把某些数据在不同的地方多放几份,以加快数据检索速度。
具体操作如下:
- 存一些派生数据:类似于往 Redux Store 中塞计算属性,把需要频繁重复计算的结果存起来,例如在一对多关系中,把“多”的数量作为“一”的属性存储起来
- 预先连接(pre-joined)生成汇总表:把需要频繁join的表提前join好
- 采用硬编码值:把依赖表中的常量值(或者不经常变化的值)直接硬编码到当前表中,从而避免join操作
- 把详情信息纳入主表中:对于数据量不大的详情表,可以把全部/部分详情信息塞到主表中,以避免join操作
反范式化的代价
- 失去了数据完整性保障:打破范式,意味着之前通过范式化解决的更新、插入、删除异常问题又将重新冒出来,也就是说,冗余数据的一致性要靠 DBA 自己来保证,而不像索引视图等由 DBMS 来保证
- 牺牲了写入速度:由于反范式化引入了冗余数据,更新时要修改多处,但大多数场景都是读密集的,写入慢一点问题不大
- 浪费了存储空间:存储了不必要的冗余数据,自然会浪费一些存储空间,但空间换时间一般是可接受的(毕竟内存、硬盘等资源已经相对廉价了)
参考
http://www.ayqy.net/blog/database-denormalization/