简介:ERP数据库详细设计说明书以PDF文档形式提供,适合ERP实施顾问、数据库架构师、开发工程师以及高校相关专业学生阅读。文档依照企业ERP常见业务模块划分,涵盖命名规则、基础数据、库存子系统、销售子系统、采购子系统等设计内容;其中对物料类别、仓库、物料主文件、客户主文件等关键数据表,均列出字段名、类型、是否为空、主键外键、默认值及中文说明,并附有表说明、索引说明等补充信息。这种逐表逐字段的说明方式,能为读者快速梳理表间关系与字段口径提供有力支撑。资源包内文件总数为1个,格式为PDF,压缩后大小约867KB,轻量便于传阅。该文档目前已有114人次浏览学习,无论是用于新系统设计的参考、已有系统的结构核对,还是作为数据库课程设计的范例,都能发挥实际作用。
1. 从一份 PDF 到能落库的表结构,ERP 数据库详细设计到底在设计什么
很多团队拿到“ERP数据库详细设计说明书.pdf”这个标题的第一反应是“又一份凑数的文档”。真正做过 ERP 实施或自研的人清楚,这份文档的含金量取决于一件事:它是否把业务规则翻译成了字段级、索引级、约束级的决策。概念模型可以画 ER 图,概要设计可以定模块边界,但数据库详细设计说明书必须回答“这张表为什么有这个字段、这个字段为什么是这个类型、这条查询为什么走这个索引”。
ERP 系统的复杂度不在单表,而在表与表之间的事务边界、单据状态流转和库存账实一致。一个订单从创建到关单,涉及订单头、订单行、库存预留、批次锁定、财务凭证多张表,任何一张表的字段设计漏掉状态位或并发版本号,后续都要用补丁式的迁移来还债。这篇文章围绕“ERP 数据库详细设计说明书”拆开讲:字段怎么定、表怎么拆、索引怎么放、权限怎么落,每一步都会给你可以直接抄走的 DDL、检查脚本和参数取值边界。适合正在做 ERP 系统设计、数据库建模评审或者准备从零搭一套进销存加财务骨架的工程师。
2. 从业务对象到字段说明书:实体识别与属性拆解方法
2.1 详细设计说明书的主线是“字段级设计”
概要设计阶段我们讨论“有哪些模块”,详细设计阶段讨论的是“每个模块的表有哪些列”。一份合格的 ERP 数据库详细设计说明书,每个表的字段说明至少包含:字段名、物理类型、长度、精度、是否为空、默认值、业务含义、取值来源、关联对象、变更频率。这十项缺一不可,缺了默认值,程序里每个 insert 都要显式赋值,漏一个就是线上空指针;缺了取值来源,接口对接时不知道这个状态是用户选的还是系统算的,客制化需求来了只能逐行问。
我在评审别人的详细设计时,第一个动作是检查“单据编号”这类高频字段的生成规则是否写清。ERP 单据号通常要求格式如 SO20250315001,包含单据类型、日期和流水号。如果说明书里只写 varchar(50) 而没有写生成策略,开发自己就会实现一个“查最大号加一”的逻辑,并发一上来就撞唯一索引。说明书里必须同时写清唯一索引的列组合,这样才能压制重复实现。
2.2 用二维表拆解订单类主子结构
ERP 里最典型的表结构是主表和子表,也就是订单头和订单行。设计说明书里的第一步是把业务对象拆成“一个头、多行”的二维结构,再逐列定义。比如销售订单,头表存客户、单据日期、币别、汇率、总金额、审批状态;行表存物料编码、数量、单价、含税标志、交期、仓库、库位。头和行各自有主键,行通过订单头 ID 关联,同时要有一个行号字段保证行序稳定。
为什么不能把所有字段都拍平在头表里?因为一个订单可能有几十行物料,每一行的物料、数量、交期都不同,拍平意味着要预留几十组列,既浪费存储又让查询条件没法走索引。拆成主子表后,订单行可以按物料查、按日期查,统计报表直接打在明细表上,而且头表加一个“总行数”字段可以用于对账,防止应用层漏插明细。以下是订单头与订单行的核心表结构,按实际落库脚本简化:
CREATE TABLE sales_order ( order_id BIGINT UNSIGNED NOT NULL AUTO_INCREMENT COMMENT '订单头ID,主键', order_no VARCHAR(32) NOT NULL COMMENT '单据编号,全局唯一', customer_id BIGINT UNSIGNED NOT NULL COMMENT '客户ID,关联customer表', order_date DATE NOT NULL COMMENT '单据日期,业务日期', currency_code CHAR(3) NOT NULL DEFAULT 'CNY' COMMENT '币别代码', exchange_rate DECIMAL(12, 6) NOT NULL DEFAULT 1.000000 COMMENT '汇率,以本币为基准', total_amount DECIMAL(18, 2) NOT NULL DEFAULT 0.00 COMMENT '本币含税总额', status TINYINT NOT NULL DEFAULT 0 COMMENT '状态:0草稿 1已提交 2已审核 3已关闭', version_no INT NOT NULL DEFAULT 1 COMMENT '乐观锁版本号', created_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP, updated_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, PRIMARY KEY (order_id), UNIQUE KEY uk_order_no (order_no), KEY idx_customer_date (customer_id, order_date) ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='销售订单头表'; CREATE TABLE sales_order_line ( line_id BIGINT UNSIGNED NOT NULL AUTO_INCREMENT COMMENT '行ID,主键', order_id BIGINT UNSIGNED NOT NULL COMMENT '订单头ID,关联sales_order', line_no INT NOT NULL COMMENT '行号,从10开始,步长10', item_id BIGINT UNSIGNED NOT NULL COMMENT '物料ID,关联item表', quantity DECIMAL(18, 3) NOT NULL COMMENT '数量,按物料主数据单位', unit_price DECIMAL(18, 6) NOT NULL COMMENT '未税单价', tax_rate DECIMAL(8, 3) NOT NULL DEFAULT 0.000 COMMENT '税率百分比,如13.000表示13%', line_amount DECIMAL(18, 2) NOT NULL COMMENT '行含税金额', warehouse_id BIGINT UNSIGNED NOT NULL COMMENT '仓库ID,关联warehouse表', required_date DATE NOT NULL COMMENT '需求交期', PRIMARY KEY (line_id), UNIQUE KEY uk_order_line (order_id, line_no), KEY idx_item (item_id), KEY idx_required_date (required_date), CONSTRAINT fk_order_line_head FOREIGN KEY (order_id) REFERENCES sales_order (order_id) ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='销售订单行表';主键选自增 BIGINT 是因为 ERP 单表单日写入量不大,且 InnoDB 聚簇索引对顺序插入友好,不会像 UUID 那样引发随机 IO 和页分裂。行号从 10 开始步长 10,是为了中间插入行时不重排已有行号;如果从 1 连续编号,插入一行就要更新后续所有行,频繁更新会放大 redo log。业务编号 order_no 单独建唯一索引,不给业务主键,这样后续换单号规则时只影响这一个索引。
2.3 物料档案与扩展属性:EAV 模型的使用边界
ERP 里的物料主数据是所有业务单据的基础,但不同行业的物料属性差异巨大。一个机械行业的物料可能有“重量、材质、加工工时”,一个食品行业的物料有“保质期、储存温度、检验标准”。把全部属性都做成列的物理表会变成几百个字段的怪物,而且大部分行是稀疏的;用 EAV(实体-属性-值)模型把属性全存成行,又会让物料查询变成大量 join,性能很难看。
实际项目里常见的折中方案是“基础列加扩展表”。物料表只保留所有模块都公用的列:物料编码、名称、规格、计量单位、默认仓库、物料分类、状态。真正多变的行业属性放一张扩展属性表,表结构是 entity_id、attr_code、attr_name、attr_value(varchar)、value_type、is_mandatory。查询物料列表时只查主表,点开物料详情时再按需加载扩展属性;需要按扩展属性筛选的场景,建一张物化中间表或使用 JSON 列做条件过滤。不要在详细设计说明书里把 EAV 当成万能解,它只适合低频查询的多变属性。
2.4 金额、数量字段的类型与精度选型边界
ERP 里最容易引发争议的就是小数位。数量字段建议用 DECIMAL(18,3),因为很多物料按公斤、米、平方米计价,三位小数是常规精度;单价用 DECIMAL(18,6),价格通常允许四位甚至六位小数,用于折扣和促销场景;金额字段用 DECIMAL(18,2)。关键原则:数据库中永远不要用 FLOAT 或 DOUBLE 存储金额和数量,浮点数在二进制下无法精确表示 0.1,累计求和时误差会随着行数放大。联表算总账用 DECIMAL,应用层序列化传输时用字符串而不是浮点数。
汇率字段用 DECIMAL(12,6) 是一个容易忽略的细节。ERP 跨国业务中,一种外币对人民币的汇率可能精确到小数点后四位,而多个币种间折算时的中间汇率需要六位精度。如果说明书里把汇率定义成 DECIMAL(10,4),某些币种的尾差会导致总账试算不平衡,期末调汇时对不上。这些精度差异直接影响财务模块的月度结账,必填字段和默认值那一列不要留白。
3. 状态、时区与软删除:详细设计里必须写清的公共列规范
3.1 数据行自身的元数据列不能省
ERP 数据库详细设计说明书和普通业务表设计的另一个区别是:每张业务表几乎都要携带数据行自身的控制信息。created_at、updated_at 是底线,但很多团队漏了 created_by、updated_by,出了数据问题只能查应用日志,连哪个用户改的都不知道。created_by 和 updated_by 用 BIGINT 存用户 ID,不要存用户名,用户名会改,ID 不会变;真要显示用户名,查询时关联用户表。
还有两个字段经常被争论:逻辑删除标志位 deleted_flag 和乐观锁版本号 version_no。deleted_flag 存在的价值是防止误删导致的历史单据无法追溯;但加了它之后,所有查询都必须带 deleted_flag = 0 条件,漏掉一个就是脏数据,而且唯一索引无法对“未删除的行”生效。我在设计说明书里通常只在主数据表(物料、客户、供应商)用逻辑删除,业务单据表直接物理删除,因为单据有审批流和日志表可以回溯。version_no 用于更新场景防止并发覆盖,一个典型的更新语句:
UPDATE sales_order SET customer_id = #{newCustomerId}, version_no = version_no + 1 WHERE order_id = #{orderId} AND version_no = #{oldVersion};这段 SQL 的含义是“只在版本号匹配时才更新,并把版本号加一”。如果更新影响行数为 0,说明这条订单已经被别人改过,应用层应该提示用户刷新后重试,而不是强行覆盖。ERP 的审核节点、反审核节点都必须做这种乐观锁保护,否则两个操作员同时处理同一张单据,后提交的人会把先提交的人的修改无声覆盖掉。
3.2 日期时间的存储与多时区问题
国内单体 ERP 用 DATETIME 存本地时间问题不大,但一旦集团跨时区部署,或者门店系统上报数据到总部,DATETIME 的缺陷就暴露了。DATETIME 不带时区信息,存储的是墙上时间,不同时区的两个门店在同一个 UTC 时刻写入的 DATETIME 值不一样,总部汇总时无法知道这些时间是否真的“同时发生”。正确做法:统一使用 DATETIME 存 UTC 时间,展示层按用户时区转换;或者使用带时区的 TIMESTAMP 类型,但 TIMESTAMP 的表示范围到 2038 年,不适合存长期合同和历史档案。说明书里要明确写上“所有时间字段统一为 UTC”,并标注对应 Java 侧类型为 Instant 或 OffsetDateTime,这样接口联调时就不会出现“差八小时”的问题。
业务日期和系统时间要区分。订单的 order_date 是业务日期,允许用户在权限范围内补单时往前填;created_at 是数据库落库时间,不能被人为修改。财务模块的月结和成本核算依赖的是业务日期,不是创建时间,查询跨月单据时只能用 order_date 过滤。
3.3 可配置项优先数据字典,不写死在字段注释里
ERP 里大量字段的值域是可变的。订单状态当前是“草稿、已提交、已审核、已关闭”,明年可能加一个“已取消待退款”。不要把这些取值只写在字段注释里,要在数据库里建字典表,至少包含 dict_type、dict_code、dict_name、sort_no、enabled_flag 五个字段。应用启动时加载到本地缓存,界面下拉框从缓存取值;新增状态只改数据不加代码,审计追溯时能看到完整的历史状态流转记录。状态值本身用 TINYINT,接接口时自己维护一套枚举翻译,这样数据库里存的是紧凑数字,应用层展示的是可读文案。
4. 索引与约束设计:写路径和读路径分离时的取舍规则
4.1 索引设计必须先确认查询模式再建索引
ERP 的报表查询通常比较复杂,但详细设计说明书阶段不需要考虑所有报表 SQL,只需要覆盖主线业务查询。以销售订单为例,最常见的查询有四种:按客户查订单列表、按订单号精确查单据、按交期查未发货订单、按物料查历史价格。索引设计就是围绕这四种路径:
| 查询场景 | 建议索引 | 说明 |
|---|---|---|
| 按客户和时间范围查订单 | idx_customer_date (customer_id, order_date) | 客户过滤后按日期排序,避免文件排序 |
| 按订单号精确查询 | uk_order_no (order_no) | 唯一索引,同时承担准确性约束 |
| 按交期查未发货订单 | idx_required_date (required_date) | 单列索引即可,配合状态过滤 |
| 按物料查销售记录 | idx_item (item_id) | 高频过滤列,单列索引足够 |
复合索引的列顺序很关键。idx_customer_date 把 customer_id 放前面,因为它是等值条件;order_date 放后面,因为它用于范围扫描。如果反过来建 idx_date_customer,那“按客户过滤后对日期排序”的查询很难利用索引的有序性,MySQL 要做 filesort。详细设计说明书里要写清“哪些列是等值条件、哪些列是排序条件”,开发照着建就不会建反。索引不是越多越好,ERP 里高频写操作的表每多一个索引,insert 和 update 就要多维护一棵 B+ 树。一张写密集的订单行表,索引控制在五个以内;哪些索引真正要用,用 EXPLAIN 看实际执行计划再定。
4.2 事务账表用流水号当主键,业务表用业务编码当唯一键
ERP 里的库存流水、财务流水是追加写为主的表,一天几万到几十万条。这类表的主键设计遵循“插入友好”原则,使用 BIGINT 自增即可;同时为了保证幂等和防重,要有一个流水来源相关的业务唯一键。比如库存流水表,来源单号加行号组合唯一,防止同一张出库单被重复过账。这样的结构设计下,主键索引是热写路径,唯一键索引是幂等保护路径,两条索引互不干扰。
业务主数据表(物料、客户、供应商)的主键用自增 ID 没有问题,但必须额外建立业务编码字段并加唯一索引。原因是外部系统对接时,对方只知道物料编码、客户编码,不可能知道我方库里的自增 ID。唯一索引同时对应用层的 insert 操作起到约束作用,重复编码在数据库层就被拦截,不需要应用先查一遍再插入。
4.3 单据表按业务日期做分区,保留策略要一起写进说明书
ERP 的单据表膨胀速度很快,销售订单行表三年可能上亿行。如果不做分区,按交期查询虽然能走索引,但索引本身变得又大又深,缓存命中率下降。常见做法是按月分区表,分区键选业务日期而不是创建时间。月结的时候,上月分区变成只读,查询可以直接走分区裁剪,只扫需要的月份;归档历史数据时直接 detach 分区成为一个独立表,备份走冷存储。MySQL 里创建分区表的一个例子:
CREATE TABLE sales_order ( order_id BIGINT UNSIGNED NOT NULL, order_no VARCHAR(32) NOT NULL, order_date DATE NOT NULL, customer_id BIGINT UNSIGNED NOT NULL, PRIMARY KEY (order_id, order_date) ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 PARTITION BY RANGE COLUMNS(order_date) ( PARTITION p202501 VALUES LESS THAN ('2025-02-01'), PARTITION p202502 VALUES LESS THAN ('2025-03-01'), PARTITION p202503 VALUES LESS THAN ('2025-04-01') );注意这里主键变成了复合主键(order_id, order_date),因为 MySQL 要求分区键必须是主键或唯一键的一部分。这是一个很关键的取舍:如果你的业务查询经常按 order_id 精确查一行,复合主键不影响,InnoDB 会先用 order_id 定位再按分区裁剪;但如果你依赖 order_id 单列唯一,分区就会破坏全局唯一约束,需要引入额外字段组合。说明书里必须把这种约束写清楚,否则开发在单机库里测得好好的,上生产分区表后主键冲突才暴露。每月月底执行一次分区维护 SQL,把下下个月的新分区建好,同时把三个月前的分区设为只读,避免业务数据被误改。
5. 多租户与权限模型:ERP 数据库里的组织隔离设计
5.1 多公司架构用 company_id 还是 depart_id
集团型 ERP 通常有多法人结构,A 公司和 B 公司各自独立核算,但共用一套数据库。组织隔离有两个方案:一个是在核心业务表上增加 company_id(或 org_id)字段,所有查询强制带这个条件;另一个是每个公司一套独立数据库,物理隔离。后者的运维成本高、跨公司汇总报表难写,绝大多数实施项目选前者。
在详细设计说明书里,company_id 的放置策略可以这样定:表里有金额和库存的,比如订单、出入库单、凭证,必须带 company_id;主数据表如物料、客户,如果不跨公司共享,也带 company_id;如果是集团统一维护的主数据,比如会计科目表,则用另一个字段 group_id 标识集团层,company_id 为空或为 0。查询时所有 SQL 的 where 条件里强制带 company_id,索引设计把 company_id 放在最左边作为等值前缀,这样不同公司的数据天然分散在不同索引分支上,不会互相干扰。
5.2 RBAC 权限模型的五张核心表
ERP 数据库详细设计说明书里的权限部分通常直接落地为五张表:用户表、角色表、用户角色关联表、菜单/功能权限表、角色权限关联表。用户表不带角色字段,角色表不带权限字段,全部用中间表关联,这样才能支持一个用户多角色、一个角色多权限的常见需求。行级数据权限没法用这五张表解决,需要在业务表上加数据范围字段,比如只能看本部门的订单,就在订单头上加 create_depart_id,查询时用当前用户所属部门过滤。ERP 实施里还有一种做法:数据权限用专门的规则表存储,规则内容是“用户-角色-数据范围编码”的组合,查询时动态拼接 where 条件,但这会让 SQL 无法走缓存,线上需要仔细评估性能。
5.3 敏感字段的加密与脱敏设计
ERP 里的客户手机号、银行账号属于敏感数据。详细设计说明书里要明确:密码类字段用哈希加盐存储,不能用可逆加密更不能明文;手机号和证件号这类需要展示的信息,在库里存密文并配套明文索引字段。明文索引字段只用于精确查询,比如注册时按手机号查重,查询后立即丢弃;列表展示全部走脱敏函数,比如 138****5678。不要把加密逻辑写在应用层每人一套,数据库侧统一函数或统一中间件处理,才能保证所有入口一致。
6. 用检查脚本验证表结构与说明书不一致的地方
详细设计说明书交付后,真正体现价值的动作是核对“设计文档”和“实际库结构”是否一致。表缺字段、字段类型不一致、索引缺失,这些差异在开发自测阶段很难发现,但不一致会导致上线后 SQL 报错、慢查询、数据错乱。我一般会在每个迭代结束时跑一组对照脚本,批量检查数据库里每张表是否按说明书实现了字段和索引。
SELECT t.TABLE_NAME, c.COLUMN_NAME, c.DATA_TYPE, c.COLUMN_TYPE, c.IS_NULLABLE, c.COLUMN_DEFAULT FROM information_schema.COLUMNS c JOIN information_schema.TABLES t ON c.TABLE_SCHEMA = t.TABLE_SCHEMA AND c.TABLE_NAME = t.TABLE_NAME WHERE t.TABLE_SCHEMA = 'erp_main' AND t.TABLE_TYPE = 'BASE TABLE' AND c.COLUMN_NAME IN ('order_no', 'order_date', 'status', 'version_no') ORDER BY t.TABLE_NAME, c.ORDINAL_POSITION;这个查询会把 erp_main 库中所有含指定字段的表列出来,一次看清哪些表缺少这些公共字段,字段类型和默认值也能一并核对。缺失字段的表就是详细设计说明书与建库脚本不一致的地方,逐张补上。索引部分用 information_schema.STATISTICS 查询,检查每张表的唯一索引和复合索引是否与说明书里的索引清单一致,尤其注意复合索引的列顺序,顺序反了查询不走索引是最难发现的问题。最后再用一个字段注释完整性检查,找出所有没有 COMMENT 的列,注释缺失说明建表脚本不是从说明书生成的,而是开发随手写的。
本文还有配套的精品资源,点击获取