第2章:数据库基础回顾
各位同学,咱们今天聊聊数据库基础。说实话,很多做了几年ERP开发的人,对这块的理解还是模模糊糊的。我见过太多因为基础不牢导致的性能事故了。今天咱们就把这些核心概念掰开揉碎,讲清楚。
2.1 关系型数据库核心概念
关系型数据库,说白了就是一张张表格组成的仓库。你想想看,ERP系统里的物料、订单、客户,最终都存成了一张张表。
我个人习惯把关系型数据库拆成三个层次来理解:
- 表(Table):就是二维表格,行是记录,列是字段。比如物料表,每一行是一种物料,每一列是物料的属性。
- 关系(Relation):表与表之间的关联。比如订单表关联客户表,通过客户ID建立联系。
- 约束(Constraint):保证数据完整性的规则。主键、外键、唯一约束,这些都是。
我在项目中遇到过最典型的坑:有人把客户地址直接存在订单表里。结果客户搬家了,要改几百个订单的地址。这就是没理解「关系」的本质。
核心要点:关系型数据库的精髓在于「数据冗余最小化,数据一致性最大化」。每个数据只存一份,其他地方通过关系引用。
2.2 ACID特性
ACID这四个字母,做ERP的人必须刻在脑子里。为什么?因为ERP系统里全是钱、库存、订单,出一点差错就是事故。
| 特性 | 含义 | ERP场景举例 |
|---|---|---|
| 原子性(Atomicity) | 事务要么全做,要么全不做 | 转账:扣钱和加钱必须同时成功或同时失败 |
| 一致性(Consistency) | 事务前后数据状态合法 | 库存不能为负数,订单金额必须等于明细之和 |
| 隔离性(Isolation) | 并发事务互不干扰 | 两个人同时下单,库存扣减不能乱 |
| 持久性(Durability) | 提交后数据永久保存 | 订单提交后,即使断电也不能丢 |
嗯,这里要注意。很多开发人员只记得ACID这四个字母,但实际写SQL时根本不考虑。我曾经接手过一个ERP项目,库存扣减居然没用事务。结果并发下单时,库存直接变成负数。你说这能忍吗?
避坑指南:我曾经见过一个团队,为了「性能优化」把事务隔离级别降到了READ UNCOMMITTED。结果出现了脏读,财务对账对了一个月。记住:ERP系统里,数据正确性永远优先于性能。
2.3 范式理论
范式这东西,刚学的时候觉得抽象。说白了就是一套「如何设计表才能不重复、不混乱」的规则。
咱们重点说前三范式,ERP系统里够用了:
- 第一范式(1NF):每个字段不可再分。比如「地址」字段不能同时存省、市、区,要拆开。
- 第二范式(2NF):非主键字段必须完全依赖于主键。比如订单明细表,主键是「订单ID+物料ID」,那「物料名称」就不该出现在这里。
- 第三范式(3NF):非主键字段不能传递依赖于主键。比如员工表里有「部门ID」,就不该再有「部门名称」,因为部门名称依赖于部门ID。
你想想看,违反范式会怎样?数据冗余、更新异常、插入异常、删除异常。我在项目中遇到过最典型的:一个客户表里,同一个客户录了三遍,因为地址写法不一样。这就是没做好范式设计。
个人经验:我建议ERP系统设计时,先按第三范式设计。等性能确实有问题了,再考虑反范式化。别一上来就搞反范式,那是给自己挖坑。
2.4 SQL语言基础
SQL是跟数据库打交道的语言。说实话,很多干了三五年的开发,SQL水平还停留在「能跑就行」的阶段。咱们今天把核心的捋一遍。
2.4.1 DDL(数据定义语言)
建表、改表结构用的。我习惯先画好ER图,再写DDL。
-- 创建物料表
CREATE TABLE material (
material_id INT PRIMARY KEY,
material_code VARCHAR(20) NOT NULL UNIQUE,
material_name VARCHAR(100) NOT NULL,
unit VARCHAR(10),
create_time DATETIME DEFAULT CURRENT_TIMESTAMP
);
-- 添加外键约束
ALTER TABLE order_detail
ADD CONSTRAINT fk_order_header
FOREIGN KEY (order_id) REFERENCES order_header(order_id);
2.4.2 DML(数据操作语言)
增删改查,日常用得最多。但要注意写法,性能差别很大。
-- 查询:别用SELECT *,指定字段
SELECT material_id, material_name, unit
FROM material
WHERE material_code LIKE 'M%';
-- 插入:批量插入用一条语句
INSERT INTO material (material_id, material_code, material_name, unit)
VALUES
(1, 'M001', '螺丝', '个'),
(2, 'M002', '螺母', '个');
-- 更新:一定要加WHERE,不然全表更新
UPDATE material
SET unit = 'KG'
WHERE material_id = 100;
重要提醒:我曾经见过一个事故,有人执行UPDATE忘了加WHERE,把整个物料表的单位全改了。还好有备份,但恢复数据花了半天。所以,写DML语句时,先写WHERE条件,再写SET,这是保命习惯。
2.4.3 连接查询
ERP系统里,几乎没有单表查询。订单要关联客户、物料、仓库,全是多表连接。
-- 内连接:只返回匹配的数据
SELECT o.order_no, c.customer_name, m.material_name, od.quantity
FROM order_header o
JOIN customer c ON o.customer_id = c.customer_id
JOIN order_detail od ON o.order_id = od.order_id
JOIN material m ON od.material_id = m.material_id
WHERE o.order_date >= '2024-01-01';
-- 左连接:保留左表所有数据
SELECT c.customer_name, COUNT(o.order_id) AS order_count
FROM customer c
LEFT JOIN order_header o ON c.customer_id = o.customer_id
GROUP BY c.customer_id;
嗯,这里要注意。连接查询的性能瓶颈往往在JOIN条件上。如果关联字段没有索引,数据量一大就卡死。我建议所有外键字段都建索引,这是基本操作。
2.4.4 聚合与分组
报表统计离不开聚合函数。但很多人写GROUP BY时,SELECT里的字段没包含在GROUP BY里,这在严格模式下会报错。
-- 统计每个客户的订单总额
SELECT c.customer_id, c.customer_name,
SUM(od.quantity * od.price) AS total_amount
FROM customer c
JOIN order_header o ON c.customer_id = o.customer_id
JOIN order_detail od ON o.order_id = od.order_id
GROUP BY c.customer_id, c.customer_name
HAVING total_amount > 10000
ORDER BY total_amount DESC;
个人习惯:我写聚合查询时,习惯先用小数据量验证结果对不对。因为聚合函数容易出错,特别是NULL值的处理。比如SUM遇到NULL会忽略,但COUNT(*)会统计NULL行。这些细节不注意,报表数据就是错的。
2.5 本章小结
数据库基础这块,说白了就是三件事:理解关系模型、遵守ACID、用好范式。SQL只是工具,真正重要的是设计思想。
我见过太多ERP项目,前期设计随便搞搞,后期维护累死累活。记住:数据库设计花的时间,会在后面十倍百倍地省回来。下一章咱们讲索引优化,那才是真正考验功力的地方。