在一次灾难中,你的服务器硬件机架整个倾倒,导致大量硬盘损坏,庆幸的是IT部分灾备做的比较好,你们有一份和上次备份一样的完整副本。但是在冒烟测试中你们发现所有的图片都不能加载,这是才想起来所有的文件都存在/var目录下。

目的:存储图片或其他媒体大文件

    本章的目标就是要存储这些图片并且将其和数据库实体管理起来,当查询这些实体时,我们需要确保同时能获取与其关联的图片。

反模式:假设你必须使用文件系统

    理论上来说,图片是一个字段,在account表中可能有一个portrait_image列。

1
2
3
4
create table account(
-- some columns
portrait_image BLOB
);

    如果你需要一条记录对应多个文件,比如bug表对应多个截图,你可以建立从属表

1
2
3
4
5
6
7
8
create table screenshot(
image_id serial not null,
bug_id bigint unsigned not null,
screenshot_image blob,
caption varchar(100),
primary key (image_id, bug_id),
foreign key(bug_id) references bug(bug_id)
);

    这些都不难理解,最纠结的在于,应该使用将二进制格式存储在blob类型中,还是将文件保存在文件系统中,然后用一个varchar类型来保存路径。这两种方式都有其存在的道理,一般程序员会选择第二种,接下来的几节就是要指明这样设计面临的风险。

文件不支持delete

    第一个问题就是垃圾回收,如果文件在数据库之外,你想删除包含这个路劲的记录,没有什么办法能自动的删除文件,除非你新写一个脚本程序来监视其“无人领养”的图片。

文件不支持事务隔离

    通常,当更新或删除数据,在commit之前,所有的更改对其他客户端不可见。然而数据库之外的操作并非如此,当你删除一个文件,对于其他的客户端来说就立刻无法访问该图片,同时你修改文件,其他客户端也可以立即看到,而不是看到在事务提交之前的文件状态。

文件不支持回滚操作

    出错情况下,或者程序逻辑要求取消变更时,事务回滚再正常不过了。但是当你执行一句delete语句来删除一条记录并同时移除对应的截屏文件,然后你回滚了这个操作,被删除的数据回来了,但是文件没了。

文件不支持数据库备份工具

    多数数据库都提供了备份工具,如,mysql提供了mysqldump,Oracle提供了rman,postgreSQL提供了pg_dump等。这些工具都不知道如何将路劲引用的那些文件包含在备份操作中。所以你需要记住执行两步来备份:使用数据库备份工具,然后使用文件系统备份工具手机外部文件。即使如此,你也不能保证外部文件和数据库备份是否一致

文件不支持SQL的访问权限设置

    外部文件会绕开grant 和 revoke SQL语句设定的访问权限,SQL权限管理着对表和列的访问,但他们不能应用到外部文件。

文件不是sql数据类型

    screenshot_image保存的是路径,数据无法保证这个路劲是否正确,当你修改、移动文件时,数据库也不会更新该字段,这些都依赖于你程序的逻辑。

如何识别反模式:

    典型的使用反模式的项目通常没有考虑以下几个或者全部问题

  1. 数据备份和恢复的过程是怎样的?怎么对一个备份进行验证?你有没有在一个干净的系统或者别的系统上对备份回复的数据进行测试?
  2. 图片文件堆积在那里,还是当他们孤立的时候就从系统中移除?移除他们的过程是怎么样的?这是一个自动的还是手动的过程?
  3. 系统中的哪些用户有权限查看这些图片?进入权限是怎么限制的?当用户请求查看他们无权查看的图片时会发生什么?
  4. 我能撤销对图片的变更吗?如果能,是应用程序来恢复图片之前的状态吗?

合理使用反模式:

    如下是将图片或者大文件存储在数据库之外的好理由

  1. 这个数据库在没有图片的时候能精艺很多,因为图片相比于简单的数据类型来说大很多;
  2. 当不包含图片时备份数据库会更快并且备份的文件更小。你必须额外的执行一次文件备份,但这些比备份一个大型数据库要更容易管理;
  3. 如果图片是存储在数据库之外的文件系统中,对图片的预览或者编辑就能够使用更简单直接的处理方式。如果这些图片存在文件系统中的好处是重要的,那么可以将大文件存储在数据库之外。
  4. 一些数据库产品提供了特殊的SQL数据类型,Oracle里面的BFile,SQL Server2008里面的FileStream。

解决方案:在需要的时候使用BLOB类型。

    如果在以上反模式所描述的问题对你的程序有影响,你需要考虑将文件保存到数据库中。如果你将文件保存到数据库中,以上反模式的风险都将迎刃而解。而且几乎所有的数据库产品都支持BLOB类型,支持你存储任何二进制数据。
    如果你考虑到大文件blob存储不了,大多数据库都帮你考虑到了这一点,比如mysql的mediumblob类型最大存储16M,Oracle的longraw或blob最大支持2GB或4GB的长度。
    如果你的数据库一开始是以文件的形式保存的,一些数据库也提供了加载外部文件的函数,如mysql的load_file()

1
update screenshot set screenshot_image = load_file("image/usql.png) where image_id = 123;

    你还可以将二进制直接从blob中提取并显示

1
select screenshot_image into dumpfile 'image/usql.png' from screenshot where image_id = 123

总结

    存储在数据库之外的数据不由数据库管理