《SQL反模式》
SQL Antipatterns, Avoiding the Pitfalls of Database Programming, by Bill Karwin, 2010.
Berlinix 书评
什么是反模式?我对模式
的理解是:好方法的抽象,即是技术专家对久经考验的好方法的提炼、概括、抽象,而反模式
的意思则是错误方法的抽象。很多书籍都会告诉你遇到一个问题,应该如何处理,但实际场景千变万化,没有一种方法能处理所有问题,这时候,常见错误就被专家们看在眼里,笑在心里,并撰文呵斥。
《SQL反模式》是那种短小轻薄的技术书(250页左右),分25章,每章讲述一个SQL反模式案例,并提出解决方案。适合有SQL经验的初、中级用户阅读。在阅读过程中,会有较大的心理起伏,时而幸灾乐祸,时而为自己叹惋,因为这些错误我们或多或少也都遇见、跨越,或栽在其中。
第4章 需要ID
本章反模式是:大量使用id这个名字作为表的主键列名。应该使用更有意义的主键列名,如user_id, order_id等,而不是过于宽泛的id。
主键约束的意义:
- 确保一张表中的数据不会出现重复行。
- 在查询中引用单独的一行记录。
- 支持外键。
在表中引入一个对表的域模型无意义的新列来存储一个伪值,并将之作为表的主键,这样的主键列被称为伪主键或代理健。伪主键直到SQL:2003才成为一个标准,不同数据库对伪主键有不同名称,如Mysql中的auto_increment
、serial
。伪主键很有用,但不是声明主键的唯一解决方案。
对队列应用唯一约束,如:
unique key(bug_id, product_id)
程序员拒绝在设计中使用组合键,就好像数学家拒绝使用二维或三维坐标系。虽然那样的确会使得几何学变得简单,却无法描绘我们的真实世界。
序列通过将运算和事务在逻辑上分离来解决并发问题。Mysql通过last_insert_id()
获取在会话周期内,序列生成的最后一个值,而不会有并发问题。
第6章 实体-属性-值
本章反模式是:EAV(实体-属性-值)的滥用。
当我们需要支持:动态、可变属性时,可能就用使用EAV反模式。
传统SQL中,一张表中,每一行代表一个对象,对象的属性是事先确定的(也就是列)。如果属性不确定,或属性需要随时动态增删,就无法用传统SQL表来存储。对这个问题的解决办法可以是:创建一张新表, 把属性当作行来存储 。属性表的每条记录包括3列:
- 实体。通常是指向父表的外键,父表的每条记录表示一个对象。
- 属性。等同传统SQL中的列名。
- 值。不同属性对应的值。
例如存在一个商品表,商品的属性值我们难以穷举,而需要商家动态的添加,例如商品的产地、尺寸、颜色、年代、工艺等等都会和价格、库存关联,我们可以设定商品表有价格、库存列,但没法设定属性值列。
第25章 魔豆
魔豆(Magic Beans)。
Robert L. Glass认为:
80%的软件工作是智力活动。相当大的比例是创造性的活动,很少是文书性的工作。
MVC架构指:模型-视图-控制器。我们使用MVC就是同时使用设计模式和软件框架。这是拆分程序逻辑的技术。
- View:处理输出。
- Controller:接收用户输入,处理响应逻辑,委托给Module执行操作,将结果传递给View。
- Module:程序核心,包括输入验证、业务逻辑,与数据库交互等。
MVC使用者通常犯错在:仅把Module当作DAO(数据访问对象)。
Martin Flowler把ORM(对象-模型关系)概括为一种模式,叫Active Record
(活动记录)。ORM通常是数据库中某个表的映射对象,执行CRUD(Create, Read, Update, Delete)操作。而Active Record也就是将一个类与数据库中的一张表或一个视图关联。2004年起,Ruby on Rails使Active Record在Web程序开发框架中流行起来,很多Web程序框架将Active Record/DAO作为Module层来使用。然而,过度使用就成为一个典型的反模式:如果你的唯一工具是把锤子,那么就会将每个东西都看作钉子。
Joel Spolsky在2002年提出了抽象泄漏
这个词:抽象简化了一些技术的内部工作原理并让其更加方便调用。但当需要更高效的生成效率而不得不了解内部细节时,就称之为抽象泄漏。
在MVC架构中,把Active Record当作Module层来使用就是抽象泄漏的例子。将Module简单地当作DAO,鼓励开发人员将业务逻辑放在Module类之外去实现,通常这些逻辑就会被拆分到多个Controller类中,从而减少了Module本身的内聚行为,Martin Fowler称这种反模式为弱域模型。
把Module层应该处理的逻辑放到Controller层,当新增控制器时,难免重复创造轮子。
解决
不要把SQL查询语句传给Module对象,Module对象应该囊括了所有它需要的查询。
将复杂的查询代码写在Module对象里,并作为接口暴露出来。遵循DRY原则。
分离Module和DAO。Module和DAO/Active Record的关系是包含(has-a)而不是继承(is-a)。
Module负责创建DAO。一个聚集了一系列DAO的Module应该负责创建这些对象。Controller和View只使用Module暴露出来的接口,而不要处理与数据交互的逻辑(如传递SQL给Module,以求其返回执行结果)。
Module的接口应该是直接的(与业务逻辑直接相关,而不是过于抽象的接口),而不是数据库物理结构或CRUD操作。ADO/Active Record可以提供如find(), first(), insert(), save()之类的接口,但Module层的接口不要这么干。
将Module类和DAO解耦,并可以为一个DAO设计多个模型类。
框架很难给Module提供一个通吃的解决方案。
Graig Larman的《UML和模式应用》介绍了GRASP(通用职责软件分配模式),其中一些指导原则对分离Module和DAO尤其相关。Eric Evans的《领域驱动设计:软件核心复杂性应对之道》一书中,介绍了一个更好的方案:领域模型。
让模型设计围绕着程序逻辑,而不是数据库层面。