软件架构领域中一个经久不衰的争论,其表述看似简单,实则难以解决:当需要实施某条业务规则时,它应该作为存储过程存在于数据库中,还是应该写入应用程序代码中?这个问题的答案将决定系统的测试、维护、扩展和演进方式。正如我们在今天的博文中所将看到的,这是一个值得仔细思考的问题。
我们究竟在决定什么?
业务规则是控制数据如何创建、验证、转换和删除的逻辑。例如,“如果客户账户被暂停,则无法下单”,或者“超过 30% 的折扣需要经理批准”。这些规则必须在某个地方得到执行。问题在于,这个“某个地方”是数据库引擎内部编译并缓存的存储过程,还是应用程序层中的类或函数。
存储过程是直接存储在数据库中的、带有名称的预编译 SQL 例程。它们可以接受输入参数,执行复杂的多步骤操作,并返回结果集或输出值。相比之下,应用程序逻辑存在于你的代码库中,并根据需要向数据库发出查询。它使用 Python、Java、C# 等编程语言编写,甚至可以是多种语言的混合。
存储过程的优势
存储过程确实具有一些极具吸引力的优势:
- 由于它们在服务器端执行,因此减少了网络传输的数据量:无需在应用程序内存中检索原始数据行并进行过滤,而是由数据库完成繁重的工作,并仅返回结果。这一点在数据密集型的报表生成或批处理场景中尤为重要。
- 存储过程还提供了一种天然的安全边界。数据库管理员可以授予用户执行存储过程的权限,同时不授予其对底层表的直接读写权限。这种严格的访问控制在数据治理至关重要的受监管行业中尤为重要。
- 此外,由于存储过程集中在数据库中,因此无论与数据库通信的是哪个应用程序——或是哪个客户端——它们都能始终如一地执行规则。
应用程序逻辑的必要性
反对方的论点同样有力:
- 应用程序代码的测试相对简单很多。对存储过程进行单元测试通常需要连接到实时数据库并仔细设置测试环境,而应用程序逻辑则可以使用模拟对象和内存中的伪对象进行测试。
- 存储过程通常也更难进行有效的版本控制,在拉取请求中更难进行审查,而且它们分散在各个数据库实例中,而非与代码库的其他部分集中存放。
- 现代的对象关系映射器(ORM)也使应用程序逻辑占据了主导地位。像 Hibernate、SQLAlchemy 或 Entity Framework 这样的工具处理了大量曾经被存储过程所推崇的冗余代码,同时将逻辑保留在开发人员日常工作的环境中。
- 最后,当需求发生变化时——而需求总是会变的——修改应用程序代码并部署新版本,通常比协调数据库模式变更要快。
寻找恰当的平衡
比较务实的团队不会将此视为非此即彼的抉择。对于数据库维护任务、复杂的报表查询以及必须在多个表之间保持原子性的操作而言,存储过程通常是最佳选择。而应用程序逻辑通常更适合处理领域特定的业务规则、验证逻辑,以及任何需要进行单元测试或快速迭代的内容。
一个有用的经验法则:如果规则本质上涉及数据的结构和完整性维护,那么数据库是天然的选择。如果规则涉及领域模型的行为方式,则应将其保留在应用程序中。
轻松使用存储过程
对于依赖存储过程的团队而言,拥有合适的工具将产生巨大的影响。Navicat 数据库管理和开发工具,如 Navicat Premium,包含一个专用的存储过编辑器,允许开发人员通过结构化编辑器来创建和编辑存储过程及函数。这两类对象均显示在统一的“函数”视图下——存储过程以“Px”为前缀,函数以“fx”为前缀——从而可以一目了然地区分二者。
除了编写功能外,Navicat 的 SQL 编辑器还提供自动补全功能,在你输入时会显示数据库对象(包括存储过程和函数名称),输入参数会高亮显示以便填写,且每个参数均可通过 Tab 键快速选择。在调试方面,Navicat 的调试组件支持设置断点、逐步执行程序、查看和修改变量值,以及检查调用堆栈——这些功能通常是专业级集成开发环境(IDE)所具备的。Navicat 甚至提供了一个 AI 助手,能够利用 Claude、ChatGPT、Gemini 等模型来帮助生成、解释和优化存储过程代码。
结语
关于业务规则应置于何处,并没有放之四海皆准的答案——只有需要理解和权衡的取舍。存储过程具备性能、安全性和跨客户端一致性;应用逻辑则具备可测试性、可维护性和开发效率。最优秀的架构会深思熟虑地确定各层承担的职责,并投资于能够最大限度提升所选方案生产力的工具。

