当前位置: 首页 > news >正文

在数据库中编程 vs 在应用程序中编程

原文地址 https://brandur.org/fragments/code-database-vs-app

数据库领域有一个长期存在的问题:你是更愿意将应用逻辑放在更接近数据库本身的存储过程和触发器中,还是置于数据库之上的应用程序代码中?

没有客观正确的答案,只有不同的观点。我浏览了 Stack Overflow 等地方的大量热门文章,并惊讶于普遍建议与常见事实的巨大差距。

可以肯定,大多数公司在应用程序代码中编写逻辑。开发者都不会想到编写存储过程,更不用说将领域逻辑放在存储过程中了。网络上很多人,却建议将逻辑放在数据库中。

先说个人观点:即使我因为类型、schema 和一致性而成为了关系数据库的拥护者,对把代码放到数据库中也还是持保留意见。将代码放入数据库,只在少数时候合适;即使合适,也要保持小规模、尽量少用。

对在数据库中编程的反对意见

数据库不适合应用程序编程的原因:

  • 不透明的后果

    一旦设置了触发器,如果不检查 schema,简单如插入一行的操作也可能产生严重的后果。

    同样的原因,我也反对 ActiveRecord callbacks 这类东西。

  • 调试、工具和测试

    对数据库内函数都比较困难。

    • 最多只能进行 printf 调试。
    • 很难使用开发者工具,比如用 LSP 完成代码(配置的噩梦 – LSP 必须主动与数据库交互,才能知道哪些关系和字段可用);SQL 函数会成为代码其他部分跳转到定义的死胡同。
    • 如果要(也应该)编写测试,就应该在应用程序代码中编写;这样…只要就地实现就可以了?

  • 部署和版本管理

    仍然可以对存储过程进行版本管理 – 只能通过编写新的迁移,就像数据库其他部分的版本管理一样。这就增加了更改代码的难度,毕竟部署其他应用代码肯定更容易。

    更改存储过程需要创建一个 CREATE OR REPLACE 函数,其中包含函数的整个实现(包括更改),这样就无法像使用 git blame 那样查看每行的历史记录。

  • 性能

    数据库逻辑与数据本身同在,所以有些情况下能提供最佳性能;但一些重要方面,却使性能更差:

    • 关系数据库通常是应用程序的单一阻塞点,其他应用程序代码部署在一组可以访问关系数据库的并行容器中。一个容器的应用代码容易扩展,只需部署更多的容器即可。数据库的扩展则比较困难。
    • 如果还需要运行数量未知的触发器,操作速度就会更慢。例如,当每一行都有一次隐藏触发,批量操作就要花费数倍时间。当然可以暂时禁用触发器,这就会失去触发器更多明显的好处。而且由于触发器不易被发现(参见上文「不透明的后果」),你可能无法明显感觉到操作速度变慢。

  • 程序化 SQL

    程序化 SQL 与 BASIC 和 COBOL 1 同属于最底层的编程语言,编写体验很糟糕,即使你熟悉。当然,可以激活扩展,从而使用其他语法更好的脚本语言,但你真的想让 Python 虚拟机在你的数据库中运行吗?

对在数据库中编程的支持意见

糟糕的意见

我在 Stack Overflow 上看到这样一些糟糕的论点:

  • 一致的实现:

    多个应用程序访问同一个数据库时,使用存储过程是保证它们使用相同实现的唯一方法。

    但出于多种原因,在应用程序之间共享数据库并不是好主意;在多个应用程序都可能向数据库写入数据的情况下共享数据库更糟(Schema 属于哪个程序?如何在 schema 变更时协调应用程序的部署?)。

  • 性能:

    存储过程的性能很高,因为它们与数据本身同位于数据库服务器上。

    这是事实,但依赖这一点很危险,因为数据库的可扩展性有限,任何利用这种局部性的做法都会给数据库带来很大压力。如上所述,将工作外包给可轻松扩展的应用程序代码会更安全、更具可扩展性。

  • ACID 一致性:

    触发器是保证 ACID 一致性的唯一方法。

    在一个看起来都是数据库专家的网站上发现这一点很奇怪。——并不是唯一的方法,不然为什么有数据库事务?

更好的意见

最后,有几个更好的理由支持将代码放在数据库中:

  • 非常适合一些小而受限的模块:

    有一小部分常见模块非常适合触发器。例如一个很小的函数,用来触发表上的 updated_at 时间戳:

    CREATE OR REPLACE FUNCTION set_updated_at()
    RETURNS trigger
    AS $$
    BEGINNEW.updated_at := current_timestamp;RETURN NEW;
    END
    $$ LANGUAGE plpgsql;
    

    然后数据库中的每张表都会有这个触发器:

    CREATE TRIGGER team_set_updated_atBEFORE UPDATE ON teamFOR EACH ROWEXECUTE FUNCTION set_updated_at();
    

    在应用程序代码中可以实现这一点(使用类似模型回调的方法),但会带来大量重复,一旦在某处遗漏就会造成错误。相比之下,数据库版本的运行更可靠,效果也更好。

  • 实现深度一致性,避免操作错误:

    举个例子:假设我们有两个独立的账户表,一个是在我们这里注册的账户,另一个是通过身份提供商的 SSO 进入的账户。它们有很大区别,因此我们要分别跟踪;但它们是相关的概念,账户可能拥有的资源(如 API 密钥)可能由其中一种类型或另一种类型拥有。

    另一个名为 account_common 的表通过两个小操作增强一致性:

    • 确保两个不同类型的账户不会意外共享一个 ID
    • 充当通用资源(如 API 密钥)的外键目标

    在添加账户或 SSO 账户时,要确保为其插入 account_common 记录。在应用程序代码中插入额外的记录不方便且容易忘记,因此我们用一个简单的触发器:

    CREATE OR REPLACE FUNCTION account_common_upsert() RETURNS TRIGGER AS $$BEGININSERT INTO account_common (id, kind) VALUES (NEW.id, TG_TABLE_NAME)ON CONFLICT (id, kind)DO NOTHING;RETURN NEW;END;
    $$ LANGUAGE plpgsql;CREATE TRIGGER account_common_upsert BEFORE INSERT ON accountFOR EACH ROW EXECUTE FUNCTION account_common_upsert();
    CREATE TRIGGER account_common_upsert BEFORE INSERT ON sso_accountFOR EACH ROW EXECUTE FUNCTION account_common_upsert();
    

这些情况仍然存在上述数据库代码的缺点,但也是将其放入数据库的好处大于成本的地方。

总之,应该根据具体情况评估,使用时将代码保持在较小的范围内。


💡 更多资讯,请关注 Bytebase 公号:Bytebase

http://www.lryc.cn/news/468275.html

相关文章:

  • 【设计模式系列】装饰器模式
  • 你真的知道TCP协议中的序列号确认、上层协议及记录标识问题吗?
  • 一家生物技术企业终止,科创属性可能不足,报告期内专利数猛增
  • 使用 Python 的 BeautifulSoup(bs4)解析复杂 HTML
  • Spring Cache Caffeine 高性能缓存库
  • Python3入门--数据类型
  • 开发运维警示录-20241024
  • Linux运维_搭建smb服务
  • vue3移动端可同时上传照片和视频的组件
  • PyQt入门指南二十七 QTableView表格视图组件
  • AI学习指南深度学习篇-自注意力机制(Self-Attention Mechanism)
  • 【JAVA毕业设计】基于Vue和SpringBoot的校园管理系统
  • 你对MySQL的having关键字了解多少?
  • 【STM32编码器】【STM32】
  • Python轴承故障诊断 (13)基于故障信号特征提取的超强机器学习识别模型
  • VScode分文件编写C++报错 | 如何进行VScode分文件编写C++ | 不懂也能轻松解决版
  • 洞察前沿趋势!2024深圳国际金融科技大赛——西丽湖金融科技大学生挑战赛技术公开课指南
  • Unity3D学习FPS游戏(4)重力模拟和角色跳跃
  • C#基础知识-枚举
  • 系统架构设计师教程 第2章 2.1-2计算机系统及硬件 笔记
  • 通过使用Visual Studio将你的程序一键发布到Docker
  • vue2和vue3动态引入路由,权限控制
  • Spring Boot:植物健康的智能守护者
  • 红黑树 学习笔记
  • linux更改系统时间
  • B站C#刘铁猛笔记
  • 如何使用信号发生器产生正弦波并用数字示波器进行测量
  • XJ04、消费金融|授信基本概念及其流程设计
  • 儿童预防接种预约微信小程序springboot+论文源码调试讲解
  • nginx 修改配置