11 月 19 - 20 日 Apache Pulsar 社区年度盛会来啦,立即报名! 了解详情
写点什么

从数据库查询中寻找设计问题

  • 2014-05-26
  • 本文字数:2147 字

    阅读完需:约 7 分钟

在数据库中对数据的操作可以分为两类:查询和命令。由于命令会修改状态(数据),因此会得到开发团队很高的关注。甚至我所在的组织里,对故障的分级也是对此参照的:如果是数据显示错误,则属于轻度故障;如果是数据更新错误,则属于严重故障。但查询并不总是善类,比如在《基于Oracle 的SQL 优化》一书中,作者曾提到一个案例:优化一个关联了18 个表的查询。在赞叹作者高超的SQL 调优技术的同时,我不禁想到是否真有必要编写如此复杂的SQL?复杂查询带来可能不仅仅是性能问题,其背后隐藏的还有可能是不恰当的架构和设计。查询应该受到更多的关注。

控制条件查询的复杂度

企业应用程序的开发人员经常和数据库打交道,有时会过于依赖SQL 强大的查询能力,随着需求的演化,SQL 可能会越来越复杂,难以维护。

通用的条件查询

比如考虑一个酒店预订网站,客户在下单后的30 分钟内需要完成支付或是提供信用卡担保,否则系统会自动将订单取消。开发团队使用定时任务来筛选出“过期”的订单:

图表 1 使用SQL 条件查询

看上去也挺简单,但是订单作为一个主要的业务对象,会有许多查询的需求,一般都会实现动态条件查询。

图表 2 构造条件查询,要求orderRepository 的实现能够动态拼接SQL 语句

条件查询虽然好用,但测试起来可不轻松。因为在依赖数据库的自动化测试中准备、清理数据相比单元测试更麻烦。每个测试用例的上下文最好是独立的,这样不容易因为测试用例执行顺序问题导致测试结果不一致。此外,测试数据相互独立对于维护性也很重要,当测试用例达到一定规模时,“复用”测试数据以减小工作量的初衷是好的,但是很容易让测试用例的设计顾此失彼,适得其反。不必要的条件查询和其组合加剧了这个问题。另一方面,随着需求演化,有些查询条件还需要额外关联一些表,比如,现在要求VIP 会员不受自动取消过期订单的约束。SQL 可能变成了:

图表 3 随着需求演化,SQL 开始变得复杂

于是不断有新的条件和关联表被加进来,而测试时要准备的数据也越来越多。在实际项目的iBATIS sqlmap 文件中(此处的例子都经过简化),改造前拼接动态查询的篇幅超过了200 行,可以根据条件动态关联对应的表,貌似十分“强大”,实际上看起来都很费劲,添加新功能经常导致部分查询的条件拼接不完整,非常痛苦。

查询 “结果”

对于按客户名称搜索订单,还是条件查询比较好用。但对于特定目的,又带有计算的查询就不是那样了。实际上,我们可以把计算的结果用来实现查询,而不是在查询中去计算。开发团队为了解决这个问题,后来改造了下单的过程,计算并保存了订单的截止时间并为其实现了一个专用查询。

图表 4 保存计算的结果,计算在下单时完成,很容易做单元测试

图表 5 查询结果

随着设计的变化,测试策略也相应调整,现在由ApplicationEvents 的单元测试来验证关键的业务条件,而查询可能过期订单的测试由于和订单这个“热门”隔离开了,测试的数据准备和清理不容易发生冲突,也变得更为简单。

慎用数据库集成

在实现修改状态的功能时,开发团队往往会定义良好的接口和服务契约来降低构件间的依赖。而在处理查询功能时,开发团队有时会迫不得已使用数据库集成,但这种方式却悄悄地削弱了解耦的努力。

数据来自于两个系统

让我们回到酒店预订网站的例子,现在希望提供住客点评酒店的功能。但为了防止“灌水”,要求对每张订单只能点评一次。

图表 6 订单中心可以点评

在系统实现上,酒店点评是独立于酒店订单的一个应用程序。如果只是在查看订单详情时才显示点评的入口,那么开发团队会很自然地想到通过应用程序集成,比如由酒店点评系统提供一个查询某张订单是否有对应点评的服务。不过若在订单列表就要显示的话,开发团队觉得还是直接使用数据库集成更简单一些,而且还减少通信次数。改造一下订单列表的查询语句,在其中嵌套点评计数的语句:

图表 7 数据库集成

查询“本地”数据

但很快,开发团队发现这也许不是一个明智的主意。一方面,这个方案会增加订单查询的持久化测试用例,而且为了能够部署流水线中运行这些测试,还不得不把本属于酒店点评系统的数据库脚本复制过来。而另一方面,开发团队发现这个方案其实要求两个系统合用一个数据库,或至少使用DBLink 才行。这种部署灵活性上的缺陷也给部署流水线的准备带来了不少的麻烦。

图表 8 部署限制

于是,团队调整策略,在t_hotel_res_order 上增加了点评数量的冗余字段,而该数据采用消息队列来同步,这样查询就可以在“本地”实现了。

图表 9 使用MOM 集成

Fail fast Learn fast

一般来说,一个功能总是可以通过多种方式来实现,除了性能之外,可维护性也是很重要的决策因素。开发团队的经验固然很重要,但尽早开展测试来评估、验证方案往往更科学。

参考实现方案对应的测试方案及成本:如果要为某个查询的测试用例准备 5、6 个表的数据,那么值得考虑一下是否有查询“结果”的方案。尤其是报表类的功能,非常容易演化成长篇的 SQL 语句。

参考实现方案对应的部署方案:尽早建立部署流水线,尽早规划部署资源和方案并演练。如果出现不必要的“限制或约束”,那么值得考虑更换集成方案。


感谢侯伯薇对本文的审校。

给InfoQ 中文站投稿或者参与内容翻译工作,请邮件至 editors@cn.infoq.com 。也欢迎大家通过新浪微博( @InfoQ )或者腾讯微博( @InfoQ )关注我们,并与我们的编辑和其他读者朋友交流。

2014-05-26 03:513452

评论

发布
暂无评论
发现更多内容

美团二面问源码卒!闭门偷学318页Spring深度手册,再战

Java 程序员 后端

腾讯内部Netty文档笔记,内容涵盖Netty基础+入门+中级+高级

Java 程序员 后端

老夫带你深度剖析Redisson实现分布式锁的原理

Java 程序员 后端

腾讯面试Java高频210题解析:Spirng+设计模式+Redis+MySQL

Java 程序员 后端

膜拜!阿里内部都在强力进阶学习springboot实战派文档

Java 程序员 后端

菜鸟程序员的超神之路——从校园到职场

Java 程序员 后端

落马阿里之后 开始我的恶补过程:技术笔记+面试知道+视频教程

Java 程序员 后端

编码习惯-函数编写建议

Java 程序员 后端

架构实战营模块4课后作业

胡颖

架构实战营

血拼一波算法:百度+Alibaba+字节+Tencent

Java 程序员 后端

解开疑惑之:全面解析腾讯会议的视频前处理算法

Java 程序员 后端

耗时4个月,阿里架构师打造java面试突击文档,10位朋友已拿offer

Java 程序员 后端

腾讯T3大牛-带你深入解析静态分派-&-动态分派原理

Java 程序员 后端

腾讯,阿里,小米等Java开发岗面试真题,kafka+Redis

Java 程序员 后端

菜鸟弱弱地问:找个薪资待遇差的工作能成长吗?

Java 程序员 后端

蚂蚁金服+拼多多+抖音+天猫(技术三面

Java 程序员 后端

腾讯某Java程序员为了肝出《300页图解网络知识》+《计算机底层操作系统

Java 程序员 后端

腾讯程序员熬夜码字:网络 IO 演变发展过程和模型介绍,仅此一篇

Java 程序员 后端

网关性能大PK,Spring Cloud Gateway让人大失所望!

Java 程序员 后端

网络编程三-原生JDK的BIO以及应用

Java 程序员 后端

网络编程四-原生JDK的NIO及其应用

Java 程序员 后端

聊一哈,新入如何优雅的跟老板打招呼

Java 程序员 后端

聊一聊Java中那些常见的并发控制手段(1)

Java 程序员 后端

腾讯Java岗面试,竟被“锁”给搞晕了?这样复习

Java 程序员 后端

自己把985道Java面试题整理细分26部分,五面成功面上滴滴

Java 程序员 后端

flutter版本控制第二节

坚果

flutter 版本控制 11月日更

蚂蚁金服5面,总结了49个面试题,遇到的面试官都是P7级别以上

Java 程序员 后端

聊一聊Java中那些常见的并发控制手段

Java 程序员 后端

肝到吐血!字节跳动技术专家耗时两年整理出1014页SSM开发实战,你还没看过吧!

Java 程序员 后端

腾讯社招(Java岗)四面已拿offer,问的很全面,几大块全涉及

Java 程序员 后端

见了鬼,我JVM的Survivor区怎么只有20M了?

Java 程序员 后端

从数据库查询中寻找设计问题_数据库_周宇刚_InfoQ精选文章