ERP数据库详细设计说明书:字段、索引与权限的落地实践
发布时间:2026/9/18 11:52:03 作者:尧图编辑部 阅读量:1,286

简介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) ) ENGINEInnoDB DEFAULT CHARSETutf8mb4 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) ) ENGINEInnoDB DEFAULT CHARSETutf8mb4 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_valuevarchar、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) ) ENGINEInnoDB DEFAULT CHARSETutf8mb4 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 的列注释缺失说明建表脚本不是从说明书生成的而是开发随手写的。本文还有配套的精品资源点击获取