1. 数据完整性基础

各位同学好,我是老张。今天咱们开始聊数据完整性。说实话,这话题听起来有点学院派,但我在一线摸爬滚打这么多年,可以负责任地告诉你——数据完整性出问题,往往是灾难的开始。

我记得刚带团队那会儿,有个凌晨三点被叫起来的经历。客户说系统跑着跑着,订单金额对不上了。查了一整夜,最后发现是外键约束没配好,子表里多了一堆“孤儿”记录。嗯,从那以后,我每次建表都会把完整性约束检查三遍。

什么是数据完整性?

说白了,数据完整性就是保证数据库里的数据是准确、一致、可靠的。你想想看,如果一张订单表里,订单金额是负数,或者客户ID指向了一个不存在的客户,这数据你敢用吗?

数据完整性不是某个单一技术能搞定的。它是一套规则、约束和流程的组合。我个人习惯把它分成三个层面:

  • 结构层面:表结构设计是否合理,字段类型、长度、默认值对不对
  • 关系层面:表与表之间的关联是否一致,有没有“孤儿”数据
  • 业务层面:数据是否符合业务逻辑,比如年龄不能是负数,库存不能为负

核心观点:数据完整性不是DBA一个人的事。开发、运维、业务方都得参与。我见过太多项目,上线前没人管完整性,上线后天天修数据。

完整性约束类型

数据库系统提供了几种内置的约束机制。我把它们分成三大类,咱们一个一个看。

实体完整性

实体完整性保证每一行数据都是唯一的、可识别的。最常见的实现方式就是主键约束。

-- 主键约束示例
CREATE TABLE users (
    user_id INT PRIMARY KEY,
    username VARCHAR(50) NOT NULL,
    email VARCHAR(100)
);

-- 或者用复合主键
CREATE TABLE order_items (
    order_id INT,
    product_id INT,
    quantity INT,
    PRIMARY KEY (order_id, product_id)
);

这里有个坑,我提醒一下。主键字段不能为NULL,而且必须唯一。我曾经遇到一个项目,开发人员把主键设成了可空字段,结果插入了两条NULL值记录。查询的时候,这两条记录完全无法区分。你说这数据还能用吗?

参照完整性

参照完整性,也叫引用完整性。它保证表与表之间的关联关系是正确的。外键约束就是干这个的。

-- 外键约束示例
CREATE TABLE orders (
    order_id INT PRIMARY KEY,
    user_id INT,
    order_date DATE,
    FOREIGN KEY (user_id) REFERENCES users(user_id)
);

外键约束能防止你插入一个不存在的user_id。但要注意,外键也会带来性能开销。我在做高并发系统时,有时候会权衡一下——是保留外键保证数据一致,还是去掉外键提升写入性能?

我的经验:核心业务表(订单、支付、账户)一定要保留外键。日志、流水这类只追加不修改的表,可以考虑去掉外键。但前提是应用层要做好数据一致性校验。

域完整性

域完整性保证字段的值是合法、有效的。它通过数据类型、NOT NULL、CHECK约束、默认值等来实现。

-- 域完整性示例
CREATE TABLE products (
    product_id INT PRIMARY KEY,
    product_name VARCHAR(100) NOT NULL,
    price DECIMAL(10,2) CHECK (price > 0),
    stock INT DEFAULT 0 CHECK (stock >= 0),
    status VARCHAR(20) CHECK (status IN ('active', 'inactive', 'discontinued'))
);

你看,price必须大于0,stock不能为负,status只能取三个值之一。这些规则在数据库层面就卡死了,比在应用层校验要可靠得多。

我遇到过最离谱的事,是有人把商品价格存成了负数。原因是应用层校验有bug,而数据库没加CHECK约束。结果财务对账时发现一堆“倒贴钱”的订单。你说这笑话闹得大不大?

约束类型 作用 实现方式 常见问题
实体完整性 保证行唯一 主键、唯一约束 主键重复、NULL值
参照完整性 保证关联正确 外键约束 孤儿记录、级联删除误操作
域完整性 保证字段值合法 数据类型、CHECK、NOT NULL、默认值 非法值、越界数据、空值

数据损坏的常见原因

数据完整性被破坏,原因五花八门。我总结了几类最常见的,你看看有没有踩过类似的坑。

硬件故障

磁盘坏道、内存错误、RAID卡故障……这些硬件问题会导致数据写入时出现位翻转或数据丢失。我记得有一次,一台服务器的磁盘控制器出了毛病,写进去的数据读出来全是乱码。还好有备份,不然就真凉了。

软件Bug

应用层代码写错了,或者数据库引擎本身有bug。比如事务没正确提交、并发控制没做好、存储过程逻辑有漏洞。我见过一个案例,开发人员在循环里忘了提交事务,结果数据只写了一半。另一半呢?丢了。

人为操作失误

这个最常见,也最让人头疼。误删数据、更新时忘了加WHERE条件、跑错了脚本……我自己就干过这种事。有一次在测试环境跑了个DELETE FROM orders,结果发现连到了生产库。嗯,那感觉,就像坐过山车一样刺激。

避坑指南:我曾经因为一个没加WHERE条件的UPDATE,把整个用户表的邮箱字段全改成了同一个值。从那以后,我养成了两个习惯:1) 写UPDATE/DELETE先加WHERE再写表名;2) 高危操作先在事务里跑,确认无误再提交。

网络与存储问题

网络抖动导致数据包丢失、存储系统超时、文件系统损坏……这些都会造成数据不一致。特别是在分布式系统里,网络分区会导致数据同步延迟,甚至出现脑裂。

恶意攻击

SQL注入、数据篡改、勒索软件……这些就不用我多说了吧。安全防护不到位,数据完整性就是空谈。

数据完整性知识体系 数据完整性 实体完整性 参照完整性 域完整性 主键约束 唯一约束 外键约束 级联操作 数据类型 CHECK 数据损坏常见原因 硬件故障 软件Bug 人为失误 网络/存储 恶意攻击 约束是防线,但防线之外还有硬件、软件、人为、网络、安全等多重威胁