首页 » 读书笔记 » SQL反模式

SQL BillKarwin

《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_incrementserial。伪主键很有用,但不是声明主键的唯一解决方案。

对队列应用唯一约束,如:

unique key(bug_id, product_id)

程序员拒绝在设计中使用组合键,就好像数学家拒绝使用二维或三维坐标系。虽然那样的确会使得几何学变得简单,却无法描绘我们的真实世界。

序列通过将运算和事务在逻辑上分离来解决并发问题。Mysql通过last_insert_id()获取在会话周期内,序列生成的最后一个值,而不会有并发问题。

第6章 实体-属性-值

本章反模式是:EAV(实体-属性-值)的滥用。

当我们需要支持:动态、可变属性时,可能就用使用EAV反模式。

传统SQL中,一张表中,每一行代表一个对象,对象的属性是事先确定的(也就是列)。如果属性不确定,或属性需要随时动态增删,就无法用传统SQL表来存储。对这个问题的解决办法可以是:创建一张新表, 把属性当作行来存储 。属性表的每条记录包括3列:

  1. 实体。通常是指向父表的外键,父表的每条记录表示一个对象。
  2. 属性。等同传统SQL中的列名。
  3. 值。不同属性对应的值。

例如存在一个商品表,商品的属性值我们难以穷举,而需要商家动态的添加,例如商品的产地、尺寸、颜色、年代、工艺等等都会和价格、库存关联,我们可以设定商品表有价格、库存列,但没法设定属性值列。

第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的《领域驱动设计:软件核心复杂性应对之道》一书中,介绍了一个更好的方案:领域模型。

让模型设计围绕着程序逻辑,而不是数据库层面。

分享

0