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