Golden Era's Blog

如何设计图片表

前言

如何管理网站上数量众多的图片,一直是个令人头疼的问题。是将图片集中在一张表方便管理呢还是为分散到相关文章、商品中方便业务呢?

由图片可能产生的一些应用场景,例如将图片分组用于图片复用,多种类型 banner、多张 header image,文章里面的图片,商品详情里的图片,相关推荐的图片等等,在这些不同的应用场景之下又该如何设计相关表?如何设计保持拓展性?如何在便利与扩展之间做出一个平衡?这些问题接下来的会一一讨论。

图片表的两种设计

大致上,存储图片有两种方式,直接存储和间接存储。直接存储指把所有图片信息都保存在相关表的某个字段中,例如 table_artical.header_pic,间接存储指使用第三张表来关联图片表和文章表。下面具体来看看两种存储方式的区别和优缺点。

直接存储

这里有一张文章表,下面是部分字段
table_article:

id title content header_pic
1 one article one content [{“pics”:”1”,”url”:”http://image.store.com/1.png"}]
2 two article two content [{“pics”:”2”,”url”:”http://image.store.com/1.png"}]

每篇文章都设置了一张头图,看起来好像没什么问题。现在有了另一个需求,每篇文章要添加一张尾图,那么如何?再添加一个字段

id title content header_pic foot_pic
1 one article one content [{“pics”:”1”,”url”:”http://image.store.com/1.png"}] [{“pics”:”3”,”url”:”http://image.store.com/3.png"}]
2 two article two content [{“pics”:”2”,”url”:”http://image.store.com/1.png"}] [{“pics”:”4”,”url”:”http://image.store.com/4.png"}]

到这里,我想你应该明白了。这种存储方式不容易扩展,每次新增加一种图片类型都需要新增字段。而且图片字段里面的内容是以字符串形式来存储的,每次更新都必须整个字符串,不能做到只更新图片详情里的某个字段,而且这种方式保存的图片不利于图片的集中管理和检索(虽然图片检索的场景较少)。

优点:

  1. 图片随相关表管理,直观
  2. 图片存取方便

缺点:

  1. 纵向扩展不便,需要通过添加字段形式来增加新图片类型
  2. 图片不能自定义排序
  3. 不方便检索

间接存储

文章不存储相关图片
table_article

id title content
3 three article three content
4 four article four content

这是图片表,图片存储在这,其中 imageable_id 表示存储相关表记录ID。例如在这个例子中,ID为3的文章里需要一张头图,那图片表里的imageable_id存储的就是3,通过type来确定图片的其他属性。
table_images

id url type imageable_id weight
1 http://image.store.com/1.png head 1 1
2 http://image.store.com/1.png head 2 2
3 http://image.store.com/3.png foot 1 3
4 http://image.store.com/4.png foot 2 4

这个表设计通过 type 和 imageable_id 两个维度来唯一确定一张图片,例如:

#获取ID为2的商品头图
ImageClass->getImage("head", 2);

其中 imageable_id 显然是可以重复的,例如某篇文章和某件商品的ID都是2。但这里有个问题,如果两种图片类型都是header image呢?两种解决方案

扩大type维度的表示信息

id url type imageable_id weight
1 http://image.store.com/1.png goods_head 1 1
2 http://image.store.com/1.png article_head 2 2
3 http://image.store.com/3.png goods_foot 1 3
4 http://image.store.com/4.png article_foot 2 4

这样的优点是不用新增字段,缺点是 type 字段表示的信息脱离了字段本意,一定程度上增加了理解成本

#获取ID为2的商品头图
ImageClass->getImage("goods_head", 2);

新增加一个维度(字段)location

增加一个字段 location,但这样就稍显繁琐。例如,我要得到商品的某张头图

#获取ID为2的商品头图
ImageClass->getImage("goods", "head", 2);

或是在上面的基础上加一层封装

#获取ID为2的商品头图
ImageClass->getGoodsImage(head", 2);

优点:

  1. 图片集中管理
  2. 拓展方便
  3. 图片可自定义排序

缺点:

  1. 存取图片不方便,需要多做一次查询

总结

如果图片较多,需要频繁存取,例如商品的例子,可以考虑第一种;如果是为了图片集中管理,或是有可能出现检索场景,建议使用第二种。虽然上面只提到了两种图片存储方式,但真实项目情况比较多,要注意结合实际应用场景去做适当的变化和扩展。但是万变不离其宗,图片存储的核心也就是这两种,是独立存储?还是一起存储?都需要根据当时的约束、资源以及开发场景作适当的取舍。

根据目前的一些项目经验来说,同一个项目两种方式都有采用。所以,不要拘泥于某种方式。当为了仅仅保持这种“一致性”而让你耗费更大的精力去处理,这时候你就要考虑换一种方式了。记住,没有银弹!

参考

  1. Active Record Associations
  2. v2ex 上的讨论