开头又臭又长,建议跳过,留着娱乐时间再读。直接从下一集开始阅读
数据存储与访问——理想与现实的妥协
在IT界,从来没有一个系统边界可以像数据库与应用层之间这样不断摩擦,冲突激烈,没推倒前的柏林墙庶几近之。不同的是同胞之间的藩篱终于在两德人民之间轰然倒下,而数据库与应用层的斗争只会光华灿烂,鸡飞狗跳。鹅,写错了,是光华灿烂,旦复旦兮。几年不读圣贤书,已经赶不上语文课代表啦。
是的,应用层与数据访问层的关系,只会在冲突和妥协中不断发展和变化,周而复始。
为了吸引用户,满足需求,数据库开发者总是在努力丰富服务器端的功能。从Key/Value到层次存储,网状关联,再到平面表,再到关系数据库,再到复杂数据类型和过程化编程,再到事务、存储过程、甚至嵌入式混合编程,以及咸鱼翻生的MapReduce,千奇百怪,不一而足。(当然,类似分布式存储,裸设备访问这些NB的性能优化技术和存储技术也在不断发展,但这不是今天我们要讨论的问题。)
而为了吸引用户,降低学习成本,提高占有率,应用层开发者总是在努力提高代码的通用性。于是,在第一代数据库SDK直连的理念后,顺理成章的出现了ODBC这样的通用驱动桥接层。由于各种各样的原因,ODBC始终也没有实现一统天下的目标,JDBC等平台专用链接库层出不穷,就连老东家微软都忍无可忍推出了ADO和ADO.NET,于是,这一层的访问方式基本稳定在了一种运行时一个主流库的态势上,Java是JDBC、.net是ADO.net,Perl是DBX,Python统一这一层的是DBAPI。
但是,驱动桥接是面向数据行的,对于编程并不方便,需要应用开发程序员大量编写重复代码,手工维护连接,遍历得到的游标记录集等等,这甚至对于数据库服务器也不够友好。于是,我们看到,ADO.net中开始强烈的倾向一种自动生成对象结构的自动化编程趋势,而这个思想在Java中最先成为一个诱人的产品——Hibernate,这就是ORM。
其实大家都明白ORM付出了比较高的性能和功能代价,但是ORM对生产力的提升是不可抵挡的诱惑,各个平台纷纷陷落,还出现了不同的框架自己实现ORM的场面,Python尤其明显,这对于忠诚的"统一标准"信徒,简直是一件无法无天大摇其头的荒唐事。
于是,现在对于数据存储访问,就出现了一个相对比较稳定的分层,最下面是RDBMS以及其它数据库,不过这里我们只讨论最主要的RDB应用;上一层是各种桥接层,现在的趋势是每种数据库针对每种运行时提供自己的桥接层,以达到最优的功能和性能接口,而过去由ODBC、BDE、OLE DB这些系统桥接工具提供的通用性和移植性的黑锅,就由Hibernate们背了起来。
也许Python的生产力真的是太高了,以至于即使出了几种广受好评的ORM(例如SQLObject、SQLAlchmy、Storm)之后,Django、Web.py、Web2py等web框架还是实现了自己的ORM,这其中有些是在通用ORM上实现的,有些则是框架直接基于数据库访问接口组建的。而Web2py的DAL(Database Abstraction Layer),算是其中相当有趣的一个。
DAL的取舍
如同前面所介绍的,为了使得ORM组件吸引更多的用户,获得更大的通用性,通常都会牺牲数据库层的功能,作为一个年轻的ORM工具,DAL只能做到对最常见,也最简陋的MySQL和SQLite为目标进行了支持。事实上,即使MySQL的编程语法,也做不到完全的支持(至少,MySQL的全文检索就没有支持)。调侃一下,我印象比较深刻的数据库,如果编程能力打个分,大概是下图这个样子。http://picasaweb.google.com/March.Liu/Blogger0=2#5343071442111853506
忘了加sqlite了,不过那东西也就是个40分的样子。而DAL能直接支持的,也就在这个40分的程度上。
不幸的是,我工作中用到的,恰恰是那个编程能力最强的,在我看来可以给100分(倒不是说就是满分)的PostgreSQL。
有功能不用,倒是不一定就会有什么不好,但是如果可以充分了解ORM层和DB的功能特性,就可以尽可能的发挥这两层工具的能力,提高工作效率和产品质量,精益求精永远都是一件好事。
这里简单介绍几个DAL及相关的技术特色,具体的技术细节凡是可以直接在文档中查询到的,这里就不细讲了。
数据库连接定义
DAL用来连接数据层的,是DB,对应通常的关系型数据库,是SQLDB对象,对应GAE Bigtable,则是GQLDB,这个对象是DAL整个访问操作的核心。当然,一个web2py实例内可以同时存在几个DB对象。
首先DAL的构造接口非常简单,只需要一个URI格式的字符串。例如,对于astinus项目的企业版,这个uri是postgres://astinus:march@127.0.0.1/astinus,包含了用户名、口令、服务器种类、位置、数据库名。虽然比起逐个参数进行指定的方式略为复杂,但是由于格式非常符合我们对于web url的使用经验(其实是现有uri的概念,才有了url这个具体的东西),所以非常好记。DAL支持的完整的DB列表及连接方式,可以在DAL的文档 http://web2py.com/examples/default/dal 中读到。
另外DAL针对一些数据库,例如Postgres,还提供了一个可选参数:pools,也就是数据连接池。有兴趣的朋友可以在glon/tools.py中找到这部分代码。
当然,DAL的连接定义也不是完美的,这里主要问题表现在两个方面:
- 一定要指定password,有些数据库允许密码之外的身份验证方式,这样可以提供更可靠的安全性,但是DAL目前还只能使用密码验证。
- 不能指定schema。对于PG和Oracle,不得不说是个遗憾。其实实现schema支持并不是一个很麻烦的事情,Web2py社区早有用户提过一些方法,我自己也想到过几个,例如采取类似trac DB URI的n参数方式就不错。目前我对此采用了一个妥协的方法:设定连接用户在数据库环境中的的search_path变量。这样就允许用户访问postgres数据库中public之外的schema,以及可以指定schema搜索顺序。
DAL通过调用define_table方法,可以定义数据表,表结构中的field字段由SQLField对象构造。这种结构使得DAL可以定义丰富的字段约束信息,DAL提供以下特色功能:
- 假设有SQLDB(或GQLDB对象)对象db,通过db.table或db['table']就可以访问指定的表table,再进一步可以访问表中的字段:db.table.fileld。这样就可以用应用层程序员习惯的对象结构构造查询和访问数据
- DAL的SQLField对象可以提供非常丰富的数据约束条件,通过SQLFORM对象,可以快速构造带有页面、应用服务器、数据库三层数据验证约束的数据访问层次。
- DAL可以同步数据库结构,也可以通过migate=False禁止这中同步。
- Web2py提供内置的Database Administrat 界面。
首先,同样是schema问题
- postgres提供强大的text类型(不同于MySQL等数据库中常见的Text类型),但是在DAL中string无法充分利用这一功能。
- postgres提供一个基本功能的XML类型,支持xpath 1.0和dom访问,但是同样DAL无法利用。
- DAL定义的表结构一定要带有一个整数自增字段列作为主键,这一点不如django灵活。
- DAL定义的表结构,外键定义非常简单好用,但是也不够灵活,不能指定非主键列(可以放在验证中,这样可以看作是实现了外键约束)。
- DAL可以支持导出数据,但是不如Django按对象导出那么强大。
- DAL的pg实现使用char(1)作为逻辑类型,而PG自带了真正的boolean。
- DAL无法映射存储函数,而这时PG中非常强大的一个功能。
DAL 的检索
DAL的数据检索(对应SQL select)做的很有意思。它可以支持:
- 局部化选取,只读取表中一部分字段。
- 支持连接查询,甚至支持左外连接,外连接在一些ORM工具中没有提供。
- 由上两个技术,DAL可以支持相当灵活的查询集组合。
- 子查询。
- 统计计算,而且这个统计计算是基于SQL生成和惰性计算的,将计算放在服务器端,这是一个非常好的特性,而且使用起来很友好。
- 服务器端排序和分页访问。得益于新SQL标准的关键字limit,DAL可以比较方便的提供分页查询功能,不过这个功能在MySQL上性能很糟糕(就海量数据集的场景而言),这个黑锅我想不能由DAL背。
- 将查询集直接生成为多种输出,如XML、XMLRPC、CVS、JSON等。或者生成为SQLFORM以供web请求使用。
DAL的检索基本上是如下格式:
- db(查询条件).select(字段列表,**{附加参数})
- db(filter).select(map, reduce)
同样,DAL查询也存在一些问题:
- DAL不支持PG的全文检索运算符@@。
- DAL不支持PG的正则表达式运算符~和~*。
- DAL不支持FULL Join和Right JOIN,当然,这并不是很大的问题。
- DAL不支持跨db对象的联接,即使它们指向同一个数据库。这一点我认为是合理的。
- DAL查询集不支持完整的序列运算,不能切割,不能反向索引,这一点我认为是一种遗憾,应当改进。
DAL 的update、insert、delete操作
DAL也同样支持局部化的update/insert操作,这同样是很多ORM做不到的。在数据写操作时,DAL也有一些有趣的功能实现:
- DAL允许 data={...}这样的局部化批量赋值,而db.person[0]=dict(name='Max') 则是一个非常巧妙的插入操作(DAL的id是从1开始计数的)。
- DAL允许用Python的 del 关键字删除数据记录。
- DAL的db对象支持commit和rollback。
- DAL增删改查都可以方便的cache。
- 可以从CSV批量导入数据。
- 内置的curd辅助工具。
- 可以方便的生成多种数据提供或RPC服务。
- ADO.net 支持定义增删改查对应的存储过程,DAL不提供此类功能。
- DAL不能像Django一样提供基于业务对象的数据导入(XML或JSON格式)。
- 仿dict接口的快捷访问非常漂亮,但是受限于id列,不能像django的get方法或ado.net的command.ExecuteScala(方法名记不清了)r一样直接构造单个对象选取操作。
- DAL的发展与讨论
- 无id列,用户自定义主键的表结构,至少应该支持uuid。
- 可以方便的指定schema范围或直接引用。
- 更方便的外键定义。
- 支持函数,最好可以自定义返回集结构,可以select query。
- 至少应该提供参数化SQL执行功能。
- 基于业务模型的数据迁移能力。
- 更友好和强大的数据库管理工具。
- 更充分和合理的利用PG的数据类型,如text和boolean。
- 支持复杂数据结构,如数组、几何类型等。
--
杀人放火金腰带,补路修桥无尸骸。
……
劉鑫
March.Liu
没有评论:
发表评论