V2EX = way to explore
V2EX 是一个关于分享和探索的地方
现在注册
已注册用户请  登录
ljk
V2EX  ›  程序员

是什么理由让你彻底抛弃数据库的外键?

  •  1
     
  •   ljk · 2016-11-22 14:28:29 +08:00 · 25490 次点击
    这是一个创建于 2924 天前的主题,其中的信息可能已经有所发展或是发生改变。

    初涉后台开发未深,诚惶诚恐看 <SQL 反模式>,学习最佳实践。

    发现现在这个项目应用了不少反模式,其中最让我困惑的是,完全不使用外键,涉及到外键的逻辑全部在应用代码里实现。

    询问项目负责人,告知不使用数据库外键主要有两原因:

    1. 多台服务器不停机升级时,有可能数据库新加入的外键限制导致新程序没办法在旧数据库上跑
    2. 运维人员对数据库外键深恶痛绝(举例说唯品会就是这样做的)

    听起来只要是分布式的服务器热升级(第一点原因)、或者团队里有运维人员,就应该抛弃数据库外键了。

    业界都是这样做的吗?书上说的当真脱离了实际?

    谢不吝赐教。

    第 1 条附言  ·  2016-11-23 10:54:52 +08:00
    指教不少,收获颇多,先谢各位了。

    另外再请问一下,如果在代码里实现约束的话,那么当我要插入一条 student 的纪录时,想先验证它的 class_id 字段是否有效,这时是否要先来一句 SELECT sql 查一下 class 表里有没这个 class_id ,确认 class_id 存在再进行 INSERT ?

    如果是这样实现约束的话,开销会不会有点大?毕竟每个约束都来一句 SQL 验证的话, N 个约束就是 N 句 SQL ……
    117 条回复    2019-01-08 06:16:29 +08:00
    1  2  
    ralphxlx
        101
    ralphxlx  
       2016-11-23 16:36:36 +08:00
    @mko0okmko0 就是说只是建立一个字段,业务逻辑中关联?但是还是要 join 啊,还是要关联查询啊
    mko0okmko0
        102
    mko0okmko0  
       2016-11-23 19:53:34 +08:00   ❤️ 2
    @ralphxlx
    你注意看这串讨论大多数人,不在资料库内设定外键关联.也省去连带变动 /连带删除设定,然后就直接 JOIN 直接查.

    这样直到性能有问题.变成直接将要关联的 N 个表作一个初步过滤后.直接读到 N 个变数中.
    (这时候资料库出输出已经完工.以下与资料库几乎无关)
    然后用程式码写关联,或是放入自订类,或是放入泛形容器(模板).
    然后再把关联后的查询在程式码中做.
    然后输出结果,或是依照结果存入修改到资料库内的每个变动 N 表.

    以上完全没做外键.
    所以做外健目标是什么?我认为是强关系约束 /连带变动 /连带删除.

    但在资料库设计初期.可能你还没做完关联设定.上头就传了讯息要你改一下新设计.搞不好连整个表都要重新设计.这时候资料库建模者就会满肚子表.甚至就不想设外键等设定了.

    设计中期.开始要丢测试数据了.结果关联子表没资料不行.所以写入程式码就要补上先写入子表后写入主表等复杂语句.还会遇到一个问题.整个写入是有问题的.你要保留已经写入的子表资料还是删除?
    一个一个删除.你要写的语句就不会少.不然就加上交易(Begin Transaction).出问题就 Rollback.没问题就 Commit.语句是变少了.但交易的性能下降你要先了解.

    设计后期或业务更动时.更可怕了.你可能要先复制一个测试用库最好保留全部资料.然后开始加入新表.设定外键等.然后删改旧表..并跑完所有业务流程模拟与性能测试(简单说就是搜集过去用户输入大约 N 万笔.从 WEB 端丢入动态网页开始跑.看跑多久.跟旧的比差多少.个别增删查改耗时统计).
    然后才写成 SQL 档去更新主资料库架构(同时更新业务逻辑代码).

    到这里.我们看看.外键给了我们什么?一点点的好处
    跟一堆麻烦.还有性能下降.还有水平扩展困难.
    外键有帮我们省下空间吗?没有.

    所以大部分工作一段时间的资料库工作者.不会使用外键.而是在业务逻辑代码中处理资料库.

    我没有放弃外键.我很喜欢建模初期就设定上去.因为可以很清楚的从关连线了解资料库架构.
    而正式上机使用时.除非已经遇到性能或其他问题.我不会主动去掉外键.

    或是.一开始了解用户需求后.发现太多标准资料库不好做的设计(例如 POE 的技能网系统).就直接用 图论 /json 资料库 设计.
    例如 arangoDB.
    ifhwhlwpto
        103
    ifhwhlwpto  
       2016-11-24 02:26:32 +08:00 via Android
    @sunsh217 国外当然有教科书,我上的数据库用的就是 database system concepts
    ryd994
        104
    ryd994  
       2016-11-24 03:48:49 +08:00 via Android
    @nandaye 数据库的运作不可能没有主键,有时候没有明确主键的话用随便加一项自增整数顶着而已
    ryd994
        105
    ryd994  
       2016-11-24 04:08:21 +08:00 via Android
    @jjx 匠人心态成不了工程师。忽略实际能力,什么都想要什么都去做,只会一事无成。
    工程设计是以目标为导向,有明确的需求。在满足所有必须之后,最优化其中一项。优化过程中当然有所取舍。对于互联网行业,第一需求是可维护和可拓展,其次才是一致性。
    更何况,不用外键只是舍弃了数据库提供的强一致性。完全可以用其他实现最终一致性。
    ralphxlx
        106
    ralphxlx  
       2016-11-24 08:15:32 +08:00
    @mko0okmko0 感谢解答
    nandaye
        107
    nandaye  
       2016-11-24 09:37:42 +08:00
    @ryd994 我是专门做这方面的,有的的确是没有的,你觉得不可能是没见过吧
    jjx
        108
    jjx  
       2016-11-24 09:38:50 +08:00   ❤️ 3
    @ryd994

    我没有数据库应用必须强制一定要用外键的想法, 这看应用的类型. 你用不用是你的事情, 如果你觉得对你自己有益. 在一些应用类型中, 正确的使用外键是必要的, 这些应用类型典型的例子就是进销存, erp . 而且,这些软件当前也正在互联网化, 但不能用所谓的全部的互联网思维去做的. 就好比学 nodejs 就是后端的想法一样.

    具体分析一下

    外键的三个作用

    1. 值必须存在
    2. 级联更新
    3. 级联删除

    由于最近几年只用 postgresql , 我只知道我的 postgresql 默认生成的外键 2 和 3 都是 no action, 也就是根本不用考虑 ls 一些帖子所谓的级联删除的问题. 这个设置现在多在 orm 配置层面比方说 sqlalchemy 的 cascade 选项, 也就是说还是在业务逻辑层面上,出现了级联删除和更新,是你的业务代码如此定义了

    那么讨论 1 值必须存在, 如果表中插入一个不存在的值会怎么样, 显然针对这个表的统计都会出错, 对于某些应用类型, 这些应用通常是

    1. 实时统计. 报表不是定时生成的(定时生成的会对数据清理), 而是实时统计当前数据
    2. 各种关系到钱的, 错不得

    为了达到防止插入一个不存在的值, 解决方案就是在插入和更新时检查, 这里有个最大的问题就是, 你假定了所有的操作都会通过你的业务代码. 但实际企业应用场景. 维护, 第三方等都可能会接触你的数据库, 修改你的数据库, 你自己的应用随着逻辑的增加(这很正常)都可能会忘了检查, 导致这个逻辑被跳过,

    这里同时要反驳一下 自己检查的性能和开发效率会高于 数据库, 简单的举个例子,

    举一个 erp 中一个销售单据, 一般最少会引用多少个相关表

    sale_orders

    * employee_id 员工
    * department_id 部门
    * store_id 仓库
    * partner_id 客户 /供应商
    * logistics_company_id 物流公司
    * logistics_product_type_id 物流公司产品类型
    * 各种收款账户和类型

    sale_order_lines

    * product_id
    * measure_unit_id

    你自己的代码要 select 多少次, 企业软件通常是个大的整体应用, 表很多, 一般都用几百个, 像 axapta/oracle 这样的有几千个, 每个表基本都会引用一个或多个相关表, 没有外键, 自己每个表的更新插入都写业务逻辑, 不可想象

    外键对同步的问题是显而易见的, 表同步时必须按顺序同步, 如果一个表示父子关系, 则必须按父子顺序同步.

    erp 对同步要求比较少, 是因为 erp 这种应用, 就是你做 saas, 建议方案也是一租户一数据库(或一 schema), 自然的在分库分表, 而且 erp 软件通常会不会超过几年结转一次, 老的帐作为归档数据库仅仅用于查询. 分布式压力基本比较少. 但不同库中同步数据的需求还是有的, 有外键在的确是比较麻烦. 但这个就像上面所说的, 你对表的依赖性心里有数, 其实也不是个问题. 当然, 这个层面上不同应用有不同应用的策略. 我个人不在意这些.

    外键更多的在我看来是一种防护, 如果你的应用对数据比较敏感, 报表必须实时生成, 而你又不能 100%保证数据是正确的,
    我的建议, 还是用!

    再次重申, 我没有让你们一定用外键的想法, 这关我什么事情呢? 只是外事得看场合
    zgoing
        109
    zgoing  
       2016-11-24 09:49:29 +08:00 via iPhone
    数据库设计是要满足第三范式的,目的是为了最小冗余
    nandaye
        110
    nandaye  
       2016-11-24 10:03:53 +08:00
    @ryd994 我也看过很多书,事务型的、分析型的,以前我也和你想法一样,为什么某某数据库设计完全违背了某某模型的要求,是不是设计者不懂。就拿分析型数据库来说,整个库不加一个约束一个键的也是有的(并不是小公司哦),经常会做大批量增删改,你提到的约束会影响性能,所以干脆不要了。你提到的加主外键其实针对的对象主要只是传统的事务型数据库。
    mko0okmko0
        111
    mko0okmko0  
       2016-11-24 11:07:58 +08:00   ❤️ 3
    @ryd994 @ralphxlx @ljk @jjx @nandaye

    补充一个我没放弃外键的原因.而且是因为这事情导致我过去 7 年的工作始终做好外键的原因...

    7 年前我第一个算稳定的纯软开发工作.专案管理员(PM)给了我一个网站跟资料库.说明整体目标与目标用户之后.让我试着融入工作团队( 40 人左右)工作.

    但显然我很失败.PM 说我可以询问任何人.但每个人身上几乎都超过 4 个案子.所以其实大家都很忙.
    而且以前没有这样的团队经验.不知道如何开口.所以我只好拿着 PM 给我的东西研究...

    拿着资复制出来的料库一打开.....超过 40 个表.只有用户表跟权限表有关联外键.其他的表都没有.
    这里简单说一下.这是一个特有(保育)生物的调查与纪录网站.涵盖了 GIS/动植物 /生态环境 /人...我的意思是.他比一般商业网站还复杂很多.应该比进销存还复杂一点吧.

    重点是...没有说明 没有说明 没有任何说明 ....重点要说 3 次.

    所以我告诉 PM 这样我没办法.太难了.做过这个案子的只剩两个人.一个前端一个全端.可是他们也很忙.
    PM 说那我就去当驻点吧.

    然后我就当了 4 个月的驻点.然后就换工作了...直接变成该案的老板的助理.然后重新设计者个系统(与厂商平行作业)

    总之.这 4 个月充满痛苦的经验.让我一摸到资料库软体或是一听到系统建模.
    我的手就无法阻止的点上了外键设定.还有每个表每个栏位写满了注解.因为很多软体可以从外键设定生出清楚的架构图.
    而且对后面来的同事,或是到校合作教学生,我都会说"把外键做上去,每一栏都写中文注解".

    资料库性能问题,我们可以业务与资料库主机分离,可以换 SSD,可以用 RAMDISK,可以主从模式,
    但只有"没有关联没有注解没有说明档"修复的代价最痛.

    一次受害终身受用.共勉.
    mko0okmko0
        112
    mko0okmko0  
       2016-11-24 11:17:56 +08:00
    再补充一个,
    我有用过外键关联但可以 null 的设定,其实子表该值不存在也是可以动的.
    从这个现象我特意去验证了外键关联到底是否有省下空间,答案是没有.
    所以主表 /子表的外键关联栏位值在两边都存在,所以非数字栏位做外键关联根本空间吃超多.

    所以对我来说外键对于减少资料错误率没有想的那么好,但设定与验证时间确实比较多,
    但我系统架构文件也很懒得写,直接叫出资料库建模软体的架构生成图含注解就当有写了(被殴
    所以设定与验证=写说明文件(奔逃
    ryd994
        113
    ryd994  
       2016-11-24 13:17:48 +08:00
    @jjx 我也从来没说过就必须用,我一直说的是有所取舍。用外键主要是保护强一致,最终一致不是必须用外键。但是你觉得这是在漠视数据一致性, 我不同意。
    比如说有千万分之一的概率会丢一篇日常吐槽微博,但是性能至少提高两个数量级,那是不是值得呢?比如说有千万分之一的概率会丢一单几千万的交易记录,但是性能至少提高两个数量级,那是不是值得呢?

    工程思维讲究的是确定需求,确定技术可行性,满足需求。不是盲目乐观也不是盲目悲观。有可能出错么?有多大概率?会出什么问题?最坏情形会有多大损失?修复起来麻烦么?这些问题其实远超技术的范围。作为技术实现者,我只能向决策者说明技术上的可能性与成本。

    脱开剂量谈毒性,都是耍流氓。实际情况实际讨论。

    @mko0okmko0 用文档来证明外键的必要性,这不是缘木求鱼么?在我受的教育里,是先画关系图,和所有人确定好,写定文档之后,才着手实现。接口文档必须先于代码。

    @nandaye 抱歉,这篇帖子说的是外键,我就默认你说的也只是传统的 SQL 类。没多想,没说明白。
    mko0okmko0
        114
    mko0okmko0  
       2016-11-24 14:18:46 +08:00
    @ryd994
    恩恩,理想上就是上头给一个不再大更动的规格书,或是自己写一份规格书,上头确认后,技术人员开始实作.

    但这几年的工作经验,全都是技术 /文书人员工作量超载的情况,
    然后部分厂商就算拿到规格书,也会有规格外的要求在期中会议或内测过程被提出修改,
    我在会议上一边录音一边草草的记下要修改的内容,回去忙着写 code,没什么时间写规格书给自己看.
    而且先写规格书没用,我上头没有懂技术的,
    上头只想看漂漂亮亮的图表,而我又不是职业前端,做图表很丑,所以常常就是看成果 @o@.
    而期末需要缴交的架构图,xmind 画一份各类用户逻辑图(但我不说没人看得出那是 xmind 了,手工大改非自动拉线),资料库软体拉出资料库结构与关联图,就当作有写了.
    不得不说我的文档有瑕疵,但重点是过去跟我合作的其他人是否能从我的文档了解整个案子的目标与意图.并且成功实作或是接手.
    接口名称在白板讨论 /xmind 的中文意图讨论没有问题后就会标上.所以也算是先文档后实作了.

    这里要小小抱怨一下,技术人员不够用的时候,不懂资讯的文书人员没办法帮技术人员写文档或产架构图,而技术人员会在做两次跟做一次的选择中优先选做一次(再次被殴)然后有剩下时间才写文档.

    我第一篇回文就有说明每个人的经历不同会有很多不同的结论.我觉得这篇大家的答案都没有什么错.我在这里用其他人的角度再次检查自己过去几年的行为是否是当时我能做的最好选择.

    所以我标记自己(被殴)(奔逃)原因就是我没办法全按照标准来作业,但我能做到当时时间与实作的极限平衡就这样了.

    放轻松~~慢慢思考~~感觉大家有点急躁~~
    atcdef
        115
    atcdef  
       2017-12-04 18:43:01 +08:00
    我觉得呢,如果外键的存在导致性能上的损失对你来说特别敏感的时候,那就可以考虑不用外键了,前提是你自己对记录的约束控制能够和数据库系统自行控制达到一样的可靠性(这个很重要),如果没有这样的能耐,那还是用外键吧。
    zhongkouwei
        116
    zhongkouwei  
       2018-08-02 15:01:54 +08:00
    因为很少有单机库的存在,分库后操作复杂,其实就是降低反向依赖,不用存储过程也是这样
    autogen
        117
    autogen  
       2019-01-08 06:16:29 +08:00
    对,某商品表分 1024 个表,订单表外键商品 ID 怎么搞定?
    1  2  
    关于   ·   帮助文档   ·   博客   ·   API   ·   FAQ   ·   实用小工具   ·   3635 人在线   最高记录 6679   ·     Select Language
    创意工作者们的社区
    World is powered by solitude
    VERSION: 3.9.8.5 · 24ms · UTC 04:27 · PVG 12:27 · LAX 20:27 · JFK 23:27
    Developed with CodeLauncher
    ♥ Do have faith in what you're doing.