深入探讨:为何避免使用外键与级联操作

2024年 2月 25日 116.5k 0

在阿里的《Java开发手册》里面有这么一句 “【强制】不得使用外键与级联,一切外键概念必须在应用层解决。”

那么,到底为什么会有这个强制规定呢?本文将从以下几个方面深入了解!

graph LR
A(深挖内容)
B(外键的作用与限制)
C(级联操作的工作原理)
D(避免使用外键与级联操作的场景)
E(替代方案)

A ---> B
A ---> C
A ---> D
A ---> E

style B fill:#FFC0CB,stroke:#FFC0CB,stroke-width:2px
style C fill:#FFA07A,stroke:#FFA07A,stroke-width:2px
style D fill:#FFFFE0,stroke:#FFFFE0,stroke-width:2px
style E fill:#98FB98,stroke:#98FB98,stroke-width:2px

一、外键的作用与限制

1.1 外键的定义

外键是用于建立表之间关系的一种约束,它定义了一个表中的列与另一个表中的列之间的关联。外键的定义和功能如下所述:

  • 定义外键

MySQL中,可以使用FOREIGN KEY关键字来定义外键。外键定义通常在创建表时使用,可以指定要关联的列以及关联的表和列。例如,下面的代码演示了如何在MySQL中定义外键:

CREATE TABLE 表名 (
  列名 数据类型,
  ...
  FOREIGN KEY (外键列) REFERENCES 关联表名(关联列)
);

1.2 外键的功能

外键可以强制引用完整性、自动维护关系、提高查询性能,并提供了一种有效的方式来管理相关表之间的数据。

graph LR
A(外键的功能)
B(关联表之间的关系)
C(强制引用完整性)
D(自动维护关系)
E(提高查询性能)

A ---> B
A ---> C
A ---> D
A ---> E

style B fill:#FFC0CB,stroke:#FFC0CB,stroke-width:2px
style C fill:#FFA07A,stroke:#FFA07A,stroke-width:2px
style D fill:#FFFFE0,stroke:#FFFFE0,stroke-width:2px
style E fill:#98FB98,stroke:#98FB98,stroke-width:2px
  • 关联表之间的关系

外键用于建立一个表与另一个表之间的关系。通过在一个表中定义外键,可以指定该表中的列与另一个表中的列之间的关联关系。这种关系可以是一对一、一对多或多对多关系。

  • 强制引用完整性

外键的一个主要功能是强制引用完整性。通过定义外键,可以确保在关联表中的数据保持一致性。

例如,如果一个表中的列是另一个表的外键,那么在插入或更新数据时,MySQL会检查外键约束,确保插入或更新的值存在于关联表的列中。

  • 自动维护关系

外键还可以自动维护关联表之间的关系。当在主表中插入、更新或删除记录时,MySQL会自动更新从表中的相关记录。这样可以确保关联表之间的数据保持一致性,并减少了手动处理关系的工作量。

  • 提高查询性能

使用外键可以提高查询性能。通过在表之间建立关系,可以使用JOIN操作连接相关的表,从而执行更复杂的查询。这样可以减少数据冗余,并提高查询的效率。

1.3 外键的限制

外键在MySQL中有一些限制,这些限制对于确保数据的完整性和一致性非常重要。外键的限制主要包括以下7个方面:

graph LR
A(外键的限制)
B(表类型限制)
C(数据类型限制)
D(唯一索引限制)
E(级联操作限制)
F(删除限制)
G(修改限制)
H(外键名称限制)

A ---> B
A ---> C
A ---> D
A ---> E
A ---> F
A ---> G
A ---> H

style B fill:#FFC0CB,stroke:#FFC0CB,stroke-width:2px
style C fill:#FFA07A,stroke:#FFA07A,stroke-width:2px
style D fill:#FFFFE0,stroke:#FFFFE0,stroke-width:2px
style E fill:#98FB98,stroke:#98FB98,stroke-width:2px
style F fill:#B2FFFF,stroke:#B2FFFF,stroke-width:2px
style G fill:#ADD8E6,stroke:#ADD8E6,stroke-width:2px
style H fill:#E6E6FA,stroke:#E6E6FA,stroke-width:2px
  • 表类型限制

    • 外键约束只适用于使用InnoDB存储引擎的表。默认的MyISAM存储引擎不支持外键。因此,在创建表时,应明确指定使用InnoDB引擎,或者在修改已有表时将存储引擎更改为InnoDB
  • 数据类型限制

    • 外键列和引用列的数据类型必须匹配。例如,如果外键列是INT类型,那么引用列也必须是INT类型。
  • 唯一索引限制

    • 在引用表中,被外键引用的列必须具有唯一索引或主键约束。这是为了确保在外键关系中不存在重复的值,以保持数据的完整性。
  • 级联操作限制

    • 在定义外键时,可以指定级联操作,如ON DELETEON UPDATE。但是,一些级联操作是有限制的。例如,不允许在自引用的外键上使用ON DELETE SET NULL操作。
  • 删除限制

    • 如果存在外键关系,删除主表中的记录时会有一些限制。默认情况下,MySQL不允许删除具有相关外键的记录。可以通过使用ON DELETE子句来指定级联操作,如CASCADE,以便在删除主表记录时同时删除相关的从表记录。
  • 修改限制

    • 如果存在外键关系,修改主表中被引用的列时会有一些限制。默认情况下,MySQL不允许修改主表中被外键引用的列的值,除非同时修改从表中相关的外键列。可以通过使用ON UPDATE子句来指定级联操作,如CASCADE,以便在修改主表列值时同时更新从表中相关的外键列。
  • 外键名称限制

    • 外键的名称必须是唯一的,不能与其他外键或索引名称冲突。

二、级联操作的工作原理

2.1 级联更新与级联删除

当在主表中操作一个被外键引用的列时,级联操作会自动更新相关的从表中的对应列的值。这样可以确保相关表之间的数据保持一致性。级联操作的过程如下:

  • 在主表中更新或删除被外键引用的列的值。

  • 系统自动检查所有从表中与主表相关联的外键列。

  • 对于每个从表中的外键列,系统自动更新其对应的值或者自动删除其对应的记录,使其与主表中更新后的值保持一致。

  • 通过级联更新或删除,可以避免在更新主表后手动更新或删除所有相关从表的外键列的工作。

  • 2.2 级联操作的潜在问题

    虽然级联操作在某些情况下可以简化数据管理并保持数据一致性,但也存在一些潜在问题需要注意。以下是一些常见的级联操作可能导致的问题:

    graph LR
    A(级联操作的潜在问题)
    B(意外数据更改或删除)
    C(多级联操作的复杂性)
    D(数据一致性问题)
    E(性能影响)
    
    A ---> B
    A ---> C
    A ---> D
    A ---> E
    
    style B fill:#FFC0CB,stroke:#FFC0CB,stroke-width:2px
    style C fill:#FFA07A,stroke:#FFA07A,stroke-width:2px
    style D fill:#FFFFE0,stroke:#FFFFE0,stroke-width:2px
    style E fill:#98FB98,stroke:#98FB98,stroke-width:2px
    
    • 意外数据更改或删除

      • 当使用级联操作时,需要非常小心,以免意外地更改或删除相关表中的数据。如果级联操作配置不正确或不符合业务需求,可能会导致意外的数据更改或删除,破坏数据的完整性。
    • 多级联操作的复杂性

      • 如果存在多个表之间的级联操作,复杂性会增加。多级联操作可能导致数据更新的连锁反应,使得数据变更变得更加复杂和困难。在设计数据库结构时,需要仔细考虑多级联操作的潜在复杂性,并确保其可维护性和可理解性。
    • 数据一致性问题

      • 尽管级联操作可以维护数据一致性,但在某些情况下可能会导致数据一致性问题。例如,在级联更新时,如果更新的数据不符合从表的约束条件,可能会导致数据不一致的情况。因此,在使用级联操作时,需要仔细检查和验证数据的一致性。
    • 性能影响

      • 级联操作可能对性能产生影响。特别是在处理大量数据和复杂的关联关系时,级联操作可能导致查询和更新操作的性能下降。因此,需要评估级联操作对数据库性能的影响,并确保其在可接受范围内。

    三、避免使用外键与级联操作的场景

    3.1 性能考虑

    写代码不关注程序性能,那就是挖坑专员;那么,外键和级联那些场景中会有性能问题呢?

    graph LR
    A(性能问题)
    B(高并发写入场景)
    C(大规模数据操作场景)
    D(高度优化的查询场景)
    
    A ---> B
    A ---> C
    A ---> D
    
    style B fill:#FFC0CB,stroke:#FFC0CB,stroke-width:2px
    style C fill:#FFA07A,stroke:#FFA07A,stroke-width:2px
    style D fill:#FFFFE0,stroke:#FFFFE0,stroke-width:2px
    
    • 高并发写入场景

      • 当有大量并发写入操作时,外键约束和级联操作可能会导致性能下降。每次写入操作都需要检查和更新相关的外键关系,这会增加数据库的锁竞争和资源消耗,降低并发性能。在这种情况下,可以考虑在应用层面上手动管理数据的一致性,而不依赖于数据库的外键约束和级联操作。
    • 大规模数据操作场景

      • 当进行大规模数据操作,如批量导入或大量数据迁移时,外键约束和级联操作会增加额外的开销和复杂性。每次操作都需要触发外键检查和相关表的更新,这会导致操作的执行时间增加。在这种情况下,可以在操作之前暂时禁用外键约束,完成操作后再重新启用外键约束,以提高操作的性能和效率。
    • 高度优化的查询场景

      • 在一些高度优化的查询场景中,外键约束和级联操作可能会对查询性能产生负面影响。外键的存在会增加查询的复杂性,并且在查询过程中可能需要访问多个表,导致额外的开销和响应时间增加。在这种情况下,可以考虑放宽外键约束,或者根据具体需求进行冗余数据存储和冗余索引的设计,以提高查询性能。

    上面三种场景,实际可以统一理解成数据量一旦上来了,就不要使用外键和级联操作了。。。

    3.2 数据一致性与业务逻辑

    某些业务逻辑可能需要更复杂的处理,超出了级联操作的能力。

    级联操作是数据库层面的自动处理,无法涵盖所有业务规则的变化和需求。

    在这种情况下,需要通过应用层面的定制逻辑和手动操作来处理数据的关联操作,以确保符合业务规则。

    切记:没有万能的技术实现方案,千万不要生搬硬套。

    3.3 数据库迁移

    在数据库迁移过程中,外键和级联操作可能会带来许多挑战,主要包括以下几个方面:

    graph LR
    A(数据库迁移挑战)
    B(数据一致性)
    C(迁移顺序与依赖关系)
    D(执行时间和性能开销)
    E(迁移后的数据验证)
    F(跨数据库平台的兼容性)
    
    A ---> B
    A ---> C
    A ---> D
    A ---> E
    A ---> F
    
    style B fill:#FFC0CB,stroke:#FFC0CB,stroke-width:2px
    style C fill:#FFA07A,stroke:#FFA07A,stroke-width:2px
    style D fill:#FFFFE0,stroke:#FFFFE0,stroke-width:2px
    style E fill:#98FB98,stroke:#98FB98,stroke-width:2px
    style F fill:#B2FFFF,stroke:#B2FFFF,stroke-width:2px
    
    • 数据一致性

      • 当迁移涉及到外键和级联操作时,确保数据的一致性是一个重要的挑战。如果迁移过程中处理外键和级联操作不正确,可能会导致数据的不一致性或丢失关键数据。因此,在迁移过程中需要仔细处理外键和级联操作,确保数据的完整性和一致性。
    • 迁移顺序与依赖关系

      • 如果数据库中存在多个表之间的外键关系和级联操作,迁移过程中需要考虑正确的迁移顺序和依赖关系。迁移顺序必须遵循外键依赖关系,确保先迁移被依赖的表,再迁移依赖的表。否则,可能会出现违反外键约束的情况,导致迁移失败或数据不一致。
    • 执行时间和性能开销

      • 外键和级联操作可能会导致迁移过程的执行时间增加和性能开销加大。每次进行外键检查和更新操作都需要消耗额外的时间和资源。对于大规模的数据迁移,这可能会显著影响迁移的效率和时间。因此,在迁移过程中需要评估可能的性能开销,并根据实际情况考虑是否禁用外键约束或使用其他优化策略。
    • 迁移后的数据验证

      • 在完成迁移后,需要进行数据验证以确保外键关系和级联操作的正确性。验证过程可能需要检查每个表之间的关联关系,并确保外键约束和级联操作正常工作。这需要仔细设计验证策略和测试用例,以确保数据的一致性和正确性。
    • 跨数据库平台的兼容性

      • 如果数据库迁移涉及到跨不同的数据库平台,外键和级联操作的语法和行为可能会有所差异。在迁移过程中,需要了解目标数据库平台的支持和限制,并相应地调整外键和级联操作的定义和处理方式。

    四、替代方案

    替代方案是有多种的,比较流行的就是应用程序层面的约束和实用触发器,不过本文忽略触发器,着重介绍一下使用范围更加广泛的应用程序层面的约束。

    4.1 应用程序层面的约束

    在应用程序层面实现数据完整性约束可以作为数据库外键约束的补充,以增强数据的一致性和完整性。具体实现可以参考以下五个方面来操作:

    graph LR
    A(应用程序层面的约束)
    B(输入验证)
    C(业务规则和逻辑)
    D(事务管理)
    E(强制关系)
    F(完整性检查和修复)
    
    A ---> B
    A ---> C
    A ---> D
    A ---> E
    A ---> F
    
    style B fill:#FFC0CB,stroke:#FFC0CB,stroke-width:2px
    style C fill:#FFA07A,stroke:#FFA07A,stroke-width:2px
    style D fill:#FFFFE0,stroke:#FFFFE0,stroke-width:2px
    style E fill:#98FB98,stroke:#98FB98,stroke-width:2px
    style F fill:#B2FFFF,stroke:#B2FFFF,stroke-width:2px
    
    • 输入验证

      • 在应用程序中,对用户输入的数据进行验证是确保数据完整性的关键步骤。通过在应用程序中实施严格的验证规则和逻辑,可以防止无效或不符合要求的数据进入数据库。例如,对于日期字段,可以验证输入是否符合特定的日期格式;对于数字字段,可以验证输入是否为有效的数字范围。
    • 业务规则和逻辑

      • 应用程序应该根据业务规则和逻辑来限制数据的操作和变更。例如,如果某个字段必须为非空值,应该在应用程序中强制执行该规则,并在数据插入或更新时进行验证。类似地,可以定义其他业务规则,如唯一性约束、范围限制等,并在应用程序中进行检查。
    • 事务管理

      • 使用数据库事务可以确保在一系列操作中保持数据的完整性。通过将相关的数据库操作放在一个事务中,可以保证这些操作要么全部成功要么全部失败。如果某个操作违反了数据完整性约束,可以回滚整个事务,防止无效数据的持久化。
    • 强制关系

      • 在应用程序中,可以通过编程方式实施表之间的关系,并在相关表之间强制执行这些关系。例如,在关系数据库中,可以在应用程序中手动编写查询或使用ORM(对象关系映射)工具来确保相关表之间的关系正确并遵守业务规则。
    • 完整性检查和修复

      • 应用程序可以定期进行数据完整性检查,并对发现的问题进行修复。这可以通过编写脚本或定时任务来实现。例如,检查外键关系是否存在无效引用,检查数据是否符合约束条件等。如果发现数据不一致或违反完整性约束,可以自动或手动修复这些问题。

    五、总结

    总的来说,虽然外键和级联操作在数据库层面提供了一种方便的方式来维护数据的引用完整性,但在实际开发中,由于性能、业务逻辑复杂性以及数据库迁移等方面的考虑,阿里巴巴推荐开发者在应用层处理这些关系,以获得更高的灵活性和更好的性能。这种做法要求开发者在应用层实现相应的数据一致性逻辑,确保数据的正确性和完整性。

    但是实际应用中,用不用还是要根据自己的系统数据综合考虑,别把本来简单的事情做复杂了,比如一个内部考核系统,就是完全可以使用外键的。

    希望本文对您有所帮助。如果有任何错误或建议,请随时指正和提出。

    同时,如果您觉得这篇文章有价值,请考虑点赞和收藏。这将激励我进一步改进和创作更多有用的内容。

    感谢您的支持和理解!

    相关文章

    Oracle如何使用授予和撤销权限的语法和示例
    Awesome Project: 探索 MatrixOrigin 云原生分布式数据库
    下载丨66页PDF,云和恩墨技术通讯(2024年7月刊)
    社区版oceanbase安装
    Oracle 导出CSV工具-sqluldr2
    ETL数据集成丨快速将MySQL数据迁移至Doris数据库

    发布评论